From 1f59e703d82b47f3aeaf432045a2633c2841169b Mon Sep 17 00:00:00 2001 From: Christian Grothoff Date: Mon, 11 Feb 2019 20:39:36 +0100 Subject: [PATCH] initial import from gnunet.git --- ABOUT-NLS | 1282 +++ AUTHORS | 1 + COPYING | 674 ++ ChangeLog | 14 + INSTALL | 365 + Makefile.am | 9 + NEWS | 0 README | 1 + bootstrap | 8 + config.rpath | 672 ++ configure.ac | 221 + m4/ac_define_dir.m4 | 35 + m4/gettext.m4 | 383 + m4/iconv.m4 | 214 + m4/lib-ld.m4 | 110 + m4/lib-link.m4 | 774 ++ m4/lib-prefix.m4 | 224 + m4/libtool.m4 | 8387 +++++++++++++++++ m4/ltoptions.m4 | 437 + m4/ltsugar.m4 | 124 + m4/ltversion.m4 | 23 + m4/lt~obsolete.m4 | 99 + m4/nls.m4 | 32 + m4/pkg.m4 | 57 + m4/po.m4 | 449 + m4/progtest.m4 | 92 + pkgconfig/Makefile.am | 25 + pkgconfig/gnunetmulticast.pc.in | 12 + pkgconfig/gnunetpsyc.pc.in | 12 + pkgconfig/gnunetpsycstore.pc.in | 12 + po/.gitignore | 6 + po/ChangeLog | 12 + po/Makefile.in.in | 444 + po/Makevars | 41 + po/POTFILES.in | 2 + po/Rules-quot | 47 + po/boldquot.sed | 10 + po/en@boldquot.header | 25 + po/en@quot.header | 22 + po/insert-header.sin | 23 + po/quot.sed | 6 + po/remove-potcdate.sin | 19 + src/.gitignore | 2 + src/Makefile.am | 7 + src/include/.gitignore | 2 + src/include/Makefile.am | 15 + src/include/gnunet_multicast_service.h | 925 ++ src/include/gnunet_psyc_env.h | 340 + src/include/gnunet_psyc_message.h | 278 + src/include/gnunet_psyc_service.h | 1364 +++ src/include/gnunet_psyc_slicer.h | 378 + src/include/gnunet_psyc_util_lib.h | 53 + src/include/gnunet_psycstore_plugin.h | 383 + src/include/gnunet_psycstore_service.h | 701 ++ src/include/gnunet_social_service.h | 1344 +++ src/multicast/.gitignore | 7 + src/multicast/Makefile.am | 79 + src/multicast/gnunet-multicast.c | 79 + src/multicast/gnunet-service-multicast.c | 2234 +++++ src/multicast/multicast.conf.in | 22 + src/multicast/multicast.h | 303 + src/multicast/multicast_api.c | 1399 +++ src/multicast/test_multicast.c | 758 ++ src/multicast/test_multicast.conf | 56 + src/multicast/test_multicast_2peers.c | 520 + src/multicast/test_multicast_line.conf | 63 + src/multicast/test_multicast_multipeer.c | 643 ++ src/multicast/test_multicast_star.conf | 64 + src/psyc/.gitignore | 2 + src/psyc/Makefile.am | 77 + src/psyc/gnunet-service-psyc.c | 2860 ++++++ src/psyc/psyc.conf.in | 12 + src/psyc/psyc.h | 178 + src/psyc/psyc_api.c | 1584 ++++ src/psyc/psyc_test_lib.h | 67 + src/psyc/test_psyc.c | 1018 ++ src/psyc/test_psyc2.c | 284 + src/psyc/test_psyc_api_join.c | 282 + src/psycstore/.gitignore | 5 + src/psycstore/Makefile.am | 155 + src/psycstore/gnunet-service-psycstore.c | 1049 +++ src/psycstore/plugin_psycstore_mysql.c | 1960 ++++ src/psycstore/plugin_psycstore_postgres.c | 1530 +++ src/psycstore/plugin_psycstore_sqlite.c | 1948 ++++ src/psycstore/psycstore.conf.in | 28 + src/psycstore/psycstore.h | 520 + src/psycstore/psycstore_api.c | 1285 +++ src/psycstore/test_plugin_psycstore.c | 532 ++ .../test_plugin_psycstore_mysql.conf | 7 + .../test_plugin_psycstore_postgres.conf | 2 + .../test_plugin_psycstore_sqlite.conf | 2 + src/psycstore/test_psycstore.c | 586 ++ src/psycstore/test_psycstore.conf | 8 + src/psycutil/.gitignore | 1 + src/psycutil/Makefile.am | 45 + src/psycutil/psyc_env.c | 196 + src/psycutil/psyc_message.c | 1355 +++ src/psycutil/psyc_slicer.c | 711 ++ src/psycutil/test_psyc_env.c | 96 + src/social/.gitignore | 3 + src/social/Makefile.am | 79 + src/social/gnunet-service-social.c | 3760 ++++++++ src/social/gnunet-social.c | 1411 +++ src/social/social.conf.in | 15 + src/social/social.h | 292 + src/social/social_api.c | 2827 ++++++ src/social/test_social.c | 1449 +++ src/social/test_social.conf | 19 + 108 files changed, 55664 insertions(+) create mode 100644 ABOUT-NLS create mode 100644 AUTHORS create mode 100644 COPYING create mode 100644 ChangeLog create mode 100644 INSTALL create mode 100644 Makefile.am create mode 100644 NEWS create mode 100644 README create mode 100755 bootstrap create mode 100755 config.rpath create mode 100644 configure.ac create mode 100644 m4/ac_define_dir.m4 create mode 100644 m4/gettext.m4 create mode 100644 m4/iconv.m4 create mode 100644 m4/lib-ld.m4 create mode 100644 m4/lib-link.m4 create mode 100644 m4/lib-prefix.m4 create mode 100644 m4/libtool.m4 create mode 100644 m4/ltoptions.m4 create mode 100644 m4/ltsugar.m4 create mode 100644 m4/ltversion.m4 create mode 100644 m4/lt~obsolete.m4 create mode 100644 m4/nls.m4 create mode 100644 m4/pkg.m4 create mode 100644 m4/po.m4 create mode 100644 m4/progtest.m4 create mode 100644 pkgconfig/Makefile.am create mode 100644 pkgconfig/gnunetmulticast.pc.in create mode 100644 pkgconfig/gnunetpsyc.pc.in create mode 100644 pkgconfig/gnunetpsycstore.pc.in create mode 100644 po/.gitignore create mode 100644 po/ChangeLog create mode 100644 po/Makefile.in.in create mode 100644 po/Makevars create mode 100644 po/POTFILES.in create mode 100644 po/Rules-quot create mode 100644 po/boldquot.sed create mode 100644 po/en@boldquot.header create mode 100644 po/en@quot.header create mode 100644 po/insert-header.sin create mode 100644 po/quot.sed create mode 100644 po/remove-potcdate.sin create mode 100644 src/.gitignore create mode 100644 src/Makefile.am create mode 100644 src/include/.gitignore create mode 100644 src/include/Makefile.am create mode 100644 src/include/gnunet_multicast_service.h create mode 100644 src/include/gnunet_psyc_env.h create mode 100644 src/include/gnunet_psyc_message.h create mode 100644 src/include/gnunet_psyc_service.h create mode 100644 src/include/gnunet_psyc_slicer.h create mode 100644 src/include/gnunet_psyc_util_lib.h create mode 100644 src/include/gnunet_psycstore_plugin.h create mode 100644 src/include/gnunet_psycstore_service.h create mode 100644 src/include/gnunet_social_service.h create mode 100644 src/multicast/.gitignore create mode 100644 src/multicast/Makefile.am create mode 100644 src/multicast/gnunet-multicast.c create mode 100644 src/multicast/gnunet-service-multicast.c create mode 100644 src/multicast/multicast.conf.in create mode 100644 src/multicast/multicast.h create mode 100644 src/multicast/multicast_api.c create mode 100644 src/multicast/test_multicast.c create mode 100644 src/multicast/test_multicast.conf create mode 100644 src/multicast/test_multicast_2peers.c create mode 100644 src/multicast/test_multicast_line.conf create mode 100644 src/multicast/test_multicast_multipeer.c create mode 100644 src/multicast/test_multicast_star.conf create mode 100644 src/psyc/.gitignore create mode 100644 src/psyc/Makefile.am create mode 100644 src/psyc/gnunet-service-psyc.c create mode 100644 src/psyc/psyc.conf.in create mode 100644 src/psyc/psyc.h create mode 100644 src/psyc/psyc_api.c create mode 100644 src/psyc/psyc_test_lib.h create mode 100644 src/psyc/test_psyc.c create mode 100644 src/psyc/test_psyc2.c create mode 100644 src/psyc/test_psyc_api_join.c create mode 100644 src/psycstore/.gitignore create mode 100644 src/psycstore/Makefile.am create mode 100644 src/psycstore/gnunet-service-psycstore.c create mode 100644 src/psycstore/plugin_psycstore_mysql.c create mode 100644 src/psycstore/plugin_psycstore_postgres.c create mode 100644 src/psycstore/plugin_psycstore_sqlite.c create mode 100644 src/psycstore/psycstore.conf.in create mode 100644 src/psycstore/psycstore.h create mode 100644 src/psycstore/psycstore_api.c create mode 100644 src/psycstore/test_plugin_psycstore.c create mode 100644 src/psycstore/test_plugin_psycstore_mysql.conf create mode 100644 src/psycstore/test_plugin_psycstore_postgres.conf create mode 100644 src/psycstore/test_plugin_psycstore_sqlite.conf create mode 100644 src/psycstore/test_psycstore.c create mode 100644 src/psycstore/test_psycstore.conf create mode 100644 src/psycutil/.gitignore create mode 100644 src/psycutil/Makefile.am create mode 100644 src/psycutil/psyc_env.c create mode 100644 src/psycutil/psyc_message.c create mode 100644 src/psycutil/psyc_slicer.c create mode 100644 src/psycutil/test_psyc_env.c create mode 100644 src/social/.gitignore create mode 100644 src/social/Makefile.am create mode 100644 src/social/gnunet-service-social.c create mode 100644 src/social/gnunet-social.c create mode 100644 src/social/social.conf.in create mode 100644 src/social/social.h create mode 100644 src/social/social_api.c create mode 100644 src/social/test_social.c create mode 100644 src/social/test_social.conf diff --git a/ABOUT-NLS b/ABOUT-NLS new file mode 100644 index 0000000..b1de1b6 --- /dev/null +++ b/ABOUT-NLS @@ -0,0 +1,1282 @@ +1 Notes on the Free Translation Project +*************************************** + +Free software is going international! The Free Translation Project is +a way to get maintainers of free software, translators, and users all +together, so that free software will gradually become able to speak many +languages. A few packages already provide translations for their +messages. + + If you found this `ABOUT-NLS' file inside a distribution, you may +assume that the distributed package does use GNU `gettext' internally, +itself available at your nearest GNU archive site. But you do _not_ +need to install GNU `gettext' prior to configuring, installing or using +this package with messages translated. + + Installers will find here some useful hints. These notes also +explain how users should proceed for getting the programs to use the +available translations. They tell how people wanting to contribute and +work on translations can contact the appropriate team. + +1.1 INSTALL Matters +=================== + +Some packages are "localizable" when properly installed; the programs +they contain can be made to speak your own native language. Most such +packages use GNU `gettext'. Other packages have their own ways to +internationalization, predating GNU `gettext'. + + By default, this package will be installed to allow translation of +messages. It will automatically detect whether the system already +provides the GNU `gettext' functions. Installers may use special +options at configuration time for changing the default behaviour. The +command: + + ./configure --disable-nls + +will _totally_ disable translation of messages. + + When you already have GNU `gettext' installed on your system and run +configure without an option for your new package, `configure' will +probably detect the previously built and installed `libintl' library +and will decide to use it. If not, you may have to to use the +`--with-libintl-prefix' option to tell `configure' where to look for it. + + Internationalized packages usually have many `po/LL.po' files, where +LL gives an ISO 639 two-letter code identifying the language. Unless +translations have been forbidden at `configure' time by using the +`--disable-nls' switch, all available translations are installed +together with the package. However, the environment variable `LINGUAS' +may be set, prior to configuration, to limit the installed set. +`LINGUAS' should then contain a space separated list of two-letter +codes, stating which languages are allowed. + +1.2 Using This Package +====================== + +As a user, if your language has been installed for this package, you +only have to set the `LANG' environment variable to the appropriate +`LL_CC' combination. If you happen to have the `LC_ALL' or some other +`LC_xxx' environment variables set, you should unset them before +setting `LANG', otherwise the setting of `LANG' will not have the +desired effect. Here `LL' is an ISO 639 two-letter language code, and +`CC' is an ISO 3166 two-letter country code. For example, let's +suppose that you speak German and live in Germany. At the shell +prompt, merely execute `setenv LANG de_DE' (in `csh'), +`export LANG; LANG=de_DE' (in `sh') or `export LANG=de_DE' (in `bash'). +This can be done from your `.login' or `.profile' file, once and for +all. + + You might think that the country code specification is redundant. +But in fact, some languages have dialects in different countries. For +example, `de_AT' is used for Austria, and `pt_BR' for Brazil. The +country code serves to distinguish the dialects. + + The locale naming convention of `LL_CC', with `LL' denoting the +language and `CC' denoting the country, is the one use on systems based +on GNU libc. On other systems, some variations of this scheme are +used, such as `LL' or `LL_CC.ENCODING'. You can get the list of +locales supported by your system for your language by running the +command `locale -a | grep '^LL''. + + Not all programs have translations for all languages. By default, an +English message is shown in place of a nonexistent translation. If you +understand other languages, you can set up a priority list of languages. +This is done through a different environment variable, called +`LANGUAGE'. GNU `gettext' gives preference to `LANGUAGE' over `LANG' +for the purpose of message handling, but you still need to have `LANG' +set to the primary language; this is required by other parts of the +system libraries. For example, some Swedish users who would rather +read translations in German than English for when Swedish is not +available, set `LANGUAGE' to `sv:de' while leaving `LANG' to `sv_SE'. + + Special advice for Norwegian users: The language code for Norwegian +bokma*l changed from `no' to `nb' recently (in 2003). During the +transition period, while some message catalogs for this language are +installed under `nb' and some older ones under `no', it's recommended +for Norwegian users to set `LANGUAGE' to `nb:no' so that both newer and +older translations are used. + + In the `LANGUAGE' environment variable, but not in the `LANG' +environment variable, `LL_CC' combinations can be abbreviated as `LL' +to denote the language's main dialect. For example, `de' is equivalent +to `de_DE' (German as spoken in Germany), and `pt' to `pt_PT' +(Portuguese as spoken in Portugal) in this context. + +1.3 Translating Teams +===================== + +For the Free Translation Project to be a success, we need interested +people who like their own language and write it well, and who are also +able to synergize with other translators speaking the same language. +Each translation team has its own mailing list. The up-to-date list of +teams can be found at the Free Translation Project's homepage, +`http://translationproject.org/', in the "Teams" area. + + If you'd like to volunteer to _work_ at translating messages, you +should become a member of the translating team for your own language. +The subscribing address is _not_ the same as the list itself, it has +`-request' appended. For example, speakers of Swedish can send a +message to `sv-request@li.org', having this message body: + + subscribe + + Keep in mind that team members are expected to participate +_actively_ in translations, or at solving translational difficulties, +rather than merely lurking around. If your team does not exist yet and +you want to start one, or if you are unsure about what to do or how to +get started, please write to `coordinator@translationproject.org' to +reach the coordinator for all translator teams. + + The English team is special. It works at improving and uniformizing +the terminology in use. Proven linguistic skills are praised more than +programming skills, here. + +1.4 Available Packages +====================== + +Languages are not equally supported in all packages. The following +matrix shows the current state of internationalization, as of June +2010. The matrix shows, in regard of each package, for which languages +PO files have been submitted to translation coordination, with a +translation percentage of at least 50%. + + Ready PO files af am an ar as ast az be be@latin bg bn_IN bs ca + +--------------------------------------------------+ + a2ps | [] [] | + aegis | | + ant-phone | | + anubis | | + aspell | [] [] | + bash | | + bfd | | + bibshelf | [] | + binutils | | + bison | | + bison-runtime | [] | + bluez-pin | [] [] | + bombono-dvd | | + buzztard | | + cflow | | + clisp | | + coreutils | [] [] | + cpio | | + cppi | | + cpplib | [] | + cryptsetup | | + dfarc | | + dialog | [] [] | + dico | | + diffutils | [] | + dink | | + doodle | | + e2fsprogs | [] | + enscript | [] | + exif | | + fetchmail | [] | + findutils | [] | + flex | [] | + freedink | | + gas | | + gawk | [] [] | + gcal | [] | + gcc | | + gettext-examples | [] [] [] [] | + gettext-runtime | [] [] | + gettext-tools | [] [] | + gip | [] | + gjay | | + gliv | [] | + glunarclock | [] [] | + gnubiff | | + gnucash | [] | + gnuedu | | + gnulib | | + gnunet | | + gnunet-gtk | | + gnutls | | + gold | | + gpe-aerial | | + gpe-beam | | + gpe-bluetooth | | + gpe-calendar | | + gpe-clock | [] | + gpe-conf | | + gpe-contacts | | + gpe-edit | | + gpe-filemanager | | + gpe-go | | + gpe-login | | + gpe-ownerinfo | [] | + gpe-package | | + gpe-sketchbook | | + gpe-su | [] | + gpe-taskmanager | [] | + gpe-timesheet | [] | + gpe-today | [] | + gpe-todo | | + gphoto2 | | + gprof | [] | + gpsdrive | | + gramadoir | | + grep | | + grub | [] [] | + gsasl | | + gss | | + gst-plugins-bad | [] | + gst-plugins-base | [] | + gst-plugins-good | [] | + gst-plugins-ugly | [] | + gstreamer | [] [] [] | + gtick | | + gtkam | [] | + gtkorphan | [] | + gtkspell | [] [] [] | + gutenprint | | + hello | [] | + help2man | | + hylafax | | + idutils | | + indent | [] [] | + iso_15924 | | + iso_3166 | [] [] [] [] [] [] [] | + iso_3166_2 | | + iso_4217 | | + iso_639 | [] [] [] [] | + iso_639_3 | | + jwhois | | + kbd | | + keytouch | [] | + keytouch-editor | | + keytouch-keyboa... | [] | + klavaro | [] | + latrine | | + ld | [] | + leafpad | [] [] | + libc | [] [] | + libexif | () | + libextractor | | + libgnutls | | + libgpewidget | | + libgpg-error | | + libgphoto2 | | + libgphoto2_port | | + libgsasl | | + libiconv | [] | + libidn | | + lifelines | | + liferea | [] [] | + lilypond | | + linkdr | [] | + lordsawar | | + lprng | | + lynx | [] | + m4 | | + mailfromd | | + mailutils | | + make | | + man-db | | + man-db-manpages | | + minicom | | + mkisofs | | + myserver | | + nano | [] [] | + opcodes | | + parted | | + pies | | + popt | | + psmisc | | + pspp | [] | + pwdutils | | + radius | [] | + recode | [] [] | + rosegarden | | + rpm | | + rush | | + sarg | | + screem | | + scrollkeeper | [] [] [] | + sed | [] [] | + sharutils | [] [] | + shishi | | + skencil | | + solfege | | + solfege-manual | | + soundtracker | | + sp | | + sysstat | | + tar | [] | + texinfo | | + tin | | + unicode-han-tra... | | + unicode-transla... | | + util-linux-ng | [] | + vice | | + vmm | | + vorbis-tools | | + wastesedge | | + wdiff | | + wget | [] [] | + wyslij-po | | + xchat | [] [] [] [] | + xdg-user-dirs | [] [] [] [] [] [] [] [] [] | + xkeyboard-config | [] [] | + +--------------------------------------------------+ + af am an ar as ast az be be@latin bg bn_IN bs ca + 6 0 1 2 3 19 1 10 3 28 3 1 38 + + crh cs da de el en en_GB en_ZA eo es et eu fa + +-------------------------------------------------+ + a2ps | [] [] [] [] [] [] [] | + aegis | [] [] [] | + ant-phone | [] () | + anubis | [] [] | + aspell | [] [] [] [] [] | + bash | [] [] [] | + bfd | [] | + bibshelf | [] [] [] | + binutils | [] | + bison | [] [] | + bison-runtime | [] [] [] [] | + bluez-pin | [] [] [] [] [] [] | + bombono-dvd | [] | + buzztard | [] [] [] | + cflow | [] [] | + clisp | [] [] [] [] | + coreutils | [] [] [] [] | + cpio | | + cppi | | + cpplib | [] [] [] | + cryptsetup | [] | + dfarc | [] [] [] | + dialog | [] [] [] [] [] | + dico | | + diffutils | [] [] [] [] [] [] | + dink | [] [] [] | + doodle | [] | + e2fsprogs | [] [] [] | + enscript | [] [] [] | + exif | () [] [] | + fetchmail | [] [] () [] [] [] | + findutils | [] [] [] | + flex | [] [] | + freedink | [] [] [] | + gas | [] | + gawk | [] [] [] | + gcal | [] | + gcc | [] [] | + gettext-examples | [] [] [] [] | + gettext-runtime | [] [] [] [] | + gettext-tools | [] [] [] | + gip | [] [] [] [] | + gjay | [] | + gliv | [] [] [] | + glunarclock | [] [] | + gnubiff | () | + gnucash | [] () () () () | + gnuedu | [] [] | + gnulib | [] [] | + gnunet | | + gnunet-gtk | [] | + gnutls | [] [] | + gold | [] | + gpe-aerial | [] [] [] [] | + gpe-beam | [] [] [] [] | + gpe-bluetooth | [] [] | + gpe-calendar | [] | + gpe-clock | [] [] [] [] | + gpe-conf | [] [] [] | + gpe-contacts | [] [] [] | + gpe-edit | [] [] | + gpe-filemanager | [] [] [] | + gpe-go | [] [] [] [] | + gpe-login | [] [] | + gpe-ownerinfo | [] [] [] [] | + gpe-package | [] [] [] | + gpe-sketchbook | [] [] [] [] | + gpe-su | [] [] [] [] | + gpe-taskmanager | [] [] [] [] | + gpe-timesheet | [] [] [] [] | + gpe-today | [] [] [] [] | + gpe-todo | [] [] [] | + gphoto2 | [] [] () [] [] [] | + gprof | [] [] [] | + gpsdrive | [] [] [] | + gramadoir | [] [] [] | + grep | [] | + grub | [] [] | + gsasl | [] | + gss | | + gst-plugins-bad | [] [] [] [] [] | + gst-plugins-base | [] [] [] [] [] | + gst-plugins-good | [] [] [] [] [] [] | + gst-plugins-ugly | [] [] [] [] [] [] | + gstreamer | [] [] [] [] [] | + gtick | [] () [] | + gtkam | [] [] () [] [] | + gtkorphan | [] [] [] [] | + gtkspell | [] [] [] [] [] [] [] | + gutenprint | [] [] [] | + hello | [] [] [] [] | + help2man | [] | + hylafax | [] [] | + idutils | [] [] | + indent | [] [] [] [] [] [] [] | + iso_15924 | [] () [] [] | + iso_3166 | [] [] [] [] () [] [] [] () | + iso_3166_2 | () | + iso_4217 | [] [] [] () [] [] | + iso_639 | [] [] [] [] () [] [] | + iso_639_3 | [] | + jwhois | [] | + kbd | [] [] [] [] [] | + keytouch | [] [] | + keytouch-editor | [] [] | + keytouch-keyboa... | [] | + klavaro | [] [] [] [] | + latrine | [] () | + ld | [] [] | + leafpad | [] [] [] [] [] [] | + libc | [] [] [] [] | + libexif | [] [] () | + libextractor | | + libgnutls | [] | + libgpewidget | [] [] | + libgpg-error | [] [] | + libgphoto2 | [] () | + libgphoto2_port | [] () [] | + libgsasl | | + libiconv | [] [] [] [] [] | + libidn | [] [] [] | + lifelines | [] () | + liferea | [] [] [] [] [] | + lilypond | [] [] [] | + linkdr | [] [] [] | + lordsawar | [] | + lprng | | + lynx | [] [] [] [] | + m4 | [] [] [] [] | + mailfromd | | + mailutils | [] | + make | [] [] [] | + man-db | | + man-db-manpages | | + minicom | [] [] [] [] | + mkisofs | | + myserver | | + nano | [] [] [] | + opcodes | [] [] | + parted | [] [] | + pies | | + popt | [] [] [] [] [] | + psmisc | [] [] [] | + pspp | [] | + pwdutils | [] | + radius | [] | + recode | [] [] [] [] [] [] | + rosegarden | () () () | + rpm | [] [] [] | + rush | | + sarg | | + screem | | + scrollkeeper | [] [] [] [] [] | + sed | [] [] [] [] [] [] | + sharutils | [] [] [] [] | + shishi | | + skencil | [] () [] | + solfege | [] [] [] | + solfege-manual | [] [] | + soundtracker | [] [] [] | + sp | [] | + sysstat | [] [] [] | + tar | [] [] [] [] | + texinfo | [] [] [] | + tin | [] [] | + unicode-han-tra... | | + unicode-transla... | | + util-linux-ng | [] [] [] [] | + vice | () () | + vmm | [] | + vorbis-tools | [] [] | + wastesedge | [] | + wdiff | [] [] | + wget | [] [] [] | + wyslij-po | | + xchat | [] [] [] [] [] | + xdg-user-dirs | [] [] [] [] [] [] [] [] [] | + xkeyboard-config | [] [] [] [] [] [] | + +-------------------------------------------------+ + crh cs da de el en en_GB en_ZA eo es et eu fa + 5 64 105 117 18 1 8 0 28 89 18 19 0 + + fi fr ga gl gu he hi hr hu hy id is it ja ka kn + +----------------------------------------------------+ + a2ps | [] [] [] [] | + aegis | [] [] | + ant-phone | [] [] | + anubis | [] [] [] [] | + aspell | [] [] [] [] | + bash | [] [] [] [] | + bfd | [] [] [] | + bibshelf | [] [] [] [] [] | + binutils | [] [] [] | + bison | [] [] [] [] | + bison-runtime | [] [] [] [] [] [] | + bluez-pin | [] [] [] [] [] [] [] [] | + bombono-dvd | [] | + buzztard | [] | + cflow | [] [] [] | + clisp | [] | + coreutils | [] [] [] [] [] | + cpio | [] [] [] [] | + cppi | [] [] | + cpplib | [] [] [] | + cryptsetup | [] [] [] | + dfarc | [] [] [] | + dialog | [] [] [] [] [] [] [] | + dico | | + diffutils | [] [] [] [] [] [] [] [] [] | + dink | [] | + doodle | [] [] | + e2fsprogs | [] [] | + enscript | [] [] [] [] | + exif | [] [] [] [] [] [] | + fetchmail | [] [] [] [] | + findutils | [] [] [] [] [] [] | + flex | [] [] [] | + freedink | [] [] [] | + gas | [] [] | + gawk | [] [] [] [] () [] | + gcal | [] | + gcc | [] | + gettext-examples | [] [] [] [] [] [] [] | + gettext-runtime | [] [] [] [] [] [] | + gettext-tools | [] [] [] [] | + gip | [] [] [] [] [] [] | + gjay | [] | + gliv | [] () | + glunarclock | [] [] [] [] | + gnubiff | () [] () | + gnucash | () () () () () [] | + gnuedu | [] [] | + gnulib | [] [] [] [] [] [] | + gnunet | | + gnunet-gtk | [] | + gnutls | [] [] | + gold | [] [] | + gpe-aerial | [] [] [] | + gpe-beam | [] [] [] [] | + gpe-bluetooth | [] [] [] [] | + gpe-calendar | [] [] | + gpe-clock | [] [] [] [] [] | + gpe-conf | [] [] [] [] | + gpe-contacts | [] [] [] [] | + gpe-edit | [] [] [] | + gpe-filemanager | [] [] [] [] | + gpe-go | [] [] [] [] [] | + gpe-login | [] [] [] | + gpe-ownerinfo | [] [] [] [] [] | + gpe-package | [] [] [] | + gpe-sketchbook | [] [] [] [] | + gpe-su | [] [] [] [] [] [] | + gpe-taskmanager | [] [] [] [] [] | + gpe-timesheet | [] [] [] [] [] | + gpe-today | [] [] [] [] [] [] [] | + gpe-todo | [] [] [] | + gphoto2 | [] [] [] [] [] [] | + gprof | [] [] [] [] | + gpsdrive | [] [] [] | + gramadoir | [] [] [] | + grep | [] [] | + grub | [] [] [] [] | + gsasl | [] [] [] [] [] | + gss | [] [] [] [] [] | + gst-plugins-bad | [] [] [] [] [] [] | + gst-plugins-base | [] [] [] [] [] [] | + gst-plugins-good | [] [] [] [] [] [] | + gst-plugins-ugly | [] [] [] [] [] [] | + gstreamer | [] [] [] [] [] | + gtick | [] [] [] [] [] | + gtkam | [] [] [] [] [] | + gtkorphan | [] [] [] | + gtkspell | [] [] [] [] [] [] [] [] [] | + gutenprint | [] [] [] [] | + hello | [] [] [] | + help2man | [] [] | + hylafax | [] | + idutils | [] [] [] [] [] [] | + indent | [] [] [] [] [] [] [] [] | + iso_15924 | [] () [] [] | + iso_3166 | [] () [] [] [] [] [] [] [] [] [] [] | + iso_3166_2 | () [] [] [] | + iso_4217 | [] () [] [] [] [] | + iso_639 | [] () [] [] [] [] [] [] [] | + iso_639_3 | () [] [] | + jwhois | [] [] [] [] [] | + kbd | [] [] | + keytouch | [] [] [] [] [] [] | + keytouch-editor | [] [] [] [] [] | + keytouch-keyboa... | [] [] [] [] [] | + klavaro | [] [] | + latrine | [] [] [] | + ld | [] [] [] [] | + leafpad | [] [] [] [] [] [] [] () | + libc | [] [] [] [] [] | + libexif | [] | + libextractor | | + libgnutls | [] [] | + libgpewidget | [] [] [] [] | + libgpg-error | [] [] | + libgphoto2 | [] [] [] | + libgphoto2_port | [] [] [] | + libgsasl | [] [] [] [] [] | + libiconv | [] [] [] [] [] [] | + libidn | [] [] [] [] | + lifelines | () | + liferea | [] [] [] [] | + lilypond | [] [] | + linkdr | [] [] [] [] [] | + lordsawar | | + lprng | [] | + lynx | [] [] [] [] [] | + m4 | [] [] [] [] [] [] | + mailfromd | | + mailutils | [] [] | + make | [] [] [] [] [] [] [] [] [] | + man-db | [] [] | + man-db-manpages | [] | + minicom | [] [] [] [] [] | + mkisofs | [] [] [] [] | + myserver | | + nano | [] [] [] [] [] [] | + opcodes | [] [] [] [] | + parted | [] [] [] [] | + pies | | + popt | [] [] [] [] [] [] [] [] [] | + psmisc | [] [] [] | + pspp | | + pwdutils | [] [] | + radius | [] [] | + recode | [] [] [] [] [] [] [] [] | + rosegarden | () () () () () | + rpm | [] [] | + rush | | + sarg | [] | + screem | [] [] | + scrollkeeper | [] [] [] [] | + sed | [] [] [] [] [] [] [] [] | + sharutils | [] [] [] [] [] [] [] | + shishi | [] | + skencil | [] | + solfege | [] [] [] [] | + solfege-manual | [] [] | + soundtracker | [] [] | + sp | [] () | + sysstat | [] [] [] [] [] | + tar | [] [] [] [] [] [] [] | + texinfo | [] [] [] [] | + tin | [] | + unicode-han-tra... | | + unicode-transla... | [] [] | + util-linux-ng | [] [] [] [] [] [] | + vice | () () () | + vmm | [] | + vorbis-tools | [] | + wastesedge | () () | + wdiff | [] | + wget | [] [] [] [] [] [] [] [] | + wyslij-po | [] [] [] | + xchat | [] [] [] [] [] [] [] [] [] | + xdg-user-dirs | [] [] [] [] [] [] [] [] [] [] [] [] [] | + xkeyboard-config | [] [] [] [] [] | + +----------------------------------------------------+ + fi fr ga gl gu he hi hr hu hy id is it ja ka kn + 105 121 53 20 4 8 3 5 53 2 120 5 84 67 0 4 + + ko ku ky lg lt lv mk ml mn mr ms mt nb nds ne + +-----------------------------------------------+ + a2ps | [] | + aegis | | + ant-phone | | + anubis | [] [] | + aspell | [] | + bash | | + bfd | | + bibshelf | [] [] | + binutils | | + bison | [] | + bison-runtime | [] [] [] [] [] | + bluez-pin | [] [] [] [] [] | + bombono-dvd | | + buzztard | | + cflow | | + clisp | | + coreutils | [] | + cpio | | + cppi | | + cpplib | | + cryptsetup | | + dfarc | [] | + dialog | [] [] [] [] [] | + dico | | + diffutils | [] [] | + dink | | + doodle | | + e2fsprogs | | + enscript | | + exif | [] | + fetchmail | | + findutils | | + flex | | + freedink | [] | + gas | | + gawk | | + gcal | | + gcc | | + gettext-examples | [] [] [] [] | + gettext-runtime | [] | + gettext-tools | [] | + gip | [] [] | + gjay | | + gliv | | + glunarclock | [] | + gnubiff | | + gnucash | () () () () | + gnuedu | | + gnulib | | + gnunet | | + gnunet-gtk | | + gnutls | [] | + gold | | + gpe-aerial | [] | + gpe-beam | [] | + gpe-bluetooth | [] [] | + gpe-calendar | [] | + gpe-clock | [] [] [] [] [] | + gpe-conf | [] [] | + gpe-contacts | [] [] | + gpe-edit | [] | + gpe-filemanager | [] [] | + gpe-go | [] [] [] | + gpe-login | [] | + gpe-ownerinfo | [] [] | + gpe-package | [] [] | + gpe-sketchbook | [] [] | + gpe-su | [] [] [] [] [] [] | + gpe-taskmanager | [] [] [] [] [] [] | + gpe-timesheet | [] [] | + gpe-today | [] [] [] [] | + gpe-todo | [] [] | + gphoto2 | | + gprof | [] | + gpsdrive | | + gramadoir | | + grep | | + grub | | + gsasl | | + gss | | + gst-plugins-bad | [] [] [] [] | + gst-plugins-base | [] [] | + gst-plugins-good | [] [] | + gst-plugins-ugly | [] [] [] [] [] | + gstreamer | | + gtick | | + gtkam | [] | + gtkorphan | [] [] | + gtkspell | [] [] [] [] [] [] [] | + gutenprint | | + hello | [] [] [] | + help2man | | + hylafax | | + idutils | | + indent | | + iso_15924 | [] [] | + iso_3166 | [] [] () [] [] [] [] [] | + iso_3166_2 | | + iso_4217 | [] [] | + iso_639 | [] [] | + iso_639_3 | [] | + jwhois | [] | + kbd | | + keytouch | [] | + keytouch-editor | [] | + keytouch-keyboa... | [] | + klavaro | [] | + latrine | [] | + ld | | + leafpad | [] [] [] | + libc | [] | + libexif | | + libextractor | | + libgnutls | [] | + libgpewidget | [] [] | + libgpg-error | | + libgphoto2 | | + libgphoto2_port | | + libgsasl | | + libiconv | | + libidn | | + lifelines | | + liferea | | + lilypond | | + linkdr | | + lordsawar | | + lprng | | + lynx | | + m4 | | + mailfromd | | + mailutils | | + make | [] | + man-db | | + man-db-manpages | | + minicom | [] | + mkisofs | | + myserver | | + nano | [] [] | + opcodes | | + parted | | + pies | | + popt | [] [] [] | + psmisc | | + pspp | | + pwdutils | | + radius | | + recode | | + rosegarden | | + rpm | | + rush | | + sarg | | + screem | | + scrollkeeper | [] [] | + sed | | + sharutils | | + shishi | | + skencil | | + solfege | [] | + solfege-manual | | + soundtracker | | + sp | | + sysstat | [] | + tar | [] | + texinfo | [] | + tin | | + unicode-han-tra... | | + unicode-transla... | | + util-linux-ng | | + vice | | + vmm | | + vorbis-tools | | + wastesedge | | + wdiff | | + wget | [] | + wyslij-po | | + xchat | [] [] [] | + xdg-user-dirs | [] [] [] [] [] [] [] [] | + xkeyboard-config | [] [] [] | + +-----------------------------------------------+ + ko ku ky lg lt lv mk ml mn mr ms mt nb nds ne + 20 5 10 1 13 48 4 2 2 4 24 10 20 3 1 + + nl nn or pa pl ps pt pt_BR ro ru rw sk sl sq sr + +---------------------------------------------------+ + a2ps | [] [] [] [] [] [] [] [] | + aegis | [] [] [] | + ant-phone | [] [] | + anubis | [] [] [] | + aspell | [] [] [] [] [] | + bash | [] [] | + bfd | [] | + bibshelf | [] [] | + binutils | [] [] | + bison | [] [] [] | + bison-runtime | [] [] [] [] [] [] [] | + bluez-pin | [] [] [] [] [] [] [] [] | + bombono-dvd | [] () | + buzztard | [] [] | + cflow | [] | + clisp | [] [] | + coreutils | [] [] [] [] [] [] | + cpio | [] [] [] | + cppi | [] | + cpplib | [] | + cryptsetup | [] | + dfarc | [] | + dialog | [] [] [] [] | + dico | [] | + diffutils | [] [] [] [] [] [] | + dink | () | + doodle | [] [] | + e2fsprogs | [] [] | + enscript | [] [] [] [] [] | + exif | [] [] [] () [] | + fetchmail | [] [] [] [] | + findutils | [] [] [] [] [] | + flex | [] [] [] [] [] | + freedink | [] [] | + gas | | + gawk | [] [] [] [] | + gcal | | + gcc | [] | + gettext-examples | [] [] [] [] [] [] [] [] | + gettext-runtime | [] [] [] [] [] [] [] [] [] | + gettext-tools | [] [] [] [] [] [] | + gip | [] [] [] [] [] | + gjay | | + gliv | [] [] [] [] [] [] | + glunarclock | [] [] [] [] [] | + gnubiff | [] () | + gnucash | [] () () () | + gnuedu | [] | + gnulib | [] [] [] [] | + gnunet | | + gnunet-gtk | | + gnutls | [] [] | + gold | | + gpe-aerial | [] [] [] [] [] [] [] | + gpe-beam | [] [] [] [] [] [] [] | + gpe-bluetooth | [] [] | + gpe-calendar | [] [] [] [] | + gpe-clock | [] [] [] [] [] [] [] [] | + gpe-conf | [] [] [] [] [] [] [] | + gpe-contacts | [] [] [] [] [] | + gpe-edit | [] [] [] | + gpe-filemanager | [] [] [] | + gpe-go | [] [] [] [] [] [] [] [] | + gpe-login | [] [] | + gpe-ownerinfo | [] [] [] [] [] [] [] [] | + gpe-package | [] [] | + gpe-sketchbook | [] [] [] [] [] [] [] | + gpe-su | [] [] [] [] [] [] [] [] | + gpe-taskmanager | [] [] [] [] [] [] [] [] | + gpe-timesheet | [] [] [] [] [] [] [] [] | + gpe-today | [] [] [] [] [] [] [] [] | + gpe-todo | [] [] [] [] [] | + gphoto2 | [] [] [] [] [] [] [] [] | + gprof | [] [] [] | + gpsdrive | [] [] | + gramadoir | [] [] | + grep | [] [] [] [] | + grub | [] [] [] | + gsasl | [] [] [] [] | + gss | [] [] [] | + gst-plugins-bad | [] [] [] [] [] [] | + gst-plugins-base | [] [] [] [] [] | + gst-plugins-good | [] [] [] [] [] | + gst-plugins-ugly | [] [] [] [] [] [] | + gstreamer | [] [] [] [] [] | + gtick | [] [] [] | + gtkam | [] [] [] [] [] [] | + gtkorphan | [] | + gtkspell | [] [] [] [] [] [] [] [] [] [] | + gutenprint | [] [] | + hello | [] [] [] [] | + help2man | [] [] | + hylafax | [] | + idutils | [] [] [] [] [] | + indent | [] [] [] [] [] [] [] | + iso_15924 | [] [] [] [] | + iso_3166 | [] [] [] [] [] () [] [] [] [] [] [] [] [] | + iso_3166_2 | [] [] [] | + iso_4217 | [] [] [] [] [] [] [] [] | + iso_639 | [] [] [] [] [] [] [] [] [] | + iso_639_3 | [] [] | + jwhois | [] [] [] [] | + kbd | [] [] [] | + keytouch | [] [] [] | + keytouch-editor | [] [] [] | + keytouch-keyboa... | [] [] [] | + klavaro | [] [] | + latrine | [] [] | + ld | | + leafpad | [] [] [] [] [] [] [] [] [] | + libc | [] [] [] [] | + libexif | [] [] () [] | + libextractor | | + libgnutls | [] [] | + libgpewidget | [] [] [] | + libgpg-error | [] [] | + libgphoto2 | [] [] | + libgphoto2_port | [] [] [] [] [] | + libgsasl | [] [] [] [] [] | + libiconv | [] [] [] [] [] | + libidn | [] [] | + lifelines | [] [] | + liferea | [] [] [] [] [] () () [] | + lilypond | [] | + linkdr | [] [] [] | + lordsawar | | + lprng | [] | + lynx | [] [] [] | + m4 | [] [] [] [] [] | + mailfromd | [] | + mailutils | [] | + make | [] [] [] [] | + man-db | [] [] [] | + man-db-manpages | [] [] [] | + minicom | [] [] [] [] | + mkisofs | [] [] [] | + myserver | | + nano | [] [] [] [] | + opcodes | [] [] | + parted | [] [] [] [] | + pies | [] | + popt | [] [] [] [] | + psmisc | [] [] [] | + pspp | [] [] | + pwdutils | [] | + radius | [] [] [] | + recode | [] [] [] [] [] [] [] [] | + rosegarden | () () | + rpm | [] [] [] | + rush | [] [] | + sarg | | + screem | | + scrollkeeper | [] [] [] [] [] [] [] [] | + sed | [] [] [] [] [] [] [] [] [] | + sharutils | [] [] [] [] | + shishi | [] | + skencil | [] [] | + solfege | [] [] [] [] | + solfege-manual | [] [] [] | + soundtracker | [] | + sp | | + sysstat | [] [] [] [] | + tar | [] [] [] [] | + texinfo | [] [] [] [] | + tin | [] | + unicode-han-tra... | | + unicode-transla... | | + util-linux-ng | [] [] [] [] [] | + vice | [] | + vmm | [] | + vorbis-tools | [] [] | + wastesedge | [] | + wdiff | [] [] | + wget | [] [] [] [] [] [] [] | + wyslij-po | [] [] [] | + xchat | [] [] [] [] [] [] [] [] [] | + xdg-user-dirs | [] [] [] [] [] [] [] [] [] [] [] [] [] [] | + xkeyboard-config | [] [] [] | + +---------------------------------------------------+ + nl nn or pa pl ps pt pt_BR ro ru rw sk sl sq sr + 135 10 4 7 105 1 29 62 47 91 3 54 46 9 37 + + sv sw ta te tg th tr uk vi wa zh_CN zh_HK zh_TW + +---------------------------------------------------+ + a2ps | [] [] [] [] [] | 27 + aegis | [] | 9 + ant-phone | [] [] [] [] | 9 + anubis | [] [] [] [] | 15 + aspell | [] [] [] | 20 + bash | [] [] [] | 12 + bfd | [] | 6 + bibshelf | [] [] [] | 16 + binutils | [] [] | 8 + bison | [] [] | 12 + bison-runtime | [] [] [] [] [] [] | 29 + bluez-pin | [] [] [] [] [] [] [] [] | 37 + bombono-dvd | [] | 4 + buzztard | [] | 7 + cflow | [] [] [] | 9 + clisp | | 10 + coreutils | [] [] [] [] | 22 + cpio | [] [] [] [] [] [] | 13 + cppi | [] [] | 5 + cpplib | [] [] [] [] [] [] | 14 + cryptsetup | [] [] | 7 + dfarc | [] | 9 + dialog | [] [] [] [] [] [] [] | 30 + dico | [] | 2 + diffutils | [] [] [] [] [] [] | 30 + dink | | 4 + doodle | [] [] | 7 + e2fsprogs | [] [] [] | 11 + enscript | [] [] [] [] | 17 + exif | [] [] [] | 16 + fetchmail | [] [] [] | 17 + findutils | [] [] [] [] [] | 20 + flex | [] [] [] [] | 15 + freedink | [] | 10 + gas | [] | 4 + gawk | [] [] [] [] | 18 + gcal | [] [] | 5 + gcc | [] [] [] | 7 + gettext-examples | [] [] [] [] [] [] [] | 34 + gettext-runtime | [] [] [] [] [] [] [] | 29 + gettext-tools | [] [] [] [] [] [] | 22 + gip | [] [] [] [] | 22 + gjay | [] | 3 + gliv | [] [] [] | 14 + glunarclock | [] [] [] [] [] | 19 + gnubiff | [] [] | 4 + gnucash | () [] () [] () | 10 + gnuedu | [] [] | 7 + gnulib | [] [] [] [] | 16 + gnunet | [] | 1 + gnunet-gtk | [] [] [] | 5 + gnutls | [] [] [] | 10 + gold | [] | 4 + gpe-aerial | [] [] [] | 18 + gpe-beam | [] [] [] | 19 + gpe-bluetooth | [] [] [] | 13 + gpe-calendar | [] [] [] [] | 12 + gpe-clock | [] [] [] [] [] | 28 + gpe-conf | [] [] [] [] | 20 + gpe-contacts | [] [] [] | 17 + gpe-edit | [] [] [] | 12 + gpe-filemanager | [] [] [] [] | 16 + gpe-go | [] [] [] [] [] | 25 + gpe-login | [] [] [] | 11 + gpe-ownerinfo | [] [] [] [] [] | 25 + gpe-package | [] [] [] | 13 + gpe-sketchbook | [] [] [] | 20 + gpe-su | [] [] [] [] [] | 30 + gpe-taskmanager | [] [] [] [] [] | 29 + gpe-timesheet | [] [] [] [] [] | 25 + gpe-today | [] [] [] [] [] [] | 30 + gpe-todo | [] [] [] [] | 17 + gphoto2 | [] [] [] [] [] | 24 + gprof | [] [] [] | 15 + gpsdrive | [] [] [] | 11 + gramadoir | [] [] [] | 11 + grep | [] [] [] | 10 + grub | [] [] [] | 14 + gsasl | [] [] [] [] | 14 + gss | [] [] [] | 11 + gst-plugins-bad | [] [] [] [] | 26 + gst-plugins-base | [] [] [] [] [] | 24 + gst-plugins-good | [] [] [] [] | 24 + gst-plugins-ugly | [] [] [] [] [] | 29 + gstreamer | [] [] [] [] | 22 + gtick | [] [] [] | 13 + gtkam | [] [] [] | 20 + gtkorphan | [] [] [] | 14 + gtkspell | [] [] [] [] [] [] [] [] [] | 45 + gutenprint | [] | 10 + hello | [] [] [] [] [] [] | 21 + help2man | [] [] | 7 + hylafax | [] | 5 + idutils | [] [] [] [] | 17 + indent | [] [] [] [] [] [] | 30 + iso_15924 | () [] () [] [] | 16 + iso_3166 | [] [] () [] [] () [] [] [] () | 53 + iso_3166_2 | () [] () [] | 9 + iso_4217 | [] () [] [] () [] [] | 26 + iso_639 | [] [] [] () [] () [] [] [] [] | 38 + iso_639_3 | [] () | 8 + jwhois | [] [] [] [] [] | 16 + kbd | [] [] [] [] [] | 15 + keytouch | [] [] [] | 16 + keytouch-editor | [] [] [] | 14 + keytouch-keyboa... | [] [] [] | 14 + klavaro | [] | 11 + latrine | [] [] [] | 10 + ld | [] [] [] [] | 11 + leafpad | [] [] [] [] [] [] | 33 + libc | [] [] [] [] [] | 21 + libexif | [] () | 7 + libextractor | [] | 1 + libgnutls | [] [] [] | 9 + libgpewidget | [] [] [] | 14 + libgpg-error | [] [] [] | 9 + libgphoto2 | [] [] | 8 + libgphoto2_port | [] [] [] [] | 14 + libgsasl | [] [] [] | 13 + libiconv | [] [] [] [] | 21 + libidn | () [] [] | 11 + lifelines | [] | 4 + liferea | [] [] [] | 21 + lilypond | [] | 7 + linkdr | [] [] [] [] [] | 17 + lordsawar | | 1 + lprng | [] | 3 + lynx | [] [] [] [] | 17 + m4 | [] [] [] [] | 19 + mailfromd | [] [] | 3 + mailutils | [] | 5 + make | [] [] [] [] | 21 + man-db | [] [] [] | 8 + man-db-manpages | | 4 + minicom | [] [] | 16 + mkisofs | [] [] | 9 + myserver | | 0 + nano | [] [] [] [] | 21 + opcodes | [] [] [] | 11 + parted | [] [] [] [] [] | 15 + pies | [] [] | 3 + popt | [] [] [] [] [] [] | 27 + psmisc | [] [] | 11 + pspp | | 4 + pwdutils | [] [] | 6 + radius | [] [] | 9 + recode | [] [] [] [] | 28 + rosegarden | () | 0 + rpm | [] [] [] | 11 + rush | [] [] | 4 + sarg | | 1 + screem | [] | 3 + scrollkeeper | [] [] [] [] [] | 27 + sed | [] [] [] [] [] | 30 + sharutils | [] [] [] [] [] | 22 + shishi | [] | 3 + skencil | [] [] | 7 + solfege | [] [] [] [] | 16 + solfege-manual | [] | 8 + soundtracker | [] [] [] | 9 + sp | [] | 3 + sysstat | [] [] | 15 + tar | [] [] [] [] [] [] | 23 + texinfo | [] [] [] [] [] | 17 + tin | | 4 + unicode-han-tra... | | 0 + unicode-transla... | | 2 + util-linux-ng | [] [] [] [] | 20 + vice | () () | 1 + vmm | [] | 4 + vorbis-tools | [] | 6 + wastesedge | | 2 + wdiff | [] [] | 7 + wget | [] [] [] [] [] | 26 + wyslij-po | [] [] | 8 + xchat | [] [] [] [] [] [] | 36 + xdg-user-dirs | [] [] [] [] [] [] [] [] [] [] | 63 + xkeyboard-config | [] [] [] | 22 + +---------------------------------------------------+ + 85 teams sv sw ta te tg th tr uk vi wa zh_CN zh_HK zh_TW + 178 domains 119 1 3 3 0 10 65 51 155 17 98 7 41 2618 + + Some counters in the preceding matrix are higher than the number of +visible blocks let us expect. This is because a few extra PO files are +used for implementing regional variants of languages, or language +dialects. + + For a PO file in the matrix above to be effective, the package to +which it applies should also have been internationalized and +distributed as such by its maintainer. There might be an observable +lag between the mere existence a PO file and its wide availability in a +distribution. + + If June 2010 seems to be old, you may fetch a more recent copy of +this `ABOUT-NLS' file on most GNU archive sites. The most up-to-date +matrix with full percentage details can be found at +`http://translationproject.org/extra/matrix.html'. + +1.5 Using `gettext' in new packages +=================================== + +If you are writing a freely available program and want to +internationalize it you are welcome to use GNU `gettext' in your +package. Of course you have to respect the GNU Library General Public +License which covers the use of the GNU `gettext' library. This means +in particular that even non-free programs can use `libintl' as a shared +library, whereas only free software can use `libintl' as a static +library or use modified versions of `libintl'. + + Once the sources are changed appropriately and the setup can handle +the use of `gettext' the only thing missing are the translations. The +Free Translation Project is also available for packages which are not +developed inside the GNU project. Therefore the information given above +applies also for every other Free Software Project. Contact +`coordinator@translationproject.org' to make the `.pot' files available +to the translation teams. + diff --git a/AUTHORS b/AUTHORS new file mode 100644 index 0000000..eb41b69 --- /dev/null +++ b/AUTHORS @@ -0,0 +1 @@ +Christian Grothoff diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..94a9ed0 --- /dev/null +++ b/COPYING @@ -0,0 +1,674 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/ChangeLog b/ChangeLog new file mode 100644 index 0000000..5363c5b --- /dev/null +++ b/ChangeLog @@ -0,0 +1,14 @@ +2012-03-07 gettextize + + * m4/gettext.m4: New file, from gettext-0.18.1. + * m4/iconv.m4: New file, from gettext-0.18.1. + * m4/lib-ld.m4: New file, from gettext-0.18.1. + * m4/lib-link.m4: New file, from gettext-0.18.1. + * m4/lib-prefix.m4: New file, from gettext-0.18.1. + * m4/nls.m4: New file, from gettext-0.18.1. + * m4/po.m4: New file, from gettext-0.18.1. + * m4/progtest.m4: New file, from gettext-0.18.1. + * Makefile.am (SUBDIRS): Add po. + * configure.ac (AC_OUTPUT): Add po/Makefile.in. + (AM_GNU_GETTEXT_VERSION): Bump to 0.18.1. + diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..7d1c323 --- /dev/null +++ b/INSTALL @@ -0,0 +1,365 @@ +Installation Instructions +************************* + +Copyright (C) 1994, 1995, 1996, 1999, 2000, 2001, 2002, 2004, 2005, +2006, 2007, 2008, 2009 Free Software Foundation, Inc. + + Copying and distribution of this file, with or without modification, +are permitted in any medium without royalty provided the copyright +notice and this notice are preserved. This file is offered as-is, +without warranty of any kind. + +Basic Installation +================== + + Briefly, the shell commands `./configure; make; make install' should +configure, build, and install this package. The following +more-detailed instructions are generic; see the `README' file for +instructions specific to this package. Some packages provide this +`INSTALL' file but do not implement all of the features documented +below. The lack of an optional feature in a given package is not +necessarily a bug. More recommendations for GNU packages can be found +in *note Makefile Conventions: (standards)Makefile Conventions. + + The `configure' shell script attempts to guess correct values for +various system-dependent variables used during compilation. It uses +those values to create a `Makefile' in each directory of the package. +It may also create one or more `.h' files containing system-dependent +definitions. Finally, it creates a shell script `config.status' that +you can run in the future to recreate the current configuration, and a +file `config.log' containing compiler output (useful mainly for +debugging `configure'). + + It can also use an optional file (typically called `config.cache' +and enabled with `--cache-file=config.cache' or simply `-C') that saves +the results of its tests to speed up reconfiguring. Caching is +disabled by default to prevent problems with accidental use of stale +cache files. + + If you need to do unusual things to compile the package, please try +to figure out how `configure' could check whether to do them, and mail +diffs or instructions to the address given in the `README' so they can +be considered for the next release. If you are using the cache, and at +some point `config.cache' contains results you don't want to keep, you +may remove or edit it. + + The file `configure.ac' (or `configure.in') is used to create +`configure' by a program called `autoconf'. You need `configure.ac' if +you want to change it or regenerate `configure' using a newer version +of `autoconf'. + + The simplest way to compile this package is: + + 1. `cd' to the directory containing the package's source code and type + `./configure' to configure the package for your system. + + Running `configure' might take a while. While running, it prints + some messages telling which features it is checking for. + + 2. Type `make' to compile the package. + + 3. Optionally, type `make check' to run any self-tests that come with + the package, generally using the just-built uninstalled binaries. + + 4. Type `make install' to install the programs and any data files and + documentation. When installing into a prefix owned by root, it is + recommended that the package be configured and built as a regular + user, and only the `make install' phase executed with root + privileges. + + 5. Optionally, type `make installcheck' to repeat any self-tests, but + this time using the binaries in their final installed location. + This target does not install anything. Running this target as a + regular user, particularly if the prior `make install' required + root privileges, verifies that the installation completed + correctly. + + 6. You can remove the program binaries and object files from the + source code directory by typing `make clean'. To also remove the + files that `configure' created (so you can compile the package for + a different kind of computer), type `make distclean'. There is + also a `make maintainer-clean' target, but that is intended mainly + for the package's developers. If you use it, you may have to get + all sorts of other programs in order to regenerate files that came + with the distribution. + + 7. Often, you can also type `make uninstall' to remove the installed + files again. In practice, not all packages have tested that + uninstallation works correctly, even though it is required by the + GNU Coding Standards. + + 8. Some packages, particularly those that use Automake, provide `make + distcheck', which can by used by developers to test that all other + targets like `make install' and `make uninstall' work correctly. + This target is generally not run by end users. + +Compilers and Options +===================== + + Some systems require unusual options for compilation or linking that +the `configure' script does not know about. Run `./configure --help' +for details on some of the pertinent environment variables. + + You can give `configure' initial values for configuration parameters +by setting variables in the command line or in the environment. Here +is an example: + + ./configure CC=c99 CFLAGS=-g LIBS=-lposix + + *Note Defining Variables::, for more details. + +Compiling For Multiple Architectures +==================================== + + You can compile the package for more than one kind of computer at the +same time, by placing the object files for each architecture in their +own directory. To do this, you can use GNU `make'. `cd' to the +directory where you want the object files and executables to go and run +the `configure' script. `configure' automatically checks for the +source code in the directory that `configure' is in and in `..'. This +is known as a "VPATH" build. + + With a non-GNU `make', it is safer to compile the package for one +architecture at a time in the source code directory. After you have +installed the package for one architecture, use `make distclean' before +reconfiguring for another architecture. + + On MacOS X 10.5 and later systems, you can create libraries and +executables that work on multiple system types--known as "fat" or +"universal" binaries--by specifying multiple `-arch' options to the +compiler but only a single `-arch' option to the preprocessor. Like +this: + + ./configure CC="gcc -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CXX="g++ -arch i386 -arch x86_64 -arch ppc -arch ppc64" \ + CPP="gcc -E" CXXCPP="g++ -E" + + This is not guaranteed to produce working output in all cases, you +may have to build one architecture at a time and combine the results +using the `lipo' tool if you have problems. + +Installation Names +================== + + By default, `make install' installs the package's commands under +`/usr/local/bin', include files under `/usr/local/include', etc. You +can specify an installation prefix other than `/usr/local' by giving +`configure' the option `--prefix=PREFIX', where PREFIX must be an +absolute file name. + + You can specify separate installation prefixes for +architecture-specific files and architecture-independent files. If you +pass the option `--exec-prefix=PREFIX' to `configure', the package uses +PREFIX as the prefix for installing programs and libraries. +Documentation and other data files still use the regular prefix. + + In addition, if you use an unusual directory layout you can give +options like `--bindir=DIR' to specify different values for particular +kinds of files. Run `configure --help' for a list of the directories +you can set and what kinds of files go in them. In general, the +default for these options is expressed in terms of `${prefix}', so that +specifying just `--prefix' will affect all of the other directory +specifications that were not explicitly provided. + + The most portable way to affect installation locations is to pass the +correct locations to `configure'; however, many packages provide one or +both of the following shortcuts of passing variable assignments to the +`make install' command line to change installation locations without +having to reconfigure or recompile. + + The first method involves providing an override variable for each +affected directory. For example, `make install +prefix=/alternate/directory' will choose an alternate location for all +directory configuration variables that were expressed in terms of +`${prefix}'. Any directories that were specified during `configure', +but not in terms of `${prefix}', must each be overridden at install +time for the entire installation to be relocated. The approach of +makefile variable overrides for each directory variable is required by +the GNU Coding Standards, and ideally causes no recompilation. +However, some platforms have known limitations with the semantics of +shared libraries that end up requiring recompilation when using this +method, particularly noticeable in packages that use GNU Libtool. + + The second method involves providing the `DESTDIR' variable. For +example, `make install DESTDIR=/alternate/directory' will prepend +`/alternate/directory' before all installation names. The approach of +`DESTDIR' overrides is not required by the GNU Coding Standards, and +does not work on platforms that have drive letters. On the other hand, +it does better at avoiding recompilation issues, and works well even +when some directory options were not specified in terms of `${prefix}' +at `configure' time. + +Optional Features +================= + + If the package supports it, you can cause programs to be installed +with an extra prefix or suffix on their names by giving `configure' the +option `--program-prefix=PREFIX' or `--program-suffix=SUFFIX'. + + Some packages pay attention to `--enable-FEATURE' options to +`configure', where FEATURE indicates an optional part of the package. +They may also pay attention to `--with-PACKAGE' options, where PACKAGE +is something like `gnu-as' or `x' (for the X Window System). The +`README' should mention any `--enable-' and `--with-' options that the +package recognizes. + + For packages that use the X Window System, `configure' can usually +find the X include and library files automatically, but if it doesn't, +you can use the `configure' options `--x-includes=DIR' and +`--x-libraries=DIR' to specify their locations. + + Some packages offer the ability to configure how verbose the +execution of `make' will be. For these packages, running `./configure +--enable-silent-rules' sets the default to minimal output, which can be +overridden with `make V=1'; while running `./configure +--disable-silent-rules' sets the default to verbose, which can be +overridden with `make V=0'. + +Particular systems +================== + + On HP-UX, the default C compiler is not ANSI C compatible. If GNU +CC is not installed, it is recommended to use the following options in +order to use an ANSI C compiler: + + ./configure CC="cc -Ae -D_XOPEN_SOURCE=500" + +and if that doesn't work, install pre-built binaries of GCC for HP-UX. + + On OSF/1 a.k.a. Tru64, some versions of the default C compiler cannot +parse its `' header file. The option `-nodtk' can be used as +a workaround. If GNU CC is not installed, it is therefore recommended +to try + + ./configure CC="cc" + +and if that doesn't work, try + + ./configure CC="cc -nodtk" + + On Solaris, don't put `/usr/ucb' early in your `PATH'. This +directory contains several dysfunctional programs; working variants of +these programs are available in `/usr/bin'. So, if you need `/usr/ucb' +in your `PATH', put it _after_ `/usr/bin'. + + On Haiku, software installed for all users goes in `/boot/common', +not `/usr/local'. It is recommended to use the following options: + + ./configure --prefix=/boot/common + +Specifying the System Type +========================== + + There may be some features `configure' cannot figure out +automatically, but needs to determine by the type of machine the package +will run on. Usually, assuming the package is built to be run on the +_same_ architectures, `configure' can figure that out, but if it prints +a message saying it cannot guess the machine type, give it the +`--build=TYPE' option. TYPE can either be a short name for the system +type, such as `sun4', or a canonical name which has the form: + + CPU-COMPANY-SYSTEM + +where SYSTEM can have one of these forms: + + OS + KERNEL-OS + + See the file `config.sub' for the possible values of each field. If +`config.sub' isn't included in this package, then this package doesn't +need to know the machine type. + + If you are _building_ compiler tools for cross-compiling, you should +use the option `--target=TYPE' to select the type of system they will +produce code for. + + If you want to _use_ a cross compiler, that generates code for a +platform different from the build platform, you should specify the +"host" platform (i.e., that on which the generated programs will +eventually be run) with `--host=TYPE'. + +Sharing Defaults +================ + + If you want to set default values for `configure' scripts to share, +you can create a site shell script called `config.site' that gives +default values for variables like `CC', `cache_file', and `prefix'. +`configure' looks for `PREFIX/share/config.site' if it exists, then +`PREFIX/etc/config.site' if it exists. Or, you can set the +`CONFIG_SITE' environment variable to the location of the site script. +A warning: not all `configure' scripts look for a site script. + +Defining Variables +================== + + Variables not defined in a site shell script can be set in the +environment passed to `configure'. However, some packages may run +configure again during the build, and the customized values of these +variables may be lost. In order to avoid this problem, you should set +them in the `configure' command line, using `VAR=value'. For example: + + ./configure CC=/usr/local2/bin/gcc + +causes the specified `gcc' to be used as the C compiler (unless it is +overridden in the site shell script). + +Unfortunately, this technique does not work for `CONFIG_SHELL' due to +an Autoconf bug. Until the bug is fixed you can use this workaround: + + CONFIG_SHELL=/bin/bash /bin/bash ./configure CONFIG_SHELL=/bin/bash + +`configure' Invocation +====================== + + `configure' recognizes the following options to control how it +operates. + +`--help' +`-h' + Print a summary of all of the options to `configure', and exit. + +`--help=short' +`--help=recursive' + Print a summary of the options unique to this package's + `configure', and exit. The `short' variant lists options used + only in the top level, while the `recursive' variant lists options + also present in any nested packages. + +`--version' +`-V' + Print the version of Autoconf used to generate the `configure' + script, and exit. + +`--cache-file=FILE' + Enable the cache: use and save the results of the tests in FILE, + traditionally `config.cache'. FILE defaults to `/dev/null' to + disable caching. + +`--config-cache' +`-C' + Alias for `--cache-file=config.cache'. + +`--quiet' +`--silent' +`-q' + Do not print messages saying which checks are being made. To + suppress all normal output, redirect it to `/dev/null' (any error + messages will still be shown). + +`--srcdir=DIR' + Look for the package's source code in directory DIR. Usually + `configure' can determine that directory automatically. + +`--prefix=DIR' + Use DIR as the installation prefix. *note Installation Names:: + for more details, including other options available for fine-tuning + the installation locations. + +`--no-create' +`-n' + Run the configure checks, but stop before creating any output + files. + +`configure' also accepts some other, not widely useful, options. Run +`configure --help' for more details. + diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..4741c4d --- /dev/null +++ b/Makefile.am @@ -0,0 +1,9 @@ +# This Makefile.am is in the public domain +## Process this file with automake to produce Makefile.in + +SUBDIRS = src po pkgconfig + +EXTRA_DIST = config.rpath \ + install-sh + +ACLOCAL_AMFLAGS = -I m4 diff --git a/NEWS b/NEWS new file mode 100644 index 0000000..e69de29 diff --git a/README b/README new file mode 100644 index 0000000..b156738 --- /dev/null +++ b/README @@ -0,0 +1 @@ +Experimental code for SecuShare. diff --git a/bootstrap b/bootstrap new file mode 100755 index 0000000..c5d2098 --- /dev/null +++ b/bootstrap @@ -0,0 +1,8 @@ +#!/bin/sh + +libtoolize --automake --copy --force +aclocal -I m4 +autoheader +autoconf +automake --add-missing --copy + diff --git a/config.rpath b/config.rpath new file mode 100755 index 0000000..17298f2 --- /dev/null +++ b/config.rpath @@ -0,0 +1,672 @@ +#! /bin/sh +# Output a system dependent set of variables, describing how to set the +# run time search path of shared libraries in an executable. +# +# Copyright 1996-2010 Free Software Foundation, Inc. +# Taken from GNU libtool, 2001 +# Originally by Gordon Matzigkeit , 1996 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. +# +# The first argument passed to this file is the canonical host specification, +# CPU_TYPE-MANUFACTURER-OPERATING_SYSTEM +# or +# CPU_TYPE-MANUFACTURER-KERNEL-OPERATING_SYSTEM +# The environment variables CC, GCC, LDFLAGS, LD, with_gnu_ld +# should be set by the caller. +# +# The set of defined variables is at the end of this script. + +# Known limitations: +# - On IRIX 6.5 with CC="cc", the run time search patch must not be longer +# than 256 bytes, otherwise the compiler driver will dump core. The only +# known workaround is to choose shorter directory names for the build +# directory and/or the installation directory. + +# All known linkers require a `.a' archive for static linking (except MSVC, +# which needs '.lib'). +libext=a +shrext=.so + +host="$1" +host_cpu=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\1/'` +host_vendor=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\2/'` +host_os=`echo "$host" | sed 's/^\([^-]*\)-\([^-]*\)-\(.*\)$/\3/'` + +# Code taken from libtool.m4's _LT_CC_BASENAME. + +for cc_temp in $CC""; do + case $cc_temp in + compile | *[\\/]compile | ccache | *[\\/]ccache ) ;; + distcc | *[\\/]distcc | purify | *[\\/]purify ) ;; + \-*) ;; + *) break;; + esac +done +cc_basename=`echo "$cc_temp" | sed -e 's%^.*/%%'` + +# Code taken from libtool.m4's _LT_COMPILER_PIC. + +wl= +if test "$GCC" = yes; then + wl='-Wl,' +else + case "$host_os" in + aix*) + wl='-Wl,' + ;; + darwin*) + case $cc_basename in + xlc*) + wl='-Wl,' + ;; + esac + ;; + mingw* | cygwin* | pw32* | os2* | cegcc*) + ;; + hpux9* | hpux10* | hpux11*) + wl='-Wl,' + ;; + irix5* | irix6* | nonstopux*) + wl='-Wl,' + ;; + newsos6) + ;; + linux* | k*bsd*-gnu) + case $cc_basename in + ecc*) + wl='-Wl,' + ;; + icc* | ifort*) + wl='-Wl,' + ;; + lf95*) + wl='-Wl,' + ;; + pgcc | pgf77 | pgf90) + wl='-Wl,' + ;; + ccc*) + wl='-Wl,' + ;; + como) + wl='-lopt=' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + wl='-Wl,' + ;; + esac + ;; + esac + ;; + osf3* | osf4* | osf5*) + wl='-Wl,' + ;; + rdos*) + ;; + solaris*) + wl='-Wl,' + ;; + sunos4*) + wl='-Qoption ld ' + ;; + sysv4 | sysv4.2uw2* | sysv4.3*) + wl='-Wl,' + ;; + sysv4*MP*) + ;; + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + wl='-Wl,' + ;; + unicos*) + wl='-Wl,' + ;; + uts4*) + ;; + esac +fi + +# Code taken from libtool.m4's _LT_LINKER_SHLIBS. + +hardcode_libdir_flag_spec= +hardcode_libdir_separator= +hardcode_direct=no +hardcode_minus_L=no + +case "$host_os" in + cygwin* | mingw* | pw32* | cegcc*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test "$GCC" != yes; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd*) + with_gnu_ld=no + ;; +esac + +ld_shlibs=yes +if test "$with_gnu_ld" = yes; then + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + # Unlike libtool, we use -rpath here, not --rpath, since the documented + # option of GNU ld is called -rpath, not --rpath. + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + case "$host_os" in + aix[3-9]*) + # On AIX/PPC, the GNU linker is very broken + if test "$host_cpu" != ia64; then + ld_shlibs=no + fi + ;; + amigaos*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + # Samuel A. Falvo II reports + # that the semantics of dynamic libraries on AmigaOS, at least up + # to version 4, is to share data among multiple programs linked + # with the same dynamic library. Since this doesn't match the + # behavior of shared libraries on other platforms, we cannot use + # them. + ld_shlibs=no + ;; + beos*) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + cygwin* | mingw* | pw32* | cegcc*) + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + hardcode_libdir_flag_spec='-L$libdir' + if $LD --help 2>&1 | grep 'auto-import' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + interix[3-9]*) + hardcode_direct=no + hardcode_libdir_flag_spec='${wl}-rpath,$libdir' + ;; + gnu* | linux* | k*bsd*-gnu) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + netbsd*) + ;; + solaris*) + if $LD -v 2>&1 | grep 'BFD 2\.8' > /dev/null; then + ld_shlibs=no + elif $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [01].* | *\ 2.[0-9].* | *\ 2.1[0-5].*) + ld_shlibs=no + ;; + *) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-rpath,$libdir`' + else + ld_shlibs=no + fi + ;; + esac + ;; + sunos4*) + hardcode_direct=yes + ;; + *) + if $LD --help 2>&1 | grep ': supported targets:.* elf' > /dev/null; then + : + else + ld_shlibs=no + fi + ;; + esac + if test "$ld_shlibs" = no; then + hardcode_libdir_flag_spec= + fi +else + case "$host_os" in + aix3*) + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + hardcode_minus_L=yes + if test "$GCC" = yes; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + hardcode_direct=unsupported + fi + ;; + aix[4-9]*) + if test "$host_cpu" = ia64; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + else + aix_use_runtimelinking=no + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # need to do runtime linking. + case $host_os in aix4.[23]|aix4.[23].*|aix[5-9]*) + for ld_flag in $LDFLAGS; do + if (test $ld_flag = "-brtl" || test $ld_flag = "-Wl,-brtl"); then + aix_use_runtimelinking=yes + break + fi + done + ;; + esac + fi + hardcode_direct=yes + hardcode_libdir_separator=':' + if test "$GCC" = yes; then + case $host_os in aix4.[012]|aix4.[012].*) + collect2name=`${CC} -print-prog-name=collect2` + if test -f "$collect2name" && \ + strings "$collect2name" | grep resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + hardcode_direct=unsupported + hardcode_minus_L=yes + hardcode_libdir_flag_spec='-L$libdir' + hardcode_libdir_separator= + fi + ;; + esac + fi + # Begin _LT_AC_SYS_LIBPATH_AIX. + echo 'int main () { return 0; }' > conftest.c + ${CC} ${LDFLAGS} conftest.c -o conftest + aix_libpath=`dump -H conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } +}'` + if test -z "$aix_libpath"; then + aix_libpath=`dump -HX64 conftest 2>/dev/null | sed -n -e '/Import File Strings/,/^$/ { /^0/ { s/^0 *\(.*\)$/\1/; p; } +}'` + fi + if test -z "$aix_libpath"; then + aix_libpath="/usr/lib:/lib" + fi + rm -f conftest.c conftest + # End _LT_AC_SYS_LIBPATH_AIX. + if test "$aix_use_runtimelinking" = yes; then + hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" + else + if test "$host_cpu" = ia64; then + hardcode_libdir_flag_spec='${wl}-R $libdir:/usr/lib:/lib' + else + hardcode_libdir_flag_spec='${wl}-blibpath:$libdir:'"$aix_libpath" + fi + fi + ;; + amigaos*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + # see comment about different semantics on the GNU ld section + ld_shlibs=no + ;; + bsdi[45]*) + ;; + cygwin* | mingw* | pw32* | cegcc*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + hardcode_libdir_flag_spec=' ' + libext=lib + ;; + darwin* | rhapsody*) + hardcode_direct=no + if test "$GCC" = yes ; then + : + else + case $cc_basename in + xlc*) + ;; + *) + ld_shlibs=no + ;; + esac + fi + ;; + dgux*) + hardcode_libdir_flag_spec='-L$libdir' + ;; + freebsd1*) + ld_shlibs=no + ;; + freebsd2.2*) + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + ;; + freebsd2*) + hardcode_direct=yes + hardcode_minus_L=yes + ;; + freebsd* | dragonfly*) + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + ;; + hpux9*) + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + ;; + hpux10*) + if test "$with_gnu_ld" = no; then + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + hardcode_direct=yes + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + fi + ;; + hpux11*) + if test "$with_gnu_ld" = no; then + hardcode_libdir_flag_spec='${wl}+b ${wl}$libdir' + hardcode_libdir_separator=: + case $host_cpu in + hppa*64*|ia64*) + hardcode_direct=no + ;; + *) + hardcode_direct=yes + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + hardcode_minus_L=yes + ;; + esac + fi + ;; + irix5* | irix6* | nonstopux*) + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + ;; + netbsd*) + hardcode_libdir_flag_spec='-R$libdir' + hardcode_direct=yes + ;; + newsos6) + hardcode_direct=yes + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + ;; + openbsd*) + if test -f /usr/libexec/ld.so; then + hardcode_direct=yes + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`" || test "$host_os-$host_cpu" = "openbsd2.8-powerpc"; then + hardcode_libdir_flag_spec='${wl}-rpath,$libdir' + else + case "$host_os" in + openbsd[01].* | openbsd2.[0-7] | openbsd2.[0-7].*) + hardcode_libdir_flag_spec='-R$libdir' + ;; + *) + hardcode_libdir_flag_spec='${wl}-rpath,$libdir' + ;; + esac + fi + else + ld_shlibs=no + fi + ;; + os2*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_minus_L=yes + ;; + osf3*) + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + hardcode_libdir_separator=: + ;; + osf4* | osf5*) + if test "$GCC" = yes; then + hardcode_libdir_flag_spec='${wl}-rpath ${wl}$libdir' + else + # Both cc and cxx compiler support -rpath directly + hardcode_libdir_flag_spec='-rpath $libdir' + fi + hardcode_libdir_separator=: + ;; + solaris*) + hardcode_libdir_flag_spec='-R$libdir' + ;; + sunos4*) + hardcode_libdir_flag_spec='-L$libdir' + hardcode_direct=yes + hardcode_minus_L=yes + ;; + sysv4) + case $host_vendor in + sni) + hardcode_direct=yes # is this really true??? + ;; + siemens) + hardcode_direct=no + ;; + motorola) + hardcode_direct=no #Motorola manual says yes, but my tests say they lie + ;; + esac + ;; + sysv4.3*) + ;; + sysv4*MP*) + if test -d /usr/nec; then + ld_shlibs=yes + fi + ;; + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[01].[10]* | unixware7* | sco3.2v5.0.[024]*) + ;; + sysv5* | sco3.2v5* | sco5v6*) + hardcode_libdir_flag_spec='`test -z "$SCOABSPATH" && echo ${wl}-R,$libdir`' + hardcode_libdir_separator=':' + ;; + uts4*) + hardcode_libdir_flag_spec='-L$libdir' + ;; + *) + ld_shlibs=no + ;; + esac +fi + +# Check dynamic linker characteristics +# Code taken from libtool.m4's _LT_SYS_DYNAMIC_LINKER. +# Unlike libtool.m4, here we don't care about _all_ names of the library, but +# only about the one the linker finds when passed -lNAME. This is the last +# element of library_names_spec in libtool.m4, or possibly two of them if the +# linker has special search rules. +library_names_spec= # the last element of library_names_spec in libtool.m4 +libname_spec='lib$name' +case "$host_os" in + aix3*) + library_names_spec='$libname.a' + ;; + aix[4-9]*) + library_names_spec='$libname$shrext' + ;; + amigaos*) + library_names_spec='$libname.a' + ;; + beos*) + library_names_spec='$libname$shrext' + ;; + bsdi[45]*) + library_names_spec='$libname$shrext' + ;; + cygwin* | mingw* | pw32* | cegcc*) + shrext=.dll + library_names_spec='$libname.dll.a $libname.lib' + ;; + darwin* | rhapsody*) + shrext=.dylib + library_names_spec='$libname$shrext' + ;; + dgux*) + library_names_spec='$libname$shrext' + ;; + freebsd1*) + ;; + freebsd* | dragonfly*) + case "$host_os" in + freebsd[123]*) + library_names_spec='$libname$shrext$versuffix' ;; + *) + library_names_spec='$libname$shrext' ;; + esac + ;; + gnu*) + library_names_spec='$libname$shrext' + ;; + hpux9* | hpux10* | hpux11*) + case $host_cpu in + ia64*) + shrext=.so + ;; + hppa*64*) + shrext=.sl + ;; + *) + shrext=.sl + ;; + esac + library_names_spec='$libname$shrext' + ;; + interix[3-9]*) + library_names_spec='$libname$shrext' + ;; + irix5* | irix6* | nonstopux*) + library_names_spec='$libname$shrext' + case "$host_os" in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") libsuff= shlibsuff= ;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") libsuff=32 shlibsuff=N32 ;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") libsuff=64 shlibsuff=64 ;; + *) libsuff= shlibsuff= ;; + esac + ;; + esac + ;; + linux*oldld* | linux*aout* | linux*coff*) + ;; + linux* | k*bsd*-gnu) + library_names_spec='$libname$shrext' + ;; + knetbsd*-gnu) + library_names_spec='$libname$shrext' + ;; + netbsd*) + library_names_spec='$libname$shrext' + ;; + newsos6) + library_names_spec='$libname$shrext' + ;; + nto-qnx*) + library_names_spec='$libname$shrext' + ;; + openbsd*) + library_names_spec='$libname$shrext$versuffix' + ;; + os2*) + libname_spec='$name' + shrext=.dll + library_names_spec='$libname.a' + ;; + osf3* | osf4* | osf5*) + library_names_spec='$libname$shrext' + ;; + rdos*) + ;; + solaris*) + library_names_spec='$libname$shrext' + ;; + sunos4*) + library_names_spec='$libname$shrext$versuffix' + ;; + sysv4 | sysv4.3*) + library_names_spec='$libname$shrext' + ;; + sysv4*MP*) + library_names_spec='$libname$shrext' + ;; + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + library_names_spec='$libname$shrext' + ;; + uts4*) + library_names_spec='$libname$shrext' + ;; +esac + +sed_quote_subst='s/\(["`$\\]\)/\\\1/g' +escaped_wl=`echo "X$wl" | sed -e 's/^X//' -e "$sed_quote_subst"` +shlibext=`echo "$shrext" | sed -e 's,^\.,,'` +escaped_libname_spec=`echo "X$libname_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` +escaped_library_names_spec=`echo "X$library_names_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` +escaped_hardcode_libdir_flag_spec=`echo "X$hardcode_libdir_flag_spec" | sed -e 's/^X//' -e "$sed_quote_subst"` + +LC_ALL=C sed -e 's/^\([a-zA-Z0-9_]*\)=/acl_cv_\1=/' </dev/null 2>&1; +then + CFLAGS="-fno-strict-aliasing $CFLAGS" +fi + + +# Check system type +case "$host_os" in +*darwin* | *rhapsody* | *macosx*) + AC_DEFINE_UNQUOTED(OSX,1,[This is an OS X system]) + CFLAGS="-no-cpp-precomp $CFLAGS" + LDFLAGS="-flat_namespace -undefined suppress $LDFLAGS" + ;; +linux*) + AC_DEFINE_UNQUOTED(LINUX,1,[This is a Linux system]) + ;; +freebsd*) + AC_DEFINE_UNQUOTED(SOMEBSD,1,[This is a BSD system]) + AC_DEFINE_UNQUOTED(FREEBSD,1,[This is a FreeBSD system]) + ;; +openbsd*) + AC_DEFINE_UNQUOTED(SOMEBSD,1,[This is a BSD system]) + AC_DEFINE_UNQUOTED(OPENBSD,1,[This is an OpenBSD system]) + ;; +netbsd*) + AC_DEFINE_UNQUOTED(SOMEBSD,1,[This is a BSD system]) + AC_DEFINE_UNQUOTED(NETBSD,1,[This is a NetBSD system]) + ;; +*solaris*) + AC_DEFINE_UNQUOTED(SOLARIS,1,[This is a Solaris system]) + AC_DEFINE_UNQUOTED(_REENTRANT,1,[Need with solaris or errno doesnt work]) + build_target="solaris" + ;; +*arm-linux*) + AC_DEFINE_UNQUOTED(LINUX,1,[This is a Linux system]) + ;; +*cygwin*) + AC_DEFINE_UNQUOTED(CYGWIN,1,[This is a Cygwin system]) + AC_DEFINE_UNQUOTED(WINDOWS,1,[This is a Windows system]) + AC_CHECK_LIB(intl, gettext) + LDFLAGS="$LDFLAGS -no-undefined" + build_target="cygwin" + ;; +*mingw*) + AC_DEFINE_UNQUOTED(MINGW,1,[This is a MinGW system]) + AC_DEFINE_UNQUOTED(WINDOWS,1,[This is a Windows system]) + AC_DEFINE_UNQUOTED(_WIN32,1,[This is a Windows system]) + AC_CHECK_LIB(intl, gettext) + LDFLAGS="$LDFLAGS -no-undefined -Wl,--export-all-symbols -lws2_32" + CFLAGS="-mms-bitfields $CFLAGS" + build_target="mingw" + ;; +*) + AC_MSG_RESULT(Unrecognised OS $host_os) + AC_DEFINE_UNQUOTED(OTHEROS,1,[Some strange OS]) +;; +esac + +AM_CONDITIONAL(MINGW, test "$build_target" = "mingw") + +# check for gettext +AM_GNU_GETTEXT_VERSION([0.18.1]) +AM_GNU_GETTEXT([external]) + +AC_CHECK_HEADERS([errno.h stdio.h unistd.h locale.h sys/stat.h sys/types.h langinfo.h libintl.h unistd.h stddef.h argz.h sys/socket.h netinet/in.h stdarg.h]) + +# test for GNUnet core +gnunet=0 +lookin=${prefix} +backup_LDFLAGS="$LDFLAGS" +backup_CPPFLAGS="$CPPFLAGS" +GNUNET_LDFLAGS="" +GNUNET_CPPFLAGS="" +AC_MSG_CHECKING(for GNUnet core) +AC_ARG_WITH(gnunet, + [ --with-gnunet=PFX Base of GNUnet installation], + [AC_MSG_RESULT([$with_gnunet]) + case $with_gnunet in + no) + lookin="" + ;; + yes) + lookin="${prefix}" + ;; + *) + lookin="$with_gnunet" + ;; + esac + ], + [ + AC_MSG_RESULT([--with-gnunet not specified]) + PKG_CHECK_MODULES([GNUNET], [gnunetutil >= 0.9.0], gnunet=1) + ] +) + +if test "x$gnunet" == "x0" -a ! "x$lookin" == "x" +then + AC_MSG_CHECKING(for GNUnet util library in $lookin) + GNUNET_LDFLAGS="-L${lookin}/lib" + GNUNET_CPPFLAGS="-I${lookin}/include" + LDFLAGS="$GNUNET_LDFLAGS $backup_LDFLAGS" + CPPFLAGS="$GNUNET_CPPFLAGS $backup_CPPFLAGS" + AC_CHECK_HEADERS([gnunet/gnunet_util_lib.h], + AC_CHECK_LIB([gnunetutil], [GNUNET_xfree_], + [ + gnunet=1 + EXT_LIB_PATH="-L${lookin}/lib $EXT_LIB_PATH" + ] + ),,[#include ] + ) +fi + +if test "x$gnunet" == "x0" +then + AC_MSG_ERROR([gnunet-ext requires GNUnet]) +fi + + + +# Linker hardening options +# Currently these options are ELF specific - you can't use this with MacOSX +AC_ARG_ENABLE(linker-hardening, + AS_HELP_STRING(--enable-linker-hardening, enable linker security fixups), +[if test x$enableval = xyes; then + LDFLAGS="$LDFLAGS -z relro -z now" +fi]) + + +extra_logging=GNUNET_NO +AC_ARG_ENABLE([logging], + AS_HELP_STRING([--enable-logging@<:@=value@:>@],[Enable logging calls. Possible values: yes,no,verbose,veryverbose ('yes' is the default)]), + [AS_IF([test "x$enableval" = "xyes"], [], + [test "x$enableval" = "xno"], [AC_DEFINE([GNUNET_CULL_LOGGING],[],[Define to cull all logging calls])], + [test "x$enableval" = "xverbose"], [extra_logging=GNUNET_YES] + [test "x$enableval" = "xveryverbose"], [extra_logging=\(GNUNET_YES+1\)]) + ], []) +AC_DEFINE_UNQUOTED([GNUNET_EXTRA_LOGGING],[$extra_logging],[1 if extra logging is enabled, 2 for very verbose extra logging, 0 otherwise]) + + +AC_SUBST(GNUNET_CPPFLAGS) +AC_SUBST(GNUNET_LDFLAGS) +LDFLAGS="$backup_LDFLAGS" +CPPFLAGS="$backup_CPPFLAGS" + +AC_DEFINE_DIR([PACKAGE_DATA], [datarootdir], [The directory for installing read-only architecture-independent data]) + +# Set PACKAGE_SOURCE_DIR in gnunet_ext_config.h. +packagesrcdir=`cd $srcdir && pwd` +AC_DEFINE_UNQUOTED(PACKAGE_SOURCE_DIR, "${packagesrcdir}", [source dir]) + +AC_OUTPUT([ po/Makefile.in +Makefile +pkgconfig/Makefile +src/Makefile +src/include/Makefile +src/multicast/Makefile +src/multicast/multicast.conf +src/psycutil/Makefile +src/psyc/Makefile +src/psyc/psyc.conf +src/psycstore/Makefile +src/psycstore/psycstore.conf +src/social/Makefile +src/social/social.conf +pkgconfig/gnunetmulticast.pc +pkgconfig/gnunetpsyc.pc +pkgconfig/gnunetpsycstore.pc +pkgconfig/gnunetsocial.pc +]) diff --git a/m4/ac_define_dir.m4 b/m4/ac_define_dir.m4 new file mode 100644 index 0000000..f7e028f --- /dev/null +++ b/m4/ac_define_dir.m4 @@ -0,0 +1,35 @@ +dnl @synopsis AC_DEFINE_DIR(VARNAME, DIR [, DESCRIPTION]) +dnl +dnl This macro _AC_DEFINEs VARNAME to the expansion of the DIR +dnl variable, taking care of fixing up ${prefix} and such. +dnl +dnl VARNAME is offered as both a C preprocessor symbol, and an output +dnl variable. +dnl +dnl Note that the 3 argument form is only supported with autoconf 2.13 +dnl and later (i.e. only where _AC_DEFINE supports 3 arguments). +dnl +dnl Examples: +dnl +dnl AC_DEFINE_DIR(DATADIR, datadir) +dnl AC_DEFINE_DIR(PROG_PATH, bindir, [Location of installed binaries]) +dnl +dnl @category Misc +dnl @author Stepan Kasal +dnl @author Andreas Schwab +dnl @author Guido Draheim +dnl @author Alexandre Oliva +dnl @version 2005-01-17 +dnl @license AllPermissive + +AC_DEFUN([AC_DEFINE_DIR], [ + prefix_NONE= + exec_prefix_NONE= + test "x$prefix" = xNONE && prefix_NONE=yes && prefix=$ac_default_prefix + test "x$exec_prefix" = xNONE && exec_prefix_NONE=yes && exec_prefix=$prefix + eval ac_define_dir="\"[$]$2\"" + AC_SUBST($1, "$ac_define_dir") + AC_DEFINE_UNQUOTED($1, "$ac_define_dir", [$3]) + test "$prefix_NONE" && prefix=NONE + test "$exec_prefix_NONE" && exec_prefix=NONE +]) diff --git a/m4/gettext.m4 b/m4/gettext.m4 new file mode 100644 index 0000000..f84e6a5 --- /dev/null +++ b/m4/gettext.m4 @@ -0,0 +1,383 @@ +# gettext.m4 serial 63 (gettext-0.18) +dnl Copyright (C) 1995-2010 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. +dnl +dnl This file can can be used in projects which are not available under +dnl the GNU General Public License or the GNU Library General Public +dnl License but which still want to provide support for the GNU gettext +dnl functionality. +dnl Please note that the actual code of the GNU gettext library is covered +dnl by the GNU Library General Public License, and the rest of the GNU +dnl gettext package package is covered by the GNU General Public License. +dnl They are *not* in the public domain. + +dnl Authors: +dnl Ulrich Drepper , 1995-2000. +dnl Bruno Haible , 2000-2006, 2008-2010. + +dnl Macro to add for using GNU gettext. + +dnl Usage: AM_GNU_GETTEXT([INTLSYMBOL], [NEEDSYMBOL], [INTLDIR]). +dnl INTLSYMBOL can be one of 'external', 'no-libtool', 'use-libtool'. The +dnl default (if it is not specified or empty) is 'no-libtool'. +dnl INTLSYMBOL should be 'external' for packages with no intl directory, +dnl and 'no-libtool' or 'use-libtool' for packages with an intl directory. +dnl If INTLSYMBOL is 'use-libtool', then a libtool library +dnl $(top_builddir)/intl/libintl.la will be created (shared and/or static, +dnl depending on --{enable,disable}-{shared,static} and on the presence of +dnl AM-DISABLE-SHARED). If INTLSYMBOL is 'no-libtool', a static library +dnl $(top_builddir)/intl/libintl.a will be created. +dnl If NEEDSYMBOL is specified and is 'need-ngettext', then GNU gettext +dnl implementations (in libc or libintl) without the ngettext() function +dnl will be ignored. If NEEDSYMBOL is specified and is +dnl 'need-formatstring-macros', then GNU gettext implementations that don't +dnl support the ISO C 99 formatstring macros will be ignored. +dnl INTLDIR is used to find the intl libraries. If empty, +dnl the value `$(top_builddir)/intl/' is used. +dnl +dnl The result of the configuration is one of three cases: +dnl 1) GNU gettext, as included in the intl subdirectory, will be compiled +dnl and used. +dnl Catalog format: GNU --> install in $(datadir) +dnl Catalog extension: .mo after installation, .gmo in source tree +dnl 2) GNU gettext has been found in the system's C library. +dnl Catalog format: GNU --> install in $(datadir) +dnl Catalog extension: .mo after installation, .gmo in source tree +dnl 3) No internationalization, always use English msgid. +dnl Catalog format: none +dnl Catalog extension: none +dnl If INTLSYMBOL is 'external', only cases 2 and 3 can occur. +dnl The use of .gmo is historical (it was needed to avoid overwriting the +dnl GNU format catalogs when building on a platform with an X/Open gettext), +dnl but we keep it in order not to force irrelevant filename changes on the +dnl maintainers. +dnl +AC_DEFUN([AM_GNU_GETTEXT], +[ + dnl Argument checking. + ifelse([$1], [], , [ifelse([$1], [external], , [ifelse([$1], [no-libtool], , [ifelse([$1], [use-libtool], , + [errprint([ERROR: invalid first argument to AM_GNU_GETTEXT +])])])])]) + ifelse(ifelse([$1], [], [old])[]ifelse([$1], [no-libtool], [old]), [old], + [AC_DIAGNOSE([obsolete], [Use of AM_GNU_GETTEXT without [external] argument is deprecated.])]) + ifelse([$2], [], , [ifelse([$2], [need-ngettext], , [ifelse([$2], [need-formatstring-macros], , + [errprint([ERROR: invalid second argument to AM_GNU_GETTEXT +])])])]) + define([gt_included_intl], + ifelse([$1], [external], + ifdef([AM_GNU_GETTEXT_][INTL_SUBDIR], [yes], [no]), + [yes])) + define([gt_libtool_suffix_prefix], ifelse([$1], [use-libtool], [l], [])) + gt_NEEDS_INIT + AM_GNU_GETTEXT_NEED([$2]) + + AC_REQUIRE([AM_PO_SUBDIRS])dnl + ifelse(gt_included_intl, yes, [ + AC_REQUIRE([AM_INTL_SUBDIR])dnl + ]) + + dnl Prerequisites of AC_LIB_LINKFLAGS_BODY. + AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) + AC_REQUIRE([AC_LIB_RPATH]) + + dnl Sometimes libintl requires libiconv, so first search for libiconv. + dnl Ideally we would do this search only after the + dnl if test "$USE_NLS" = "yes"; then + dnl if { eval "gt_val=\$$gt_func_gnugettext_libc"; test "$gt_val" != "yes"; }; then + dnl tests. But if configure.in invokes AM_ICONV after AM_GNU_GETTEXT + dnl the configure script would need to contain the same shell code + dnl again, outside any 'if'. There are two solutions: + dnl - Invoke AM_ICONV_LINKFLAGS_BODY here, outside any 'if'. + dnl - Control the expansions in more detail using AC_PROVIDE_IFELSE. + dnl Since AC_PROVIDE_IFELSE is only in autoconf >= 2.52 and not + dnl documented, we avoid it. + ifelse(gt_included_intl, yes, , [ + AC_REQUIRE([AM_ICONV_LINKFLAGS_BODY]) + ]) + + dnl Sometimes, on MacOS X, libintl requires linking with CoreFoundation. + gt_INTL_MACOSX + + dnl Set USE_NLS. + AC_REQUIRE([AM_NLS]) + + ifelse(gt_included_intl, yes, [ + BUILD_INCLUDED_LIBINTL=no + USE_INCLUDED_LIBINTL=no + ]) + LIBINTL= + LTLIBINTL= + POSUB= + + dnl Add a version number to the cache macros. + case " $gt_needs " in + *" need-formatstring-macros "*) gt_api_version=3 ;; + *" need-ngettext "*) gt_api_version=2 ;; + *) gt_api_version=1 ;; + esac + gt_func_gnugettext_libc="gt_cv_func_gnugettext${gt_api_version}_libc" + gt_func_gnugettext_libintl="gt_cv_func_gnugettext${gt_api_version}_libintl" + + dnl If we use NLS figure out what method + if test "$USE_NLS" = "yes"; then + gt_use_preinstalled_gnugettext=no + ifelse(gt_included_intl, yes, [ + AC_MSG_CHECKING([whether included gettext is requested]) + AC_ARG_WITH([included-gettext], + [ --with-included-gettext use the GNU gettext library included here], + nls_cv_force_use_gnu_gettext=$withval, + nls_cv_force_use_gnu_gettext=no) + AC_MSG_RESULT([$nls_cv_force_use_gnu_gettext]) + + nls_cv_use_gnu_gettext="$nls_cv_force_use_gnu_gettext" + if test "$nls_cv_force_use_gnu_gettext" != "yes"; then + ]) + dnl User does not insist on using GNU NLS library. Figure out what + dnl to use. If GNU gettext is available we use this. Else we have + dnl to fall back to GNU NLS library. + + if test $gt_api_version -ge 3; then + gt_revision_test_code=' +#ifndef __GNU_GETTEXT_SUPPORTED_REVISION +#define __GNU_GETTEXT_SUPPORTED_REVISION(major) ((major) == 0 ? 0 : -1) +#endif +changequote(,)dnl +typedef int array [2 * (__GNU_GETTEXT_SUPPORTED_REVISION(0) >= 1) - 1]; +changequote([,])dnl +' + else + gt_revision_test_code= + fi + if test $gt_api_version -ge 2; then + gt_expression_test_code=' + * ngettext ("", "", 0)' + else + gt_expression_test_code= + fi + + AC_CACHE_CHECK([for GNU gettext in libc], [$gt_func_gnugettext_libc], + [AC_TRY_LINK([#include +$gt_revision_test_code +extern int _nl_msg_cat_cntr; +extern int *_nl_domain_bindings;], + [bindtextdomain ("", ""); +return * gettext ("")$gt_expression_test_code + _nl_msg_cat_cntr + *_nl_domain_bindings], + [eval "$gt_func_gnugettext_libc=yes"], + [eval "$gt_func_gnugettext_libc=no"])]) + + if { eval "gt_val=\$$gt_func_gnugettext_libc"; test "$gt_val" != "yes"; }; then + dnl Sometimes libintl requires libiconv, so first search for libiconv. + ifelse(gt_included_intl, yes, , [ + AM_ICONV_LINK + ]) + dnl Search for libintl and define LIBINTL, LTLIBINTL and INCINTL + dnl accordingly. Don't use AC_LIB_LINKFLAGS_BODY([intl],[iconv]) + dnl because that would add "-liconv" to LIBINTL and LTLIBINTL + dnl even if libiconv doesn't exist. + AC_LIB_LINKFLAGS_BODY([intl]) + AC_CACHE_CHECK([for GNU gettext in libintl], + [$gt_func_gnugettext_libintl], + [gt_save_CPPFLAGS="$CPPFLAGS" + CPPFLAGS="$CPPFLAGS $INCINTL" + gt_save_LIBS="$LIBS" + LIBS="$LIBS $LIBINTL" + dnl Now see whether libintl exists and does not depend on libiconv. + AC_TRY_LINK([#include +$gt_revision_test_code +extern int _nl_msg_cat_cntr; +extern +#ifdef __cplusplus +"C" +#endif +const char *_nl_expand_alias (const char *);], + [bindtextdomain ("", ""); +return * gettext ("")$gt_expression_test_code + _nl_msg_cat_cntr + *_nl_expand_alias ("")], + [eval "$gt_func_gnugettext_libintl=yes"], + [eval "$gt_func_gnugettext_libintl=no"]) + dnl Now see whether libintl exists and depends on libiconv. + if { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" != yes; } && test -n "$LIBICONV"; then + LIBS="$LIBS $LIBICONV" + AC_TRY_LINK([#include +$gt_revision_test_code +extern int _nl_msg_cat_cntr; +extern +#ifdef __cplusplus +"C" +#endif +const char *_nl_expand_alias (const char *);], + [bindtextdomain ("", ""); +return * gettext ("")$gt_expression_test_code + _nl_msg_cat_cntr + *_nl_expand_alias ("")], + [LIBINTL="$LIBINTL $LIBICONV" + LTLIBINTL="$LTLIBINTL $LTLIBICONV" + eval "$gt_func_gnugettext_libintl=yes" + ]) + fi + CPPFLAGS="$gt_save_CPPFLAGS" + LIBS="$gt_save_LIBS"]) + fi + + dnl If an already present or preinstalled GNU gettext() is found, + dnl use it. But if this macro is used in GNU gettext, and GNU + dnl gettext is already preinstalled in libintl, we update this + dnl libintl. (Cf. the install rule in intl/Makefile.in.) + if { eval "gt_val=\$$gt_func_gnugettext_libc"; test "$gt_val" = "yes"; } \ + || { { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" = "yes"; } \ + && test "$PACKAGE" != gettext-runtime \ + && test "$PACKAGE" != gettext-tools; }; then + gt_use_preinstalled_gnugettext=yes + else + dnl Reset the values set by searching for libintl. + LIBINTL= + LTLIBINTL= + INCINTL= + fi + + ifelse(gt_included_intl, yes, [ + if test "$gt_use_preinstalled_gnugettext" != "yes"; then + dnl GNU gettext is not found in the C library. + dnl Fall back on included GNU gettext library. + nls_cv_use_gnu_gettext=yes + fi + fi + + if test "$nls_cv_use_gnu_gettext" = "yes"; then + dnl Mark actions used to generate GNU NLS library. + BUILD_INCLUDED_LIBINTL=yes + USE_INCLUDED_LIBINTL=yes + LIBINTL="ifelse([$3],[],\${top_builddir}/intl,[$3])/libintl.[]gt_libtool_suffix_prefix[]a $LIBICONV $LIBTHREAD" + LTLIBINTL="ifelse([$3],[],\${top_builddir}/intl,[$3])/libintl.[]gt_libtool_suffix_prefix[]a $LTLIBICONV $LTLIBTHREAD" + LIBS=`echo " $LIBS " | sed -e 's/ -lintl / /' -e 's/^ //' -e 's/ $//'` + fi + + CATOBJEXT= + if test "$gt_use_preinstalled_gnugettext" = "yes" \ + || test "$nls_cv_use_gnu_gettext" = "yes"; then + dnl Mark actions to use GNU gettext tools. + CATOBJEXT=.gmo + fi + ]) + + if test -n "$INTL_MACOSX_LIBS"; then + if test "$gt_use_preinstalled_gnugettext" = "yes" \ + || test "$nls_cv_use_gnu_gettext" = "yes"; then + dnl Some extra flags are needed during linking. + LIBINTL="$LIBINTL $INTL_MACOSX_LIBS" + LTLIBINTL="$LTLIBINTL $INTL_MACOSX_LIBS" + fi + fi + + if test "$gt_use_preinstalled_gnugettext" = "yes" \ + || test "$nls_cv_use_gnu_gettext" = "yes"; then + AC_DEFINE([ENABLE_NLS], [1], + [Define to 1 if translation of program messages to the user's native language + is requested.]) + else + USE_NLS=no + fi + fi + + AC_MSG_CHECKING([whether to use NLS]) + AC_MSG_RESULT([$USE_NLS]) + if test "$USE_NLS" = "yes"; then + AC_MSG_CHECKING([where the gettext function comes from]) + if test "$gt_use_preinstalled_gnugettext" = "yes"; then + if { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" = "yes"; }; then + gt_source="external libintl" + else + gt_source="libc" + fi + else + gt_source="included intl directory" + fi + AC_MSG_RESULT([$gt_source]) + fi + + if test "$USE_NLS" = "yes"; then + + if test "$gt_use_preinstalled_gnugettext" = "yes"; then + if { eval "gt_val=\$$gt_func_gnugettext_libintl"; test "$gt_val" = "yes"; }; then + AC_MSG_CHECKING([how to link with libintl]) + AC_MSG_RESULT([$LIBINTL]) + AC_LIB_APPENDTOVAR([CPPFLAGS], [$INCINTL]) + fi + + dnl For backward compatibility. Some packages may be using this. + AC_DEFINE([HAVE_GETTEXT], [1], + [Define if the GNU gettext() function is already present or preinstalled.]) + AC_DEFINE([HAVE_DCGETTEXT], [1], + [Define if the GNU dcgettext() function is already present or preinstalled.]) + fi + + dnl We need to process the po/ directory. + POSUB=po + fi + + ifelse(gt_included_intl, yes, [ + dnl If this is used in GNU gettext we have to set BUILD_INCLUDED_LIBINTL + dnl to 'yes' because some of the testsuite requires it. + if test "$PACKAGE" = gettext-runtime || test "$PACKAGE" = gettext-tools; then + BUILD_INCLUDED_LIBINTL=yes + fi + + dnl Make all variables we use known to autoconf. + AC_SUBST([BUILD_INCLUDED_LIBINTL]) + AC_SUBST([USE_INCLUDED_LIBINTL]) + AC_SUBST([CATOBJEXT]) + + dnl For backward compatibility. Some configure.ins may be using this. + nls_cv_header_intl= + nls_cv_header_libgt= + + dnl For backward compatibility. Some Makefiles may be using this. + DATADIRNAME=share + AC_SUBST([DATADIRNAME]) + + dnl For backward compatibility. Some Makefiles may be using this. + INSTOBJEXT=.mo + AC_SUBST([INSTOBJEXT]) + + dnl For backward compatibility. Some Makefiles may be using this. + GENCAT=gencat + AC_SUBST([GENCAT]) + + dnl For backward compatibility. Some Makefiles may be using this. + INTLOBJS= + if test "$USE_INCLUDED_LIBINTL" = yes; then + INTLOBJS="\$(GETTOBJS)" + fi + AC_SUBST([INTLOBJS]) + + dnl Enable libtool support if the surrounding package wishes it. + INTL_LIBTOOL_SUFFIX_PREFIX=gt_libtool_suffix_prefix + AC_SUBST([INTL_LIBTOOL_SUFFIX_PREFIX]) + ]) + + dnl For backward compatibility. Some Makefiles may be using this. + INTLLIBS="$LIBINTL" + AC_SUBST([INTLLIBS]) + + dnl Make all documented variables known to autoconf. + AC_SUBST([LIBINTL]) + AC_SUBST([LTLIBINTL]) + AC_SUBST([POSUB]) +]) + + +dnl gt_NEEDS_INIT ensures that the gt_needs variable is initialized. +m4_define([gt_NEEDS_INIT], +[ + m4_divert_text([DEFAULTS], [gt_needs=]) + m4_define([gt_NEEDS_INIT], []) +]) + + +dnl Usage: AM_GNU_GETTEXT_NEED([NEEDSYMBOL]) +AC_DEFUN([AM_GNU_GETTEXT_NEED], +[ + m4_divert_text([INIT_PREPARE], [gt_needs="$gt_needs $1"]) +]) + + +dnl Usage: AM_GNU_GETTEXT_VERSION([gettext-version]) +AC_DEFUN([AM_GNU_GETTEXT_VERSION], []) diff --git a/m4/iconv.m4 b/m4/iconv.m4 new file mode 100644 index 0000000..e2041b9 --- /dev/null +++ b/m4/iconv.m4 @@ -0,0 +1,214 @@ +# iconv.m4 serial 11 (gettext-0.18.1) +dnl Copyright (C) 2000-2002, 2007-2010 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Bruno Haible. + +AC_DEFUN([AM_ICONV_LINKFLAGS_BODY], +[ + dnl Prerequisites of AC_LIB_LINKFLAGS_BODY. + AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) + AC_REQUIRE([AC_LIB_RPATH]) + + dnl Search for libiconv and define LIBICONV, LTLIBICONV and INCICONV + dnl accordingly. + AC_LIB_LINKFLAGS_BODY([iconv]) +]) + +AC_DEFUN([AM_ICONV_LINK], +[ + dnl Some systems have iconv in libc, some have it in libiconv (OSF/1 and + dnl those with the standalone portable GNU libiconv installed). + AC_REQUIRE([AC_CANONICAL_HOST]) dnl for cross-compiles + + dnl Search for libiconv and define LIBICONV, LTLIBICONV and INCICONV + dnl accordingly. + AC_REQUIRE([AM_ICONV_LINKFLAGS_BODY]) + + dnl Add $INCICONV to CPPFLAGS before performing the following checks, + dnl because if the user has installed libiconv and not disabled its use + dnl via --without-libiconv-prefix, he wants to use it. The first + dnl AC_TRY_LINK will then fail, the second AC_TRY_LINK will succeed. + am_save_CPPFLAGS="$CPPFLAGS" + AC_LIB_APPENDTOVAR([CPPFLAGS], [$INCICONV]) + + AC_CACHE_CHECK([for iconv], [am_cv_func_iconv], [ + am_cv_func_iconv="no, consider installing GNU libiconv" + am_cv_lib_iconv=no + AC_TRY_LINK([#include +#include ], + [iconv_t cd = iconv_open("",""); + iconv(cd,NULL,NULL,NULL,NULL); + iconv_close(cd);], + [am_cv_func_iconv=yes]) + if test "$am_cv_func_iconv" != yes; then + am_save_LIBS="$LIBS" + LIBS="$LIBS $LIBICONV" + AC_TRY_LINK([#include +#include ], + [iconv_t cd = iconv_open("",""); + iconv(cd,NULL,NULL,NULL,NULL); + iconv_close(cd);], + [am_cv_lib_iconv=yes] + [am_cv_func_iconv=yes]) + LIBS="$am_save_LIBS" + fi + ]) + if test "$am_cv_func_iconv" = yes; then + AC_CACHE_CHECK([for working iconv], [am_cv_func_iconv_works], [ + dnl This tests against bugs in AIX 5.1, HP-UX 11.11, Solaris 10. + am_save_LIBS="$LIBS" + if test $am_cv_lib_iconv = yes; then + LIBS="$LIBS $LIBICONV" + fi + AC_TRY_RUN([ +#include +#include +int main () +{ + /* Test against AIX 5.1 bug: Failures are not distinguishable from successful + returns. */ + { + iconv_t cd_utf8_to_88591 = iconv_open ("ISO8859-1", "UTF-8"); + if (cd_utf8_to_88591 != (iconv_t)(-1)) + { + static const char input[] = "\342\202\254"; /* EURO SIGN */ + char buf[10]; + const char *inptr = input; + size_t inbytesleft = strlen (input); + char *outptr = buf; + size_t outbytesleft = sizeof (buf); + size_t res = iconv (cd_utf8_to_88591, + (char **) &inptr, &inbytesleft, + &outptr, &outbytesleft); + if (res == 0) + return 1; + } + } + /* Test against Solaris 10 bug: Failures are not distinguishable from + successful returns. */ + { + iconv_t cd_ascii_to_88591 = iconv_open ("ISO8859-1", "646"); + if (cd_ascii_to_88591 != (iconv_t)(-1)) + { + static const char input[] = "\263"; + char buf[10]; + const char *inptr = input; + size_t inbytesleft = strlen (input); + char *outptr = buf; + size_t outbytesleft = sizeof (buf); + size_t res = iconv (cd_ascii_to_88591, + (char **) &inptr, &inbytesleft, + &outptr, &outbytesleft); + if (res == 0) + return 1; + } + } +#if 0 /* This bug could be worked around by the caller. */ + /* Test against HP-UX 11.11 bug: Positive return value instead of 0. */ + { + iconv_t cd_88591_to_utf8 = iconv_open ("utf8", "iso88591"); + if (cd_88591_to_utf8 != (iconv_t)(-1)) + { + static const char input[] = "\304rger mit b\366sen B\374bchen ohne Augenma\337"; + char buf[50]; + const char *inptr = input; + size_t inbytesleft = strlen (input); + char *outptr = buf; + size_t outbytesleft = sizeof (buf); + size_t res = iconv (cd_88591_to_utf8, + (char **) &inptr, &inbytesleft, + &outptr, &outbytesleft); + if ((int)res > 0) + return 1; + } + } +#endif + /* Test against HP-UX 11.11 bug: No converter from EUC-JP to UTF-8 is + provided. */ + if (/* Try standardized names. */ + iconv_open ("UTF-8", "EUC-JP") == (iconv_t)(-1) + /* Try IRIX, OSF/1 names. */ + && iconv_open ("UTF-8", "eucJP") == (iconv_t)(-1) + /* Try AIX names. */ + && iconv_open ("UTF-8", "IBM-eucJP") == (iconv_t)(-1) + /* Try HP-UX names. */ + && iconv_open ("utf8", "eucJP") == (iconv_t)(-1)) + return 1; + return 0; +}], [am_cv_func_iconv_works=yes], [am_cv_func_iconv_works=no], + [case "$host_os" in + aix* | hpux*) am_cv_func_iconv_works="guessing no" ;; + *) am_cv_func_iconv_works="guessing yes" ;; + esac]) + LIBS="$am_save_LIBS" + ]) + case "$am_cv_func_iconv_works" in + *no) am_func_iconv=no am_cv_lib_iconv=no ;; + *) am_func_iconv=yes ;; + esac + else + am_func_iconv=no am_cv_lib_iconv=no + fi + if test "$am_func_iconv" = yes; then + AC_DEFINE([HAVE_ICONV], [1], + [Define if you have the iconv() function and it works.]) + fi + if test "$am_cv_lib_iconv" = yes; then + AC_MSG_CHECKING([how to link with libiconv]) + AC_MSG_RESULT([$LIBICONV]) + else + dnl If $LIBICONV didn't lead to a usable library, we don't need $INCICONV + dnl either. + CPPFLAGS="$am_save_CPPFLAGS" + LIBICONV= + LTLIBICONV= + fi + AC_SUBST([LIBICONV]) + AC_SUBST([LTLIBICONV]) +]) + +dnl Define AM_ICONV using AC_DEFUN_ONCE for Autoconf >= 2.64, in order to +dnl avoid warnings like +dnl "warning: AC_REQUIRE: `AM_ICONV' was expanded before it was required". +dnl This is tricky because of the way 'aclocal' is implemented: +dnl - It requires defining an auxiliary macro whose name ends in AC_DEFUN. +dnl Otherwise aclocal's initial scan pass would miss the macro definition. +dnl - It requires a line break inside the AC_DEFUN_ONCE and AC_DEFUN expansions. +dnl Otherwise aclocal would emit many "Use of uninitialized value $1" +dnl warnings. +m4_define([gl_iconv_AC_DEFUN], + m4_version_prereq([2.64], + [[AC_DEFUN_ONCE( + [$1], [$2])]], + [[AC_DEFUN( + [$1], [$2])]])) +gl_iconv_AC_DEFUN([AM_ICONV], +[ + AM_ICONV_LINK + if test "$am_cv_func_iconv" = yes; then + AC_MSG_CHECKING([for iconv declaration]) + AC_CACHE_VAL([am_cv_proto_iconv], [ + AC_TRY_COMPILE([ +#include +#include +extern +#ifdef __cplusplus +"C" +#endif +#if defined(__STDC__) || defined(__cplusplus) +size_t iconv (iconv_t cd, char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft); +#else +size_t iconv(); +#endif +], [], [am_cv_proto_iconv_arg1=""], [am_cv_proto_iconv_arg1="const"]) + am_cv_proto_iconv="extern size_t iconv (iconv_t cd, $am_cv_proto_iconv_arg1 char * *inbuf, size_t *inbytesleft, char * *outbuf, size_t *outbytesleft);"]) + am_cv_proto_iconv=`echo "[$]am_cv_proto_iconv" | tr -s ' ' | sed -e 's/( /(/'` + AC_MSG_RESULT([ + $am_cv_proto_iconv]) + AC_DEFINE_UNQUOTED([ICONV_CONST], [$am_cv_proto_iconv_arg1], + [Define as const if the declaration of iconv() needs const.]) + fi +]) diff --git a/m4/lib-ld.m4 b/m4/lib-ld.m4 new file mode 100644 index 0000000..ebb3052 --- /dev/null +++ b/m4/lib-ld.m4 @@ -0,0 +1,110 @@ +# lib-ld.m4 serial 4 (gettext-0.18) +dnl Copyright (C) 1996-2003, 2009-2010 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl Subroutines of libtool.m4, +dnl with replacements s/AC_/AC_LIB/ and s/lt_cv/acl_cv/ to avoid collision +dnl with libtool.m4. + +dnl From libtool-1.4. Sets the variable with_gnu_ld to yes or no. +AC_DEFUN([AC_LIB_PROG_LD_GNU], +[AC_CACHE_CHECK([if the linker ($LD) is GNU ld], [acl_cv_prog_gnu_ld], +[# I'd rather use --version here, but apparently some GNU ld's only accept -v. +case `$LD -v 2>&1 conf$$.sh + echo "exit 0" >>conf$$.sh + chmod +x conf$$.sh + if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then + PATH_SEPARATOR=';' + else + PATH_SEPARATOR=: + fi + rm -f conf$$.sh +fi +ac_prog=ld +if test "$GCC" = yes; then + # Check if gcc -print-prog-name=ld gives a path. + AC_MSG_CHECKING([for ld used by GCC]) + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [[\\/]* | [A-Za-z]:[\\/]*)] + [re_direlt='/[^/][^/]*/\.\./'] + # Canonicalize the path of ld + ac_prog=`echo $ac_prog| sed 's%\\\\%/%g'` + while echo $ac_prog | grep "$re_direlt" > /dev/null 2>&1; do + ac_prog=`echo $ac_prog| sed "s%$re_direlt%/%"` + done + test -z "$LD" && LD="$ac_prog" + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test "$with_gnu_ld" = yes; then + AC_MSG_CHECKING([for GNU ld]) +else + AC_MSG_CHECKING([for non-GNU ld]) +fi +AC_CACHE_VAL([acl_cv_path_LD], +[if test -z "$LD"; then + IFS="${IFS= }"; ac_save_ifs="$IFS"; IFS="${IFS}${PATH_SEPARATOR-:}" + for ac_dir in $PATH; do + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + acl_cv_path_LD="$ac_dir/$ac_prog" + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some GNU ld's only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$acl_cv_path_LD" -v 2>&1 < /dev/null` in + *GNU* | *'with BFD'*) + test "$with_gnu_ld" != no && break ;; + *) + test "$with_gnu_ld" != yes && break ;; + esac + fi + done + IFS="$ac_save_ifs" +else + acl_cv_path_LD="$LD" # Let the user override the test with a path. +fi]) +LD="$acl_cv_path_LD" +if test -n "$LD"; then + AC_MSG_RESULT([$LD]) +else + AC_MSG_RESULT([no]) +fi +test -z "$LD" && AC_MSG_ERROR([no acceptable ld found in \$PATH]) +AC_LIB_PROG_LD_GNU +]) diff --git a/m4/lib-link.m4 b/m4/lib-link.m4 new file mode 100644 index 0000000..c73bd8e --- /dev/null +++ b/m4/lib-link.m4 @@ -0,0 +1,774 @@ +# lib-link.m4 serial 21 (gettext-0.18) +dnl Copyright (C) 2001-2010 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Bruno Haible. + +AC_PREREQ([2.54]) + +dnl AC_LIB_LINKFLAGS(name [, dependencies]) searches for libname and +dnl the libraries corresponding to explicit and implicit dependencies. +dnl Sets and AC_SUBSTs the LIB${NAME} and LTLIB${NAME} variables and +dnl augments the CPPFLAGS variable. +dnl Sets and AC_SUBSTs the LIB${NAME}_PREFIX variable to nonempty if libname +dnl was found in ${LIB${NAME}_PREFIX}/$acl_libdirstem. +AC_DEFUN([AC_LIB_LINKFLAGS], +[ + AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) + AC_REQUIRE([AC_LIB_RPATH]) + pushdef([Name],[translit([$1],[./-], [___])]) + pushdef([NAME],[translit([$1],[abcdefghijklmnopqrstuvwxyz./-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])]) + AC_CACHE_CHECK([how to link with lib[]$1], [ac_cv_lib[]Name[]_libs], [ + AC_LIB_LINKFLAGS_BODY([$1], [$2]) + ac_cv_lib[]Name[]_libs="$LIB[]NAME" + ac_cv_lib[]Name[]_ltlibs="$LTLIB[]NAME" + ac_cv_lib[]Name[]_cppflags="$INC[]NAME" + ac_cv_lib[]Name[]_prefix="$LIB[]NAME[]_PREFIX" + ]) + LIB[]NAME="$ac_cv_lib[]Name[]_libs" + LTLIB[]NAME="$ac_cv_lib[]Name[]_ltlibs" + INC[]NAME="$ac_cv_lib[]Name[]_cppflags" + LIB[]NAME[]_PREFIX="$ac_cv_lib[]Name[]_prefix" + AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME) + AC_SUBST([LIB]NAME) + AC_SUBST([LTLIB]NAME) + AC_SUBST([LIB]NAME[_PREFIX]) + dnl Also set HAVE_LIB[]NAME so that AC_LIB_HAVE_LINKFLAGS can reuse the + dnl results of this search when this library appears as a dependency. + HAVE_LIB[]NAME=yes + popdef([NAME]) + popdef([Name]) +]) + +dnl AC_LIB_HAVE_LINKFLAGS(name, dependencies, includes, testcode, [missing-message]) +dnl searches for libname and the libraries corresponding to explicit and +dnl implicit dependencies, together with the specified include files and +dnl the ability to compile and link the specified testcode. The missing-message +dnl defaults to 'no' and may contain additional hints for the user. +dnl If found, it sets and AC_SUBSTs HAVE_LIB${NAME}=yes and the LIB${NAME} +dnl and LTLIB${NAME} variables and augments the CPPFLAGS variable, and +dnl #defines HAVE_LIB${NAME} to 1. Otherwise, it sets and AC_SUBSTs +dnl HAVE_LIB${NAME}=no and LIB${NAME} and LTLIB${NAME} to empty. +dnl Sets and AC_SUBSTs the LIB${NAME}_PREFIX variable to nonempty if libname +dnl was found in ${LIB${NAME}_PREFIX}/$acl_libdirstem. +AC_DEFUN([AC_LIB_HAVE_LINKFLAGS], +[ + AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) + AC_REQUIRE([AC_LIB_RPATH]) + pushdef([Name],[translit([$1],[./-], [___])]) + pushdef([NAME],[translit([$1],[abcdefghijklmnopqrstuvwxyz./-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])]) + + dnl Search for lib[]Name and define LIB[]NAME, LTLIB[]NAME and INC[]NAME + dnl accordingly. + AC_LIB_LINKFLAGS_BODY([$1], [$2]) + + dnl Add $INC[]NAME to CPPFLAGS before performing the following checks, + dnl because if the user has installed lib[]Name and not disabled its use + dnl via --without-lib[]Name-prefix, he wants to use it. + ac_save_CPPFLAGS="$CPPFLAGS" + AC_LIB_APPENDTOVAR([CPPFLAGS], [$INC]NAME) + + AC_CACHE_CHECK([for lib[]$1], [ac_cv_lib[]Name], [ + ac_save_LIBS="$LIBS" + dnl If $LIB[]NAME contains some -l options, add it to the end of LIBS, + dnl because these -l options might require -L options that are present in + dnl LIBS. -l options benefit only from the -L options listed before it. + dnl Otherwise, add it to the front of LIBS, because it may be a static + dnl library that depends on another static library that is present in LIBS. + dnl Static libraries benefit only from the static libraries listed after + dnl it. + case " $LIB[]NAME" in + *" -l"*) LIBS="$LIBS $LIB[]NAME" ;; + *) LIBS="$LIB[]NAME $LIBS" ;; + esac + AC_TRY_LINK([$3], [$4], + [ac_cv_lib[]Name=yes], + [ac_cv_lib[]Name='m4_if([$5], [], [no], [[$5]])']) + LIBS="$ac_save_LIBS" + ]) + if test "$ac_cv_lib[]Name" = yes; then + HAVE_LIB[]NAME=yes + AC_DEFINE([HAVE_LIB]NAME, 1, [Define if you have the lib][$1 library.]) + AC_MSG_CHECKING([how to link with lib[]$1]) + AC_MSG_RESULT([$LIB[]NAME]) + else + HAVE_LIB[]NAME=no + dnl If $LIB[]NAME didn't lead to a usable library, we don't need + dnl $INC[]NAME either. + CPPFLAGS="$ac_save_CPPFLAGS" + LIB[]NAME= + LTLIB[]NAME= + LIB[]NAME[]_PREFIX= + fi + AC_SUBST([HAVE_LIB]NAME) + AC_SUBST([LIB]NAME) + AC_SUBST([LTLIB]NAME) + AC_SUBST([LIB]NAME[_PREFIX]) + popdef([NAME]) + popdef([Name]) +]) + +dnl Determine the platform dependent parameters needed to use rpath: +dnl acl_libext, +dnl acl_shlibext, +dnl acl_hardcode_libdir_flag_spec, +dnl acl_hardcode_libdir_separator, +dnl acl_hardcode_direct, +dnl acl_hardcode_minus_L. +AC_DEFUN([AC_LIB_RPATH], +[ + dnl Tell automake >= 1.10 to complain if config.rpath is missing. + m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([config.rpath])]) + AC_REQUIRE([AC_PROG_CC]) dnl we use $CC, $GCC, $LDFLAGS + AC_REQUIRE([AC_LIB_PROG_LD]) dnl we use $LD, $with_gnu_ld + AC_REQUIRE([AC_CANONICAL_HOST]) dnl we use $host + AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT]) dnl we use $ac_aux_dir + AC_CACHE_CHECK([for shared library run path origin], [acl_cv_rpath], [ + CC="$CC" GCC="$GCC" LDFLAGS="$LDFLAGS" LD="$LD" with_gnu_ld="$with_gnu_ld" \ + ${CONFIG_SHELL-/bin/sh} "$ac_aux_dir/config.rpath" "$host" > conftest.sh + . ./conftest.sh + rm -f ./conftest.sh + acl_cv_rpath=done + ]) + wl="$acl_cv_wl" + acl_libext="$acl_cv_libext" + acl_shlibext="$acl_cv_shlibext" + acl_libname_spec="$acl_cv_libname_spec" + acl_library_names_spec="$acl_cv_library_names_spec" + acl_hardcode_libdir_flag_spec="$acl_cv_hardcode_libdir_flag_spec" + acl_hardcode_libdir_separator="$acl_cv_hardcode_libdir_separator" + acl_hardcode_direct="$acl_cv_hardcode_direct" + acl_hardcode_minus_L="$acl_cv_hardcode_minus_L" + dnl Determine whether the user wants rpath handling at all. + AC_ARG_ENABLE([rpath], + [ --disable-rpath do not hardcode runtime library paths], + :, enable_rpath=yes) +]) + +dnl AC_LIB_FROMPACKAGE(name, package) +dnl declares that libname comes from the given package. The configure file +dnl will then not have a --with-libname-prefix option but a +dnl --with-package-prefix option. Several libraries can come from the same +dnl package. This declaration must occur before an AC_LIB_LINKFLAGS or similar +dnl macro call that searches for libname. +AC_DEFUN([AC_LIB_FROMPACKAGE], +[ + pushdef([NAME],[translit([$1],[abcdefghijklmnopqrstuvwxyz./-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])]) + define([acl_frompackage_]NAME, [$2]) + popdef([NAME]) + pushdef([PACK],[$2]) + pushdef([PACKUP],[translit(PACK,[abcdefghijklmnopqrstuvwxyz./-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])]) + define([acl_libsinpackage_]PACKUP, + m4_ifdef([acl_libsinpackage_]PACKUP, [acl_libsinpackage_]PACKUP[[, ]],)[lib$1]) + popdef([PACKUP]) + popdef([PACK]) +]) + +dnl AC_LIB_LINKFLAGS_BODY(name [, dependencies]) searches for libname and +dnl the libraries corresponding to explicit and implicit dependencies. +dnl Sets the LIB${NAME}, LTLIB${NAME} and INC${NAME} variables. +dnl Also, sets the LIB${NAME}_PREFIX variable to nonempty if libname was found +dnl in ${LIB${NAME}_PREFIX}/$acl_libdirstem. +AC_DEFUN([AC_LIB_LINKFLAGS_BODY], +[ + AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) + pushdef([NAME],[translit([$1],[abcdefghijklmnopqrstuvwxyz./-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])]) + pushdef([PACK],[m4_ifdef([acl_frompackage_]NAME, [acl_frompackage_]NAME, lib[$1])]) + pushdef([PACKUP],[translit(PACK,[abcdefghijklmnopqrstuvwxyz./-], + [ABCDEFGHIJKLMNOPQRSTUVWXYZ___])]) + pushdef([PACKLIBS],[m4_ifdef([acl_frompackage_]NAME, [acl_libsinpackage_]PACKUP, lib[$1])]) + dnl Autoconf >= 2.61 supports dots in --with options. + pushdef([P_A_C_K],[m4_if(m4_version_compare(m4_defn([m4_PACKAGE_VERSION]),[2.61]),[-1],[translit(PACK,[.],[_])],PACK)]) + dnl By default, look in $includedir and $libdir. + use_additional=yes + AC_LIB_WITH_FINAL_PREFIX([ + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + ]) + AC_ARG_WITH(P_A_C_K[-prefix], +[[ --with-]]P_A_C_K[[-prefix[=DIR] search for ]PACKLIBS[ in DIR/include and DIR/lib + --without-]]P_A_C_K[[-prefix don't search for ]PACKLIBS[ in includedir and libdir]], +[ + if test "X$withval" = "Xno"; then + use_additional=no + else + if test "X$withval" = "X"; then + AC_LIB_WITH_FINAL_PREFIX([ + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + ]) + else + additional_includedir="$withval/include" + additional_libdir="$withval/$acl_libdirstem" + if test "$acl_libdirstem2" != "$acl_libdirstem" \ + && ! test -d "$withval/$acl_libdirstem"; then + additional_libdir="$withval/$acl_libdirstem2" + fi + fi + fi +]) + dnl Search the library and its dependencies in $additional_libdir and + dnl $LDFLAGS. Using breadth-first-seach. + LIB[]NAME= + LTLIB[]NAME= + INC[]NAME= + LIB[]NAME[]_PREFIX= + dnl HAVE_LIB${NAME} is an indicator that LIB${NAME}, LTLIB${NAME} have been + dnl computed. So it has to be reset here. + HAVE_LIB[]NAME= + rpathdirs= + ltrpathdirs= + names_already_handled= + names_next_round='$1 $2' + while test -n "$names_next_round"; do + names_this_round="$names_next_round" + names_next_round= + for name in $names_this_round; do + already_handled= + for n in $names_already_handled; do + if test "$n" = "$name"; then + already_handled=yes + break + fi + done + if test -z "$already_handled"; then + names_already_handled="$names_already_handled $name" + dnl See if it was already located by an earlier AC_LIB_LINKFLAGS + dnl or AC_LIB_HAVE_LINKFLAGS call. + uppername=`echo "$name" | sed -e 'y|abcdefghijklmnopqrstuvwxyz./-|ABCDEFGHIJKLMNOPQRSTUVWXYZ___|'` + eval value=\"\$HAVE_LIB$uppername\" + if test -n "$value"; then + if test "$value" = yes; then + eval value=\"\$LIB$uppername\" + test -z "$value" || LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$value" + eval value=\"\$LTLIB$uppername\" + test -z "$value" || LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$value" + else + dnl An earlier call to AC_LIB_HAVE_LINKFLAGS has determined + dnl that this library doesn't exist. So just drop it. + : + fi + else + dnl Search the library lib$name in $additional_libdir and $LDFLAGS + dnl and the already constructed $LIBNAME/$LTLIBNAME. + found_dir= + found_la= + found_so= + found_a= + eval libname=\"$acl_libname_spec\" # typically: libname=lib$name + if test -n "$acl_shlibext"; then + shrext=".$acl_shlibext" # typically: shrext=.so + else + shrext= + fi + if test $use_additional = yes; then + dir="$additional_libdir" + dnl The same code as in the loop below: + dnl First look for a shared library. + if test -n "$acl_shlibext"; then + if test -f "$dir/$libname$shrext"; then + found_dir="$dir" + found_so="$dir/$libname$shrext" + else + if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then + ver=`(cd "$dir" && \ + for f in "$libname$shrext".*; do echo "$f"; done \ + | sed -e "s,^$libname$shrext\\\\.,," \ + | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ + | sed 1q ) 2>/dev/null` + if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then + found_dir="$dir" + found_so="$dir/$libname$shrext.$ver" + fi + else + eval library_names=\"$acl_library_names_spec\" + for f in $library_names; do + if test -f "$dir/$f"; then + found_dir="$dir" + found_so="$dir/$f" + break + fi + done + fi + fi + fi + dnl Then look for a static library. + if test "X$found_dir" = "X"; then + if test -f "$dir/$libname.$acl_libext"; then + found_dir="$dir" + found_a="$dir/$libname.$acl_libext" + fi + fi + if test "X$found_dir" != "X"; then + if test -f "$dir/$libname.la"; then + found_la="$dir/$libname.la" + fi + fi + fi + if test "X$found_dir" = "X"; then + for x in $LDFLAGS $LTLIB[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + case "$x" in + -L*) + dir=`echo "X$x" | sed -e 's/^X-L//'` + dnl First look for a shared library. + if test -n "$acl_shlibext"; then + if test -f "$dir/$libname$shrext"; then + found_dir="$dir" + found_so="$dir/$libname$shrext" + else + if test "$acl_library_names_spec" = '$libname$shrext$versuffix'; then + ver=`(cd "$dir" && \ + for f in "$libname$shrext".*; do echo "$f"; done \ + | sed -e "s,^$libname$shrext\\\\.,," \ + | sort -t '.' -n -r -k1,1 -k2,2 -k3,3 -k4,4 -k5,5 \ + | sed 1q ) 2>/dev/null` + if test -n "$ver" && test -f "$dir/$libname$shrext.$ver"; then + found_dir="$dir" + found_so="$dir/$libname$shrext.$ver" + fi + else + eval library_names=\"$acl_library_names_spec\" + for f in $library_names; do + if test -f "$dir/$f"; then + found_dir="$dir" + found_so="$dir/$f" + break + fi + done + fi + fi + fi + dnl Then look for a static library. + if test "X$found_dir" = "X"; then + if test -f "$dir/$libname.$acl_libext"; then + found_dir="$dir" + found_a="$dir/$libname.$acl_libext" + fi + fi + if test "X$found_dir" != "X"; then + if test -f "$dir/$libname.la"; then + found_la="$dir/$libname.la" + fi + fi + ;; + esac + if test "X$found_dir" != "X"; then + break + fi + done + fi + if test "X$found_dir" != "X"; then + dnl Found the library. + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$found_dir -l$name" + if test "X$found_so" != "X"; then + dnl Linking with a shared library. We attempt to hardcode its + dnl directory into the executable's runpath, unless it's the + dnl standard /usr/lib. + if test "$enable_rpath" = no \ + || test "X$found_dir" = "X/usr/$acl_libdirstem" \ + || test "X$found_dir" = "X/usr/$acl_libdirstem2"; then + dnl No hardcoding is needed. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" + else + dnl Use an explicit option to hardcode DIR into the resulting + dnl binary. + dnl Potentially add DIR to ltrpathdirs. + dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. + haveit= + for x in $ltrpathdirs; do + if test "X$x" = "X$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + ltrpathdirs="$ltrpathdirs $found_dir" + fi + dnl The hardcoding into $LIBNAME is system dependent. + if test "$acl_hardcode_direct" = yes; then + dnl Using DIR/libNAME.so during linking hardcodes DIR into the + dnl resulting binary. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" + else + if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then + dnl Use an explicit option to hardcode DIR into the resulting + dnl binary. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" + dnl Potentially add DIR to rpathdirs. + dnl The rpathdirs will be appended to $LIBNAME at the end. + haveit= + for x in $rpathdirs; do + if test "X$x" = "X$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + rpathdirs="$rpathdirs $found_dir" + fi + else + dnl Rely on "-L$found_dir". + dnl But don't add it if it's already contained in the LDFLAGS + dnl or the already constructed $LIBNAME + haveit= + for x in $LDFLAGS $LIB[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-L$found_dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir" + fi + if test "$acl_hardcode_minus_L" != no; then + dnl FIXME: Not sure whether we should use + dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" + dnl here. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_so" + else + dnl We cannot use $acl_hardcode_runpath_var and LD_RUN_PATH + dnl here, because this doesn't fit in flags passed to the + dnl compiler. So give up. No hardcoding. This affects only + dnl very old systems. + dnl FIXME: Not sure whether we should use + dnl "-L$found_dir -l$name" or "-L$found_dir $found_so" + dnl here. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" + fi + fi + fi + fi + else + if test "X$found_a" != "X"; then + dnl Linking with a static library. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$found_a" + else + dnl We shouldn't come here, but anyway it's good to have a + dnl fallback. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$found_dir -l$name" + fi + fi + dnl Assume the include files are nearby. + additional_includedir= + case "$found_dir" in + */$acl_libdirstem | */$acl_libdirstem/) + basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem/"'*$,,'` + if test "$name" = '$1'; then + LIB[]NAME[]_PREFIX="$basedir" + fi + additional_includedir="$basedir/include" + ;; + */$acl_libdirstem2 | */$acl_libdirstem2/) + basedir=`echo "X$found_dir" | sed -e 's,^X,,' -e "s,/$acl_libdirstem2/"'*$,,'` + if test "$name" = '$1'; then + LIB[]NAME[]_PREFIX="$basedir" + fi + additional_includedir="$basedir/include" + ;; + esac + if test "X$additional_includedir" != "X"; then + dnl Potentially add $additional_includedir to $INCNAME. + dnl But don't add it + dnl 1. if it's the standard /usr/include, + dnl 2. if it's /usr/local/include and we are using GCC on Linux, + dnl 3. if it's already present in $CPPFLAGS or the already + dnl constructed $INCNAME, + dnl 4. if it doesn't exist as a directory. + if test "X$additional_includedir" != "X/usr/include"; then + haveit= + if test "X$additional_includedir" = "X/usr/local/include"; then + if test -n "$GCC"; then + case $host_os in + linux* | gnu* | k*bsd*-gnu) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + for x in $CPPFLAGS $INC[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-I$additional_includedir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$additional_includedir"; then + dnl Really add $additional_includedir to $INCNAME. + INC[]NAME="${INC[]NAME}${INC[]NAME:+ }-I$additional_includedir" + fi + fi + fi + fi + fi + dnl Look for dependencies. + if test -n "$found_la"; then + dnl Read the .la file. It defines the variables + dnl dlname, library_names, old_library, dependency_libs, current, + dnl age, revision, installed, dlopen, dlpreopen, libdir. + save_libdir="$libdir" + case "$found_la" in + */* | *\\*) . "$found_la" ;; + *) . "./$found_la" ;; + esac + libdir="$save_libdir" + dnl We use only dependency_libs. + for dep in $dependency_libs; do + case "$dep" in + -L*) + additional_libdir=`echo "X$dep" | sed -e 's/^X-L//'` + dnl Potentially add $additional_libdir to $LIBNAME and $LTLIBNAME. + dnl But don't add it + dnl 1. if it's the standard /usr/lib, + dnl 2. if it's /usr/local/lib and we are using GCC on Linux, + dnl 3. if it's already present in $LDFLAGS or the already + dnl constructed $LIBNAME, + dnl 4. if it doesn't exist as a directory. + if test "X$additional_libdir" != "X/usr/$acl_libdirstem" \ + && test "X$additional_libdir" != "X/usr/$acl_libdirstem2"; then + haveit= + if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem" \ + || test "X$additional_libdir" = "X/usr/local/$acl_libdirstem2"; then + if test -n "$GCC"; then + case $host_os in + linux* | gnu* | k*bsd*-gnu) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + haveit= + for x in $LDFLAGS $LIB[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-L$additional_libdir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$additional_libdir"; then + dnl Really add $additional_libdir to $LIBNAME. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-L$additional_libdir" + fi + fi + haveit= + for x in $LDFLAGS $LTLIB[]NAME; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-L$additional_libdir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test -d "$additional_libdir"; then + dnl Really add $additional_libdir to $LTLIBNAME. + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-L$additional_libdir" + fi + fi + fi + fi + ;; + -R*) + dir=`echo "X$dep" | sed -e 's/^X-R//'` + if test "$enable_rpath" != no; then + dnl Potentially add DIR to rpathdirs. + dnl The rpathdirs will be appended to $LIBNAME at the end. + haveit= + for x in $rpathdirs; do + if test "X$x" = "X$dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + rpathdirs="$rpathdirs $dir" + fi + dnl Potentially add DIR to ltrpathdirs. + dnl The ltrpathdirs will be appended to $LTLIBNAME at the end. + haveit= + for x in $ltrpathdirs; do + if test "X$x" = "X$dir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + ltrpathdirs="$ltrpathdirs $dir" + fi + fi + ;; + -l*) + dnl Handle this in the next round. + names_next_round="$names_next_round "`echo "X$dep" | sed -e 's/^X-l//'` + ;; + *.la) + dnl Handle this in the next round. Throw away the .la's + dnl directory; it is already contained in a preceding -L + dnl option. + names_next_round="$names_next_round "`echo "X$dep" | sed -e 's,^X.*/,,' -e 's,^lib,,' -e 's,\.la$,,'` + ;; + *) + dnl Most likely an immediate library name. + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$dep" + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }$dep" + ;; + esac + done + fi + else + dnl Didn't find the library; assume it is in the system directories + dnl known to the linker and runtime loader. (All the system + dnl directories known to the linker should also be known to the + dnl runtime loader, otherwise the system is severely misconfigured.) + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }-l$name" + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-l$name" + fi + fi + fi + done + done + if test "X$rpathdirs" != "X"; then + if test -n "$acl_hardcode_libdir_separator"; then + dnl Weird platform: only the last -rpath option counts, the user must + dnl pass all path elements in one option. We can arrange that for a + dnl single library, but not when more than one $LIBNAMEs are used. + alldirs= + for found_dir in $rpathdirs; do + alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$found_dir" + done + dnl Note: acl_hardcode_libdir_flag_spec uses $libdir and $wl. + acl_save_libdir="$libdir" + libdir="$alldirs" + eval flag=\"$acl_hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" + else + dnl The -rpath options are cumulative. + for found_dir in $rpathdirs; do + acl_save_libdir="$libdir" + libdir="$found_dir" + eval flag=\"$acl_hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + LIB[]NAME="${LIB[]NAME}${LIB[]NAME:+ }$flag" + done + fi + fi + if test "X$ltrpathdirs" != "X"; then + dnl When using libtool, the option that works for both libraries and + dnl executables is -R. The -R options are cumulative. + for found_dir in $ltrpathdirs; do + LTLIB[]NAME="${LTLIB[]NAME}${LTLIB[]NAME:+ }-R$found_dir" + done + fi + popdef([P_A_C_K]) + popdef([PACKLIBS]) + popdef([PACKUP]) + popdef([PACK]) + popdef([NAME]) +]) + +dnl AC_LIB_APPENDTOVAR(VAR, CONTENTS) appends the elements of CONTENTS to VAR, +dnl unless already present in VAR. +dnl Works only for CPPFLAGS, not for LIB* variables because that sometimes +dnl contains two or three consecutive elements that belong together. +AC_DEFUN([AC_LIB_APPENDTOVAR], +[ + for element in [$2]; do + haveit= + for x in $[$1]; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X$element"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + [$1]="${[$1]}${[$1]:+ }$element" + fi + done +]) + +dnl For those cases where a variable contains several -L and -l options +dnl referring to unknown libraries and directories, this macro determines the +dnl necessary additional linker options for the runtime path. +dnl AC_LIB_LINKFLAGS_FROM_LIBS([LDADDVAR], [LIBSVALUE], [USE-LIBTOOL]) +dnl sets LDADDVAR to linker options needed together with LIBSVALUE. +dnl If USE-LIBTOOL evaluates to non-empty, linking with libtool is assumed, +dnl otherwise linking without libtool is assumed. +AC_DEFUN([AC_LIB_LINKFLAGS_FROM_LIBS], +[ + AC_REQUIRE([AC_LIB_RPATH]) + AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) + $1= + if test "$enable_rpath" != no; then + if test -n "$acl_hardcode_libdir_flag_spec" && test "$acl_hardcode_minus_L" = no; then + dnl Use an explicit option to hardcode directories into the resulting + dnl binary. + rpathdirs= + next= + for opt in $2; do + if test -n "$next"; then + dir="$next" + dnl No need to hardcode the standard /usr/lib. + if test "X$dir" != "X/usr/$acl_libdirstem" \ + && test "X$dir" != "X/usr/$acl_libdirstem2"; then + rpathdirs="$rpathdirs $dir" + fi + next= + else + case $opt in + -L) next=yes ;; + -L*) dir=`echo "X$opt" | sed -e 's,^X-L,,'` + dnl No need to hardcode the standard /usr/lib. + if test "X$dir" != "X/usr/$acl_libdirstem" \ + && test "X$dir" != "X/usr/$acl_libdirstem2"; then + rpathdirs="$rpathdirs $dir" + fi + next= ;; + *) next= ;; + esac + fi + done + if test "X$rpathdirs" != "X"; then + if test -n ""$3""; then + dnl libtool is used for linking. Use -R options. + for dir in $rpathdirs; do + $1="${$1}${$1:+ }-R$dir" + done + else + dnl The linker is used for linking directly. + if test -n "$acl_hardcode_libdir_separator"; then + dnl Weird platform: only the last -rpath option counts, the user + dnl must pass all path elements in one option. + alldirs= + for dir in $rpathdirs; do + alldirs="${alldirs}${alldirs:+$acl_hardcode_libdir_separator}$dir" + done + acl_save_libdir="$libdir" + libdir="$alldirs" + eval flag=\"$acl_hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + $1="$flag" + else + dnl The -rpath options are cumulative. + for dir in $rpathdirs; do + acl_save_libdir="$libdir" + libdir="$dir" + eval flag=\"$acl_hardcode_libdir_flag_spec\" + libdir="$acl_save_libdir" + $1="${$1}${$1:+ }$flag" + done + fi + fi + fi + fi + fi + AC_SUBST([$1]) +]) diff --git a/m4/lib-prefix.m4 b/m4/lib-prefix.m4 new file mode 100644 index 0000000..1601cea --- /dev/null +++ b/m4/lib-prefix.m4 @@ -0,0 +1,224 @@ +# lib-prefix.m4 serial 7 (gettext-0.18) +dnl Copyright (C) 2001-2005, 2008-2010 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. + +dnl From Bruno Haible. + +dnl AC_LIB_ARG_WITH is synonymous to AC_ARG_WITH in autoconf-2.13, and +dnl similar to AC_ARG_WITH in autoconf 2.52...2.57 except that is doesn't +dnl require excessive bracketing. +ifdef([AC_HELP_STRING], +[AC_DEFUN([AC_LIB_ARG_WITH], [AC_ARG_WITH([$1],[[$2]],[$3],[$4])])], +[AC_DEFUN([AC_][LIB_ARG_WITH], [AC_ARG_WITH([$1],[$2],[$3],[$4])])]) + +dnl AC_LIB_PREFIX adds to the CPPFLAGS and LDFLAGS the flags that are needed +dnl to access previously installed libraries. The basic assumption is that +dnl a user will want packages to use other packages he previously installed +dnl with the same --prefix option. +dnl This macro is not needed if only AC_LIB_LINKFLAGS is used to locate +dnl libraries, but is otherwise very convenient. +AC_DEFUN([AC_LIB_PREFIX], +[ + AC_BEFORE([$0], [AC_LIB_LINKFLAGS]) + AC_REQUIRE([AC_PROG_CC]) + AC_REQUIRE([AC_CANONICAL_HOST]) + AC_REQUIRE([AC_LIB_PREPARE_MULTILIB]) + AC_REQUIRE([AC_LIB_PREPARE_PREFIX]) + dnl By default, look in $includedir and $libdir. + use_additional=yes + AC_LIB_WITH_FINAL_PREFIX([ + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + ]) + AC_LIB_ARG_WITH([lib-prefix], +[ --with-lib-prefix[=DIR] search for libraries in DIR/include and DIR/lib + --without-lib-prefix don't search for libraries in includedir and libdir], +[ + if test "X$withval" = "Xno"; then + use_additional=no + else + if test "X$withval" = "X"; then + AC_LIB_WITH_FINAL_PREFIX([ + eval additional_includedir=\"$includedir\" + eval additional_libdir=\"$libdir\" + ]) + else + additional_includedir="$withval/include" + additional_libdir="$withval/$acl_libdirstem" + fi + fi +]) + if test $use_additional = yes; then + dnl Potentially add $additional_includedir to $CPPFLAGS. + dnl But don't add it + dnl 1. if it's the standard /usr/include, + dnl 2. if it's already present in $CPPFLAGS, + dnl 3. if it's /usr/local/include and we are using GCC on Linux, + dnl 4. if it doesn't exist as a directory. + if test "X$additional_includedir" != "X/usr/include"; then + haveit= + for x in $CPPFLAGS; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-I$additional_includedir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test "X$additional_includedir" = "X/usr/local/include"; then + if test -n "$GCC"; then + case $host_os in + linux* | gnu* | k*bsd*-gnu) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + if test -d "$additional_includedir"; then + dnl Really add $additional_includedir to $CPPFLAGS. + CPPFLAGS="${CPPFLAGS}${CPPFLAGS:+ }-I$additional_includedir" + fi + fi + fi + fi + dnl Potentially add $additional_libdir to $LDFLAGS. + dnl But don't add it + dnl 1. if it's the standard /usr/lib, + dnl 2. if it's already present in $LDFLAGS, + dnl 3. if it's /usr/local/lib and we are using GCC on Linux, + dnl 4. if it doesn't exist as a directory. + if test "X$additional_libdir" != "X/usr/$acl_libdirstem"; then + haveit= + for x in $LDFLAGS; do + AC_LIB_WITH_FINAL_PREFIX([eval x=\"$x\"]) + if test "X$x" = "X-L$additional_libdir"; then + haveit=yes + break + fi + done + if test -z "$haveit"; then + if test "X$additional_libdir" = "X/usr/local/$acl_libdirstem"; then + if test -n "$GCC"; then + case $host_os in + linux*) haveit=yes;; + esac + fi + fi + if test -z "$haveit"; then + if test -d "$additional_libdir"; then + dnl Really add $additional_libdir to $LDFLAGS. + LDFLAGS="${LDFLAGS}${LDFLAGS:+ }-L$additional_libdir" + fi + fi + fi + fi + fi +]) + +dnl AC_LIB_PREPARE_PREFIX creates variables acl_final_prefix, +dnl acl_final_exec_prefix, containing the values to which $prefix and +dnl $exec_prefix will expand at the end of the configure script. +AC_DEFUN([AC_LIB_PREPARE_PREFIX], +[ + dnl Unfortunately, prefix and exec_prefix get only finally determined + dnl at the end of configure. + if test "X$prefix" = "XNONE"; then + acl_final_prefix="$ac_default_prefix" + else + acl_final_prefix="$prefix" + fi + if test "X$exec_prefix" = "XNONE"; then + acl_final_exec_prefix='${prefix}' + else + acl_final_exec_prefix="$exec_prefix" + fi + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + eval acl_final_exec_prefix=\"$acl_final_exec_prefix\" + prefix="$acl_save_prefix" +]) + +dnl AC_LIB_WITH_FINAL_PREFIX([statement]) evaluates statement, with the +dnl variables prefix and exec_prefix bound to the values they will have +dnl at the end of the configure script. +AC_DEFUN([AC_LIB_WITH_FINAL_PREFIX], +[ + acl_save_prefix="$prefix" + prefix="$acl_final_prefix" + acl_save_exec_prefix="$exec_prefix" + exec_prefix="$acl_final_exec_prefix" + $1 + exec_prefix="$acl_save_exec_prefix" + prefix="$acl_save_prefix" +]) + +dnl AC_LIB_PREPARE_MULTILIB creates +dnl - a variable acl_libdirstem, containing the basename of the libdir, either +dnl "lib" or "lib64" or "lib/64", +dnl - a variable acl_libdirstem2, as a secondary possible value for +dnl acl_libdirstem, either the same as acl_libdirstem or "lib/sparcv9" or +dnl "lib/amd64". +AC_DEFUN([AC_LIB_PREPARE_MULTILIB], +[ + dnl There is no formal standard regarding lib and lib64. + dnl On glibc systems, the current practice is that on a system supporting + dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under + dnl $prefix/lib64 and 32-bit libraries go under $prefix/lib. We determine + dnl the compiler's default mode by looking at the compiler's library search + dnl path. If at least one of its elements ends in /lib64 or points to a + dnl directory whose absolute pathname ends in /lib64, we assume a 64-bit ABI. + dnl Otherwise we use the default, namely "lib". + dnl On Solaris systems, the current practice is that on a system supporting + dnl 32-bit and 64-bit instruction sets or ABIs, 64-bit libraries go under + dnl $prefix/lib/64 (which is a symlink to either $prefix/lib/sparcv9 or + dnl $prefix/lib/amd64) and 32-bit libraries go under $prefix/lib. + AC_REQUIRE([AC_CANONICAL_HOST]) + acl_libdirstem=lib + acl_libdirstem2= + case "$host_os" in + solaris*) + dnl See Solaris 10 Software Developer Collection > Solaris 64-bit Developer's Guide > The Development Environment + dnl . + dnl "Portable Makefiles should refer to any library directories using the 64 symbolic link." + dnl But we want to recognize the sparcv9 or amd64 subdirectory also if the + dnl symlink is missing, so we set acl_libdirstem2 too. + AC_CACHE_CHECK([for 64-bit host], [gl_cv_solaris_64bit], + [AC_EGREP_CPP([sixtyfour bits], [ +#ifdef _LP64 +sixtyfour bits +#endif + ], [gl_cv_solaris_64bit=yes], [gl_cv_solaris_64bit=no]) + ]) + if test $gl_cv_solaris_64bit = yes; then + acl_libdirstem=lib/64 + case "$host_cpu" in + sparc*) acl_libdirstem2=lib/sparcv9 ;; + i*86 | x86_64) acl_libdirstem2=lib/amd64 ;; + esac + fi + ;; + *) + searchpath=`(LC_ALL=C $CC -print-search-dirs) 2>/dev/null | sed -n -e 's,^libraries: ,,p' | sed -e 's,^=,,'` + if test -n "$searchpath"; then + acl_save_IFS="${IFS= }"; IFS=":" + for searchdir in $searchpath; do + if test -d "$searchdir"; then + case "$searchdir" in + */lib64/ | */lib64 ) acl_libdirstem=lib64 ;; + */../ | */.. ) + # Better ignore directories of this form. They are misleading. + ;; + *) searchdir=`cd "$searchdir" && pwd` + case "$searchdir" in + */lib64 ) acl_libdirstem=lib64 ;; + esac ;; + esac + fi + done + IFS="$acl_save_IFS" + fi + ;; + esac + test -n "$acl_libdirstem2" || acl_libdirstem2="$acl_libdirstem" +]) diff --git a/m4/libtool.m4 b/m4/libtool.m4 new file mode 100644 index 0000000..ee80844 --- /dev/null +++ b/m4/libtool.m4 @@ -0,0 +1,8387 @@ +# libtool.m4 - Configure libtool for the host system. -*-Autoconf-*- +# +# Copyright (C) 1996-2001, 2003-2015 Free Software Foundation, Inc. +# Written by Gordon Matzigkeit, 1996 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +m4_define([_LT_COPYING], [dnl +# Copyright (C) 2014 Free Software Foundation, Inc. +# This is free software; see the source for copying conditions. There is NO +# warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + +# GNU Libtool is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation; either version 2 of of the License, or +# (at your option) any later version. +# +# As a special exception to the GNU General Public License, if you +# distribute this file as part of a program or library that is built +# using GNU Libtool, you may include this file under the same +# distribution terms that you use for the rest of that program. +# +# GNU Libtool is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +]) + +# serial 58 LT_INIT + + +# LT_PREREQ(VERSION) +# ------------------ +# Complain and exit if this libtool version is less that VERSION. +m4_defun([LT_PREREQ], +[m4_if(m4_version_compare(m4_defn([LT_PACKAGE_VERSION]), [$1]), -1, + [m4_default([$3], + [m4_fatal([Libtool version $1 or higher is required], + 63)])], + [$2])]) + + +# _LT_CHECK_BUILDDIR +# ------------------ +# Complain if the absolute build directory name contains unusual characters +m4_defun([_LT_CHECK_BUILDDIR], +[case `pwd` in + *\ * | *\ *) + AC_MSG_WARN([Libtool does not cope well with whitespace in `pwd`]) ;; +esac +]) + + +# LT_INIT([OPTIONS]) +# ------------------ +AC_DEFUN([LT_INIT], +[AC_PREREQ([2.62])dnl We use AC_PATH_PROGS_FEATURE_CHECK +AC_REQUIRE([AC_CONFIG_AUX_DIR_DEFAULT])dnl +AC_BEFORE([$0], [LT_LANG])dnl +AC_BEFORE([$0], [LT_OUTPUT])dnl +AC_BEFORE([$0], [LTDL_INIT])dnl +m4_require([_LT_CHECK_BUILDDIR])dnl + +dnl Autoconf doesn't catch unexpanded LT_ macros by default: +m4_pattern_forbid([^_?LT_[A-Z_]+$])dnl +m4_pattern_allow([^(_LT_EOF|LT_DLGLOBAL|LT_DLLAZY_OR_NOW|LT_MULTI_MODULE)$])dnl +dnl aclocal doesn't pull ltoptions.m4, ltsugar.m4, or ltversion.m4 +dnl unless we require an AC_DEFUNed macro: +AC_REQUIRE([LTOPTIONS_VERSION])dnl +AC_REQUIRE([LTSUGAR_VERSION])dnl +AC_REQUIRE([LTVERSION_VERSION])dnl +AC_REQUIRE([LTOBSOLETE_VERSION])dnl +m4_require([_LT_PROG_LTMAIN])dnl + +_LT_SHELL_INIT([SHELL=${CONFIG_SHELL-/bin/sh}]) + +dnl Parse OPTIONS +_LT_SET_OPTIONS([$0], [$1]) + +# This can be used to rebuild libtool when needed +LIBTOOL_DEPS=$ltmain + +# Always use our own libtool. +LIBTOOL='$(SHELL) $(top_builddir)/libtool' +AC_SUBST(LIBTOOL)dnl + +_LT_SETUP + +# Only expand once: +m4_define([LT_INIT]) +])# LT_INIT + +# Old names: +AU_ALIAS([AC_PROG_LIBTOOL], [LT_INIT]) +AU_ALIAS([AM_PROG_LIBTOOL], [LT_INIT]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_PROG_LIBTOOL], []) +dnl AC_DEFUN([AM_PROG_LIBTOOL], []) + + +# _LT_PREPARE_CC_BASENAME +# ----------------------- +m4_defun([_LT_PREPARE_CC_BASENAME], [ +# Calculate cc_basename. Skip known compiler wrappers and cross-prefix. +func_cc_basename () +{ + for cc_temp in @S|@*""; do + case $cc_temp in + compile | *[[\\/]]compile | ccache | *[[\\/]]ccache ) ;; + distcc | *[[\\/]]distcc | purify | *[[\\/]]purify ) ;; + \-*) ;; + *) break;; + esac + done + func_cc_basename_result=`$ECHO "$cc_temp" | $SED "s%.*/%%; s%^$host_alias-%%"` +} +])# _LT_PREPARE_CC_BASENAME + + +# _LT_CC_BASENAME(CC) +# ------------------- +# It would be clearer to call AC_REQUIREs from _LT_PREPARE_CC_BASENAME, +# but that macro is also expanded into generated libtool script, which +# arranges for $SED and $ECHO to be set by different means. +m4_defun([_LT_CC_BASENAME], +[m4_require([_LT_PREPARE_CC_BASENAME])dnl +AC_REQUIRE([_LT_DECL_SED])dnl +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl +func_cc_basename $1 +cc_basename=$func_cc_basename_result +]) + + +# _LT_FILEUTILS_DEFAULTS +# ---------------------- +# It is okay to use these file commands and assume they have been set +# sensibly after 'm4_require([_LT_FILEUTILS_DEFAULTS])'. +m4_defun([_LT_FILEUTILS_DEFAULTS], +[: ${CP="cp -f"} +: ${MV="mv -f"} +: ${RM="rm -f"} +])# _LT_FILEUTILS_DEFAULTS + + +# _LT_SETUP +# --------- +m4_defun([_LT_SETUP], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +AC_REQUIRE([_LT_PREPARE_SED_QUOTE_VARS])dnl +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH])dnl + +_LT_DECL([], [PATH_SEPARATOR], [1], [The PATH separator for the build system])dnl +dnl +_LT_DECL([], [host_alias], [0], [The host system])dnl +_LT_DECL([], [host], [0])dnl +_LT_DECL([], [host_os], [0])dnl +dnl +_LT_DECL([], [build_alias], [0], [The build system])dnl +_LT_DECL([], [build], [0])dnl +_LT_DECL([], [build_os], [0])dnl +dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([LT_PATH_LD])dnl +AC_REQUIRE([LT_PATH_NM])dnl +dnl +AC_REQUIRE([AC_PROG_LN_S])dnl +test -z "$LN_S" && LN_S="ln -s" +_LT_DECL([], [LN_S], [1], [Whether we need soft or hard links])dnl +dnl +AC_REQUIRE([LT_CMD_MAX_LEN])dnl +_LT_DECL([objext], [ac_objext], [0], [Object file suffix (normally "o")])dnl +_LT_DECL([], [exeext], [0], [Executable file suffix (normally "")])dnl +dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_CHECK_SHELL_FEATURES])dnl +m4_require([_LT_PATH_CONVERSION_FUNCTIONS])dnl +m4_require([_LT_CMD_RELOAD])dnl +m4_require([_LT_CHECK_MAGIC_METHOD])dnl +m4_require([_LT_CHECK_SHAREDLIB_FROM_LINKLIB])dnl +m4_require([_LT_CMD_OLD_ARCHIVE])dnl +m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl +m4_require([_LT_WITH_SYSROOT])dnl +m4_require([_LT_CMD_TRUNCATE])dnl + +_LT_CONFIG_LIBTOOL_INIT([ +# See if we are running on zsh, and set the options that allow our +# commands through without removal of \ escapes INIT. +if test -n "\${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi +]) +if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST +fi + +_LT_CHECK_OBJDIR + +m4_require([_LT_TAG_COMPILER])dnl + +case $host_os in +aix3*) + # AIX sometimes has problems with the GCC collect2 program. For some + # reason, if we set the COLLECT_NAMES environment variable, the problems + # vanish in a puff of smoke. + if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES + fi + ;; +esac + +# Global variables: +ofile=libtool +can_build_shared=yes + +# All known linkers require a '.a' archive for static linking (except MSVC, +# which needs '.lib'). +libext=a + +with_gnu_ld=$lt_cv_prog_gnu_ld + +old_CC=$CC +old_CFLAGS=$CFLAGS + +# Set sane defaults for various variables +test -z "$CC" && CC=cc +test -z "$LTCC" && LTCC=$CC +test -z "$LTCFLAGS" && LTCFLAGS=$CFLAGS +test -z "$LD" && LD=ld +test -z "$ac_objext" && ac_objext=o + +_LT_CC_BASENAME([$compiler]) + +# Only perform the check for file, if the check method requires it +test -z "$MAGIC_CMD" && MAGIC_CMD=file +case $deplibs_check_method in +file_magic*) + if test "$file_magic_cmd" = '$MAGIC_CMD'; then + _LT_PATH_MAGIC + fi + ;; +esac + +# Use C for the default configuration in the libtool script +LT_SUPPORTED_TAG([CC]) +_LT_LANG_C_CONFIG +_LT_LANG_DEFAULT_CONFIG +_LT_CONFIG_COMMANDS +])# _LT_SETUP + + +# _LT_PREPARE_SED_QUOTE_VARS +# -------------------------- +# Define a few sed substitution that help us do robust quoting. +m4_defun([_LT_PREPARE_SED_QUOTE_VARS], +[# Backslashify metacharacters that are still active within +# double-quoted strings. +sed_quote_subst='s/\([["`$\\]]\)/\\\1/g' + +# Same as above, but do not quote variable references. +double_quote_subst='s/\([["`\\]]\)/\\\1/g' + +# Sed substitution to delay expansion of an escaped shell variable in a +# double_quote_subst'ed string. +delay_variable_subst='s/\\\\\\\\\\\$/\\\\\\$/g' + +# Sed substitution to delay expansion of an escaped single quote. +delay_single_quote_subst='s/'\''/'\'\\\\\\\'\''/g' + +# Sed substitution to avoid accidental globbing in evaled expressions +no_glob_subst='s/\*/\\\*/g' +]) + +# _LT_PROG_LTMAIN +# --------------- +# Note that this code is called both from 'configure', and 'config.status' +# now that we use AC_CONFIG_COMMANDS to generate libtool. Notably, +# 'config.status' has no value for ac_aux_dir unless we are using Automake, +# so we pass a copy along to make sure it has a sensible value anyway. +m4_defun([_LT_PROG_LTMAIN], +[m4_ifdef([AC_REQUIRE_AUX_FILE], [AC_REQUIRE_AUX_FILE([ltmain.sh])])dnl +_LT_CONFIG_LIBTOOL_INIT([ac_aux_dir='$ac_aux_dir']) +ltmain=$ac_aux_dir/ltmain.sh +])# _LT_PROG_LTMAIN + + +## ------------------------------------- ## +## Accumulate code for creating libtool. ## +## ------------------------------------- ## + +# So that we can recreate a full libtool script including additional +# tags, we accumulate the chunks of code to send to AC_CONFIG_COMMANDS +# in macros and then make a single call at the end using the 'libtool' +# label. + + +# _LT_CONFIG_LIBTOOL_INIT([INIT-COMMANDS]) +# ---------------------------------------- +# Register INIT-COMMANDS to be passed to AC_CONFIG_COMMANDS later. +m4_define([_LT_CONFIG_LIBTOOL_INIT], +[m4_ifval([$1], + [m4_append([_LT_OUTPUT_LIBTOOL_INIT], + [$1 +])])]) + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_INIT]) + + +# _LT_CONFIG_LIBTOOL([COMMANDS]) +# ------------------------------ +# Register COMMANDS to be passed to AC_CONFIG_COMMANDS later. +m4_define([_LT_CONFIG_LIBTOOL], +[m4_ifval([$1], + [m4_append([_LT_OUTPUT_LIBTOOL_COMMANDS], + [$1 +])])]) + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS]) + + +# _LT_CONFIG_SAVE_COMMANDS([COMMANDS], [INIT_COMMANDS]) +# ----------------------------------------------------- +m4_defun([_LT_CONFIG_SAVE_COMMANDS], +[_LT_CONFIG_LIBTOOL([$1]) +_LT_CONFIG_LIBTOOL_INIT([$2]) +]) + + +# _LT_FORMAT_COMMENT([COMMENT]) +# ----------------------------- +# Add leading comment marks to the start of each line, and a trailing +# full-stop to the whole comment if one is not present already. +m4_define([_LT_FORMAT_COMMENT], +[m4_ifval([$1], [ +m4_bpatsubst([m4_bpatsubst([$1], [^ *], [# ])], + [['`$\]], [\\\&])]m4_bmatch([$1], [[!?.]$], [], [.]) +)]) + + + +## ------------------------ ## +## FIXME: Eliminate VARNAME ## +## ------------------------ ## + + +# _LT_DECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION], [IS-TAGGED?]) +# ------------------------------------------------------------------- +# CONFIGNAME is the name given to the value in the libtool script. +# VARNAME is the (base) name used in the configure script. +# VALUE may be 0, 1 or 2 for a computed quote escaped value based on +# VARNAME. Any other value will be used directly. +m4_define([_LT_DECL], +[lt_if_append_uniq([lt_decl_varnames], [$2], [, ], + [lt_dict_add_subkey([lt_decl_dict], [$2], [libtool_name], + [m4_ifval([$1], [$1], [$2])]) + lt_dict_add_subkey([lt_decl_dict], [$2], [value], [$3]) + m4_ifval([$4], + [lt_dict_add_subkey([lt_decl_dict], [$2], [description], [$4])]) + lt_dict_add_subkey([lt_decl_dict], [$2], + [tagged?], [m4_ifval([$5], [yes], [no])])]) +]) + + +# _LT_TAGDECL([CONFIGNAME], VARNAME, VALUE, [DESCRIPTION]) +# -------------------------------------------------------- +m4_define([_LT_TAGDECL], [_LT_DECL([$1], [$2], [$3], [$4], [yes])]) + + +# lt_decl_tag_varnames([SEPARATOR], [VARNAME1...]) +# ------------------------------------------------ +m4_define([lt_decl_tag_varnames], +[_lt_decl_filter([tagged?], [yes], $@)]) + + +# _lt_decl_filter(SUBKEY, VALUE, [SEPARATOR], [VARNAME1..]) +# --------------------------------------------------------- +m4_define([_lt_decl_filter], +[m4_case([$#], + [0], [m4_fatal([$0: too few arguments: $#])], + [1], [m4_fatal([$0: too few arguments: $#: $1])], + [2], [lt_dict_filter([lt_decl_dict], [$1], [$2], [], lt_decl_varnames)], + [3], [lt_dict_filter([lt_decl_dict], [$1], [$2], [$3], lt_decl_varnames)], + [lt_dict_filter([lt_decl_dict], $@)])[]dnl +]) + + +# lt_decl_quote_varnames([SEPARATOR], [VARNAME1...]) +# -------------------------------------------------- +m4_define([lt_decl_quote_varnames], +[_lt_decl_filter([value], [1], $@)]) + + +# lt_decl_dquote_varnames([SEPARATOR], [VARNAME1...]) +# --------------------------------------------------- +m4_define([lt_decl_dquote_varnames], +[_lt_decl_filter([value], [2], $@)]) + + +# lt_decl_varnames_tagged([SEPARATOR], [VARNAME1...]) +# --------------------------------------------------- +m4_define([lt_decl_varnames_tagged], +[m4_assert([$# <= 2])dnl +_$0(m4_quote(m4_default([$1], [[, ]])), + m4_ifval([$2], [[$2]], [m4_dquote(lt_decl_tag_varnames)]), + m4_split(m4_normalize(m4_quote(_LT_TAGS)), [ ]))]) +m4_define([_lt_decl_varnames_tagged], +[m4_ifval([$3], [lt_combine([$1], [$2], [_], $3)])]) + + +# lt_decl_all_varnames([SEPARATOR], [VARNAME1...]) +# ------------------------------------------------ +m4_define([lt_decl_all_varnames], +[_$0(m4_quote(m4_default([$1], [[, ]])), + m4_if([$2], [], + m4_quote(lt_decl_varnames), + m4_quote(m4_shift($@))))[]dnl +]) +m4_define([_lt_decl_all_varnames], +[lt_join($@, lt_decl_varnames_tagged([$1], + lt_decl_tag_varnames([[, ]], m4_shift($@))))dnl +]) + + +# _LT_CONFIG_STATUS_DECLARE([VARNAME]) +# ------------------------------------ +# Quote a variable value, and forward it to 'config.status' so that its +# declaration there will have the same value as in 'configure'. VARNAME +# must have a single quote delimited value for this to work. +m4_define([_LT_CONFIG_STATUS_DECLARE], +[$1='`$ECHO "$][$1" | $SED "$delay_single_quote_subst"`']) + + +# _LT_CONFIG_STATUS_DECLARATIONS +# ------------------------------ +# We delimit libtool config variables with single quotes, so when +# we write them to config.status, we have to be sure to quote all +# embedded single quotes properly. In configure, this macro expands +# each variable declared with _LT_DECL (and _LT_TAGDECL) into: +# +# ='`$ECHO "$" | $SED "$delay_single_quote_subst"`' +m4_defun([_LT_CONFIG_STATUS_DECLARATIONS], +[m4_foreach([_lt_var], m4_quote(lt_decl_all_varnames), + [m4_n([_LT_CONFIG_STATUS_DECLARE(_lt_var)])])]) + + +# _LT_LIBTOOL_TAGS +# ---------------- +# Output comment and list of tags supported by the script +m4_defun([_LT_LIBTOOL_TAGS], +[_LT_FORMAT_COMMENT([The names of the tagged configurations supported by this script])dnl +available_tags='_LT_TAGS'dnl +]) + + +# _LT_LIBTOOL_DECLARE(VARNAME, [TAG]) +# ----------------------------------- +# Extract the dictionary values for VARNAME (optionally with TAG) and +# expand to a commented shell variable setting: +# +# # Some comment about what VAR is for. +# visible_name=$lt_internal_name +m4_define([_LT_LIBTOOL_DECLARE], +[_LT_FORMAT_COMMENT(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], + [description])))[]dnl +m4_pushdef([_libtool_name], + m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [libtool_name])))[]dnl +m4_case(m4_quote(lt_dict_fetch([lt_decl_dict], [$1], [value])), + [0], [_libtool_name=[$]$1], + [1], [_libtool_name=$lt_[]$1], + [2], [_libtool_name=$lt_[]$1], + [_libtool_name=lt_dict_fetch([lt_decl_dict], [$1], [value])])[]dnl +m4_ifval([$2], [_$2])[]m4_popdef([_libtool_name])[]dnl +]) + + +# _LT_LIBTOOL_CONFIG_VARS +# ----------------------- +# Produce commented declarations of non-tagged libtool config variables +# suitable for insertion in the LIBTOOL CONFIG section of the 'libtool' +# script. Tagged libtool config variables (even for the LIBTOOL CONFIG +# section) are produced by _LT_LIBTOOL_TAG_VARS. +m4_defun([_LT_LIBTOOL_CONFIG_VARS], +[m4_foreach([_lt_var], + m4_quote(_lt_decl_filter([tagged?], [no], [], lt_decl_varnames)), + [m4_n([_LT_LIBTOOL_DECLARE(_lt_var)])])]) + + +# _LT_LIBTOOL_TAG_VARS(TAG) +# ------------------------- +m4_define([_LT_LIBTOOL_TAG_VARS], +[m4_foreach([_lt_var], m4_quote(lt_decl_tag_varnames), + [m4_n([_LT_LIBTOOL_DECLARE(_lt_var, [$1])])])]) + + +# _LT_TAGVAR(VARNAME, [TAGNAME]) +# ------------------------------ +m4_define([_LT_TAGVAR], [m4_ifval([$2], [$1_$2], [$1])]) + + +# _LT_CONFIG_COMMANDS +# ------------------- +# Send accumulated output to $CONFIG_STATUS. Thanks to the lists of +# variables for single and double quote escaping we saved from calls +# to _LT_DECL, we can put quote escaped variables declarations +# into 'config.status', and then the shell code to quote escape them in +# for loops in 'config.status'. Finally, any additional code accumulated +# from calls to _LT_CONFIG_LIBTOOL_INIT is expanded. +m4_defun([_LT_CONFIG_COMMANDS], +[AC_PROVIDE_IFELSE([LT_OUTPUT], + dnl If the libtool generation code has been placed in $CONFIG_LT, + dnl instead of duplicating it all over again into config.status, + dnl then we will have config.status run $CONFIG_LT later, so it + dnl needs to know what name is stored there: + [AC_CONFIG_COMMANDS([libtool], + [$SHELL $CONFIG_LT || AS_EXIT(1)], [CONFIG_LT='$CONFIG_LT'])], + dnl If the libtool generation code is destined for config.status, + dnl expand the accumulated commands and init code now: + [AC_CONFIG_COMMANDS([libtool], + [_LT_OUTPUT_LIBTOOL_COMMANDS], [_LT_OUTPUT_LIBTOOL_COMMANDS_INIT])]) +])#_LT_CONFIG_COMMANDS + + +# Initialize. +m4_define([_LT_OUTPUT_LIBTOOL_COMMANDS_INIT], +[ + +# The HP-UX ksh and POSIX shell print the target directory to stdout +# if CDPATH is set. +(unset CDPATH) >/dev/null 2>&1 && unset CDPATH + +sed_quote_subst='$sed_quote_subst' +double_quote_subst='$double_quote_subst' +delay_variable_subst='$delay_variable_subst' +_LT_CONFIG_STATUS_DECLARATIONS +LTCC='$LTCC' +LTCFLAGS='$LTCFLAGS' +compiler='$compiler_DEFAULT' + +# A function that is used when there is no print builtin or printf. +func_fallback_echo () +{ + eval 'cat <<_LTECHO_EOF +\$[]1 +_LTECHO_EOF' +} + +# Quote evaled strings. +for var in lt_decl_all_varnames([[ \ +]], lt_decl_quote_varnames); do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[[\\\\\\\`\\"\\\$]]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED \\"\\\$sed_quote_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +# Double-quote double-evaled strings. +for var in lt_decl_all_varnames([[ \ +]], lt_decl_dquote_varnames); do + case \`eval \\\\\$ECHO \\\\""\\\\\$\$var"\\\\"\` in + *[[\\\\\\\`\\"\\\$]]*) + eval "lt_\$var=\\\\\\"\\\`\\\$ECHO \\"\\\$\$var\\" | \\\$SED -e \\"\\\$double_quote_subst\\" -e \\"\\\$sed_quote_subst\\" -e \\"\\\$delay_variable_subst\\"\\\`\\\\\\"" ## exclude from sc_prohibit_nested_quotes + ;; + *) + eval "lt_\$var=\\\\\\"\\\$\$var\\\\\\"" + ;; + esac +done + +_LT_OUTPUT_LIBTOOL_INIT +]) + +# _LT_GENERATED_FILE_INIT(FILE, [COMMENT]) +# ------------------------------------ +# Generate a child script FILE with all initialization necessary to +# reuse the environment learned by the parent script, and make the +# file executable. If COMMENT is supplied, it is inserted after the +# '#!' sequence but before initialization text begins. After this +# macro, additional text can be appended to FILE to form the body of +# the child script. The macro ends with non-zero status if the +# file could not be fully written (such as if the disk is full). +m4_ifdef([AS_INIT_GENERATED], +[m4_defun([_LT_GENERATED_FILE_INIT],[AS_INIT_GENERATED($@)])], +[m4_defun([_LT_GENERATED_FILE_INIT], +[m4_require([AS_PREPARE])]dnl +[m4_pushdef([AS_MESSAGE_LOG_FD])]dnl +[lt_write_fail=0 +cat >$1 <<_ASEOF || lt_write_fail=1 +#! $SHELL +# Generated by $as_me. +$2 +SHELL=\${CONFIG_SHELL-$SHELL} +export SHELL +_ASEOF +cat >>$1 <<\_ASEOF || lt_write_fail=1 +AS_SHELL_SANITIZE +_AS_PREPARE +exec AS_MESSAGE_FD>&1 +_ASEOF +test 0 = "$lt_write_fail" && chmod +x $1[]dnl +m4_popdef([AS_MESSAGE_LOG_FD])])])# _LT_GENERATED_FILE_INIT + +# LT_OUTPUT +# --------- +# This macro allows early generation of the libtool script (before +# AC_OUTPUT is called), incase it is used in configure for compilation +# tests. +AC_DEFUN([LT_OUTPUT], +[: ${CONFIG_LT=./config.lt} +AC_MSG_NOTICE([creating $CONFIG_LT]) +_LT_GENERATED_FILE_INIT(["$CONFIG_LT"], +[# Run this file to recreate a libtool stub with the current configuration.]) + +cat >>"$CONFIG_LT" <<\_LTEOF +lt_cl_silent=false +exec AS_MESSAGE_LOG_FD>>config.log +{ + echo + AS_BOX([Running $as_me.]) +} >&AS_MESSAGE_LOG_FD + +lt_cl_help="\ +'$as_me' creates a local libtool stub from the current configuration, +for use in further configure time tests before the real libtool is +generated. + +Usage: $[0] [[OPTIONS]] + + -h, --help print this help, then exit + -V, --version print version number, then exit + -q, --quiet do not print progress messages + -d, --debug don't remove temporary files + +Report bugs to ." + +lt_cl_version="\ +m4_ifset([AC_PACKAGE_NAME], [AC_PACKAGE_NAME ])config.lt[]dnl +m4_ifset([AC_PACKAGE_VERSION], [ AC_PACKAGE_VERSION]) +configured by $[0], generated by m4_PACKAGE_STRING. + +Copyright (C) 2011 Free Software Foundation, Inc. +This config.lt script is free software; the Free Software Foundation +gives unlimited permision to copy, distribute and modify it." + +while test 0 != $[#] +do + case $[1] in + --version | --v* | -V ) + echo "$lt_cl_version"; exit 0 ;; + --help | --h* | -h ) + echo "$lt_cl_help"; exit 0 ;; + --debug | --d* | -d ) + debug=: ;; + --quiet | --q* | --silent | --s* | -q ) + lt_cl_silent=: ;; + + -*) AC_MSG_ERROR([unrecognized option: $[1] +Try '$[0] --help' for more information.]) ;; + + *) AC_MSG_ERROR([unrecognized argument: $[1] +Try '$[0] --help' for more information.]) ;; + esac + shift +done + +if $lt_cl_silent; then + exec AS_MESSAGE_FD>/dev/null +fi +_LTEOF + +cat >>"$CONFIG_LT" <<_LTEOF +_LT_OUTPUT_LIBTOOL_COMMANDS_INIT +_LTEOF + +cat >>"$CONFIG_LT" <<\_LTEOF +AC_MSG_NOTICE([creating $ofile]) +_LT_OUTPUT_LIBTOOL_COMMANDS +AS_EXIT(0) +_LTEOF +chmod +x "$CONFIG_LT" + +# configure is writing to config.log, but config.lt does its own redirection, +# appending to config.log, which fails on DOS, as config.log is still kept +# open by configure. Here we exec the FD to /dev/null, effectively closing +# config.log, so it can be properly (re)opened and appended to by config.lt. +lt_cl_success=: +test yes = "$silent" && + lt_config_lt_args="$lt_config_lt_args --quiet" +exec AS_MESSAGE_LOG_FD>/dev/null +$SHELL "$CONFIG_LT" $lt_config_lt_args || lt_cl_success=false +exec AS_MESSAGE_LOG_FD>>config.log +$lt_cl_success || AS_EXIT(1) +])# LT_OUTPUT + + +# _LT_CONFIG(TAG) +# --------------- +# If TAG is the built-in tag, create an initial libtool script with a +# default configuration from the untagged config vars. Otherwise add code +# to config.status for appending the configuration named by TAG from the +# matching tagged config vars. +m4_defun([_LT_CONFIG], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +_LT_CONFIG_SAVE_COMMANDS([ + m4_define([_LT_TAG], m4_if([$1], [], [C], [$1]))dnl + m4_if(_LT_TAG, [C], [ + # See if we are running on zsh, and set the options that allow our + # commands through without removal of \ escapes. + if test -n "${ZSH_VERSION+set}"; then + setopt NO_GLOB_SUBST + fi + + cfgfile=${ofile}T + trap "$RM \"$cfgfile\"; exit 1" 1 2 15 + $RM "$cfgfile" + + cat <<_LT_EOF >> "$cfgfile" +#! $SHELL +# Generated automatically by $as_me ($PACKAGE) $VERSION +# NOTE: Changes made to this file will be lost: look at ltmain.sh. + +# Provide generalized library-building support services. +# Written by Gordon Matzigkeit, 1996 + +_LT_COPYING +_LT_LIBTOOL_TAGS + +# Configured defaults for sys_lib_dlsearch_path munging. +: \${LT_SYS_LIBRARY_PATH="$configure_time_lt_sys_library_path"} + +# ### BEGIN LIBTOOL CONFIG +_LT_LIBTOOL_CONFIG_VARS +_LT_LIBTOOL_TAG_VARS +# ### END LIBTOOL CONFIG + +_LT_EOF + + cat <<'_LT_EOF' >> "$cfgfile" + +# ### BEGIN FUNCTIONS SHARED WITH CONFIGURE + +_LT_PREPARE_MUNGE_PATH_LIST +_LT_PREPARE_CC_BASENAME + +# ### END FUNCTIONS SHARED WITH CONFIGURE + +_LT_EOF + + case $host_os in + aix3*) + cat <<\_LT_EOF >> "$cfgfile" +# AIX sometimes has problems with the GCC collect2 program. For some +# reason, if we set the COLLECT_NAMES environment variable, the problems +# vanish in a puff of smoke. +if test set != "${COLLECT_NAMES+set}"; then + COLLECT_NAMES= + export COLLECT_NAMES +fi +_LT_EOF + ;; + esac + + _LT_PROG_LTMAIN + + # We use sed instead of cat because bash on DJGPP gets confused if + # if finds mixed CR/LF and LF-only lines. Since sed operates in + # text mode, it properly converts lines to CR/LF. This bash problem + # is reportedly fixed, but why not run on old versions too? + sed '$q' "$ltmain" >> "$cfgfile" \ + || (rm -f "$cfgfile"; exit 1) + + mv -f "$cfgfile" "$ofile" || + (rm -f "$ofile" && cp "$cfgfile" "$ofile" && rm -f "$cfgfile") + chmod +x "$ofile" +], +[cat <<_LT_EOF >> "$ofile" + +dnl Unfortunately we have to use $1 here, since _LT_TAG is not expanded +dnl in a comment (ie after a #). +# ### BEGIN LIBTOOL TAG CONFIG: $1 +_LT_LIBTOOL_TAG_VARS(_LT_TAG) +# ### END LIBTOOL TAG CONFIG: $1 +_LT_EOF +])dnl /m4_if +], +[m4_if([$1], [], [ + PACKAGE='$PACKAGE' + VERSION='$VERSION' + RM='$RM' + ofile='$ofile'], []) +])dnl /_LT_CONFIG_SAVE_COMMANDS +])# _LT_CONFIG + + +# LT_SUPPORTED_TAG(TAG) +# --------------------- +# Trace this macro to discover what tags are supported by the libtool +# --tag option, using: +# autoconf --trace 'LT_SUPPORTED_TAG:$1' +AC_DEFUN([LT_SUPPORTED_TAG], []) + + +# C support is built-in for now +m4_define([_LT_LANG_C_enabled], []) +m4_define([_LT_TAGS], []) + + +# LT_LANG(LANG) +# ------------- +# Enable libtool support for the given language if not already enabled. +AC_DEFUN([LT_LANG], +[AC_BEFORE([$0], [LT_OUTPUT])dnl +m4_case([$1], + [C], [_LT_LANG(C)], + [C++], [_LT_LANG(CXX)], + [Go], [_LT_LANG(GO)], + [Java], [_LT_LANG(GCJ)], + [Fortran 77], [_LT_LANG(F77)], + [Fortran], [_LT_LANG(FC)], + [Windows Resource], [_LT_LANG(RC)], + [m4_ifdef([_LT_LANG_]$1[_CONFIG], + [_LT_LANG($1)], + [m4_fatal([$0: unsupported language: "$1"])])])dnl +])# LT_LANG + + +# _LT_LANG(LANGNAME) +# ------------------ +m4_defun([_LT_LANG], +[m4_ifdef([_LT_LANG_]$1[_enabled], [], + [LT_SUPPORTED_TAG([$1])dnl + m4_append([_LT_TAGS], [$1 ])dnl + m4_define([_LT_LANG_]$1[_enabled], [])dnl + _LT_LANG_$1_CONFIG($1)])dnl +])# _LT_LANG + + +m4_ifndef([AC_PROG_GO], [ +############################################################ +# NOTE: This macro has been submitted for inclusion into # +# GNU Autoconf as AC_PROG_GO. When it is available in # +# a released version of Autoconf we should remove this # +# macro and use it instead. # +############################################################ +m4_defun([AC_PROG_GO], +[AC_LANG_PUSH(Go)dnl +AC_ARG_VAR([GOC], [Go compiler command])dnl +AC_ARG_VAR([GOFLAGS], [Go compiler flags])dnl +_AC_ARG_VAR_LDFLAGS()dnl +AC_CHECK_TOOL(GOC, gccgo) +if test -z "$GOC"; then + if test -n "$ac_tool_prefix"; then + AC_CHECK_PROG(GOC, [${ac_tool_prefix}gccgo], [${ac_tool_prefix}gccgo]) + fi +fi +if test -z "$GOC"; then + AC_CHECK_PROG(GOC, gccgo, gccgo, false) +fi +])#m4_defun +])#m4_ifndef + + +# _LT_LANG_DEFAULT_CONFIG +# ----------------------- +m4_defun([_LT_LANG_DEFAULT_CONFIG], +[AC_PROVIDE_IFELSE([AC_PROG_CXX], + [LT_LANG(CXX)], + [m4_define([AC_PROG_CXX], defn([AC_PROG_CXX])[LT_LANG(CXX)])]) + +AC_PROVIDE_IFELSE([AC_PROG_F77], + [LT_LANG(F77)], + [m4_define([AC_PROG_F77], defn([AC_PROG_F77])[LT_LANG(F77)])]) + +AC_PROVIDE_IFELSE([AC_PROG_FC], + [LT_LANG(FC)], + [m4_define([AC_PROG_FC], defn([AC_PROG_FC])[LT_LANG(FC)])]) + +dnl The call to [A][M_PROG_GCJ] is quoted like that to stop aclocal +dnl pulling things in needlessly. +AC_PROVIDE_IFELSE([AC_PROG_GCJ], + [LT_LANG(GCJ)], + [AC_PROVIDE_IFELSE([A][M_PROG_GCJ], + [LT_LANG(GCJ)], + [AC_PROVIDE_IFELSE([LT_PROG_GCJ], + [LT_LANG(GCJ)], + [m4_ifdef([AC_PROG_GCJ], + [m4_define([AC_PROG_GCJ], defn([AC_PROG_GCJ])[LT_LANG(GCJ)])]) + m4_ifdef([A][M_PROG_GCJ], + [m4_define([A][M_PROG_GCJ], defn([A][M_PROG_GCJ])[LT_LANG(GCJ)])]) + m4_ifdef([LT_PROG_GCJ], + [m4_define([LT_PROG_GCJ], defn([LT_PROG_GCJ])[LT_LANG(GCJ)])])])])]) + +AC_PROVIDE_IFELSE([AC_PROG_GO], + [LT_LANG(GO)], + [m4_define([AC_PROG_GO], defn([AC_PROG_GO])[LT_LANG(GO)])]) + +AC_PROVIDE_IFELSE([LT_PROG_RC], + [LT_LANG(RC)], + [m4_define([LT_PROG_RC], defn([LT_PROG_RC])[LT_LANG(RC)])]) +])# _LT_LANG_DEFAULT_CONFIG + +# Obsolete macros: +AU_DEFUN([AC_LIBTOOL_CXX], [LT_LANG(C++)]) +AU_DEFUN([AC_LIBTOOL_F77], [LT_LANG(Fortran 77)]) +AU_DEFUN([AC_LIBTOOL_FC], [LT_LANG(Fortran)]) +AU_DEFUN([AC_LIBTOOL_GCJ], [LT_LANG(Java)]) +AU_DEFUN([AC_LIBTOOL_RC], [LT_LANG(Windows Resource)]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_CXX], []) +dnl AC_DEFUN([AC_LIBTOOL_F77], []) +dnl AC_DEFUN([AC_LIBTOOL_FC], []) +dnl AC_DEFUN([AC_LIBTOOL_GCJ], []) +dnl AC_DEFUN([AC_LIBTOOL_RC], []) + + +# _LT_TAG_COMPILER +# ---------------- +m4_defun([_LT_TAG_COMPILER], +[AC_REQUIRE([AC_PROG_CC])dnl + +_LT_DECL([LTCC], [CC], [1], [A C compiler])dnl +_LT_DECL([LTCFLAGS], [CFLAGS], [1], [LTCC compiler flags])dnl +_LT_TAGDECL([CC], [compiler], [1], [A language specific compiler])dnl +_LT_TAGDECL([with_gcc], [GCC], [0], [Is the compiler the GNU compiler?])dnl + +# If no C compiler was specified, use CC. +LTCC=${LTCC-"$CC"} + +# If no C compiler flags were specified, use CFLAGS. +LTCFLAGS=${LTCFLAGS-"$CFLAGS"} + +# Allow CC to be a program name with arguments. +compiler=$CC +])# _LT_TAG_COMPILER + + +# _LT_COMPILER_BOILERPLATE +# ------------------------ +# Check for compiler boilerplate output or warnings with +# the simple compiler test code. +m4_defun([_LT_COMPILER_BOILERPLATE], +[m4_require([_LT_DECL_SED])dnl +ac_outfile=conftest.$ac_objext +echo "$lt_simple_compile_test_code" >conftest.$ac_ext +eval "$ac_compile" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_compiler_boilerplate=`cat conftest.err` +$RM conftest* +])# _LT_COMPILER_BOILERPLATE + + +# _LT_LINKER_BOILERPLATE +# ---------------------- +# Check for linker boilerplate output or warnings with +# the simple link test code. +m4_defun([_LT_LINKER_BOILERPLATE], +[m4_require([_LT_DECL_SED])dnl +ac_outfile=conftest.$ac_objext +echo "$lt_simple_link_test_code" >conftest.$ac_ext +eval "$ac_link" 2>&1 >/dev/null | $SED '/^$/d; /^ *+/d' >conftest.err +_lt_linker_boilerplate=`cat conftest.err` +$RM -r conftest* +])# _LT_LINKER_BOILERPLATE + +# _LT_REQUIRED_DARWIN_CHECKS +# ------------------------- +m4_defun_once([_LT_REQUIRED_DARWIN_CHECKS],[ + case $host_os in + rhapsody* | darwin*) + AC_CHECK_TOOL([DSYMUTIL], [dsymutil], [:]) + AC_CHECK_TOOL([NMEDIT], [nmedit], [:]) + AC_CHECK_TOOL([LIPO], [lipo], [:]) + AC_CHECK_TOOL([OTOOL], [otool], [:]) + AC_CHECK_TOOL([OTOOL64], [otool64], [:]) + _LT_DECL([], [DSYMUTIL], [1], + [Tool to manipulate archived DWARF debug symbol files on Mac OS X]) + _LT_DECL([], [NMEDIT], [1], + [Tool to change global to local symbols on Mac OS X]) + _LT_DECL([], [LIPO], [1], + [Tool to manipulate fat objects and archives on Mac OS X]) + _LT_DECL([], [OTOOL], [1], + [ldd/readelf like tool for Mach-O binaries on Mac OS X]) + _LT_DECL([], [OTOOL64], [1], + [ldd/readelf like tool for 64 bit Mach-O binaries on Mac OS X 10.4]) + + AC_CACHE_CHECK([for -single_module linker flag],[lt_cv_apple_cc_single_mod], + [lt_cv_apple_cc_single_mod=no + if test -z "$LT_MULTI_MODULE"; then + # By default we will add the -single_module flag. You can override + # by either setting the environment variable LT_MULTI_MODULE + # non-empty at configure time, or by adding -multi_module to the + # link flags. + rm -rf libconftest.dylib* + echo "int foo(void){return 1;}" > conftest.c + echo "$LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ +-dynamiclib -Wl,-single_module conftest.c" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS $LDFLAGS -o libconftest.dylib \ + -dynamiclib -Wl,-single_module conftest.c 2>conftest.err + _lt_result=$? + # If there is a non-empty error log, and "single_module" + # appears in it, assume the flag caused a linker warning + if test -s conftest.err && $GREP single_module conftest.err; then + cat conftest.err >&AS_MESSAGE_LOG_FD + # Otherwise, if the output was created with a 0 exit code from + # the compiler, it worked. + elif test -f libconftest.dylib && test 0 = "$_lt_result"; then + lt_cv_apple_cc_single_mod=yes + else + cat conftest.err >&AS_MESSAGE_LOG_FD + fi + rm -rf libconftest.dylib* + rm -f conftest.* + fi]) + + AC_CACHE_CHECK([for -exported_symbols_list linker flag], + [lt_cv_ld_exported_symbols_list], + [lt_cv_ld_exported_symbols_list=no + save_LDFLAGS=$LDFLAGS + echo "_main" > conftest.sym + LDFLAGS="$LDFLAGS -Wl,-exported_symbols_list,conftest.sym" + AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], + [lt_cv_ld_exported_symbols_list=yes], + [lt_cv_ld_exported_symbols_list=no]) + LDFLAGS=$save_LDFLAGS + ]) + + AC_CACHE_CHECK([for -force_load linker flag],[lt_cv_ld_force_load], + [lt_cv_ld_force_load=no + cat > conftest.c << _LT_EOF +int forced_loaded() { return 2;} +_LT_EOF + echo "$LTCC $LTCFLAGS -c -o conftest.o conftest.c" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS -c -o conftest.o conftest.c 2>&AS_MESSAGE_LOG_FD + echo "$AR cru libconftest.a conftest.o" >&AS_MESSAGE_LOG_FD + $AR cru libconftest.a conftest.o 2>&AS_MESSAGE_LOG_FD + echo "$RANLIB libconftest.a" >&AS_MESSAGE_LOG_FD + $RANLIB libconftest.a 2>&AS_MESSAGE_LOG_FD + cat > conftest.c << _LT_EOF +int main() { return 0;} +_LT_EOF + echo "$LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a" >&AS_MESSAGE_LOG_FD + $LTCC $LTCFLAGS $LDFLAGS -o conftest conftest.c -Wl,-force_load,./libconftest.a 2>conftest.err + _lt_result=$? + if test -s conftest.err && $GREP force_load conftest.err; then + cat conftest.err >&AS_MESSAGE_LOG_FD + elif test -f conftest && test 0 = "$_lt_result" && $GREP forced_load conftest >/dev/null 2>&1; then + lt_cv_ld_force_load=yes + else + cat conftest.err >&AS_MESSAGE_LOG_FD + fi + rm -f conftest.err libconftest.a conftest conftest.c + rm -rf conftest.dSYM + ]) + case $host_os in + rhapsody* | darwin1.[[012]]) + _lt_dar_allow_undefined='$wl-undefined ${wl}suppress' ;; + darwin1.*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + darwin*) # darwin 5.x on + # if running on 10.5 or later, the deployment target defaults + # to the OS version, if on x86, and 10.4, the deployment + # target defaults to 10.4. Don't you love it? + case ${MACOSX_DEPLOYMENT_TARGET-10.0},$host in + 10.0,*86*-darwin8*|10.0,*-darwin[[91]]*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + 10.[[012]][[,.]]*) + _lt_dar_allow_undefined='$wl-flat_namespace $wl-undefined ${wl}suppress' ;; + 10.*) + _lt_dar_allow_undefined='$wl-undefined ${wl}dynamic_lookup' ;; + esac + ;; + esac + if test yes = "$lt_cv_apple_cc_single_mod"; then + _lt_dar_single_mod='$single_module' + fi + if test yes = "$lt_cv_ld_exported_symbols_list"; then + _lt_dar_export_syms=' $wl-exported_symbols_list,$output_objdir/$libname-symbols.expsym' + else + _lt_dar_export_syms='~$NMEDIT -s $output_objdir/$libname-symbols.expsym $lib' + fi + if test : != "$DSYMUTIL" && test no = "$lt_cv_ld_force_load"; then + _lt_dsymutil='~$DSYMUTIL $lib || :' + else + _lt_dsymutil= + fi + ;; + esac +]) + + +# _LT_DARWIN_LINKER_FEATURES([TAG]) +# --------------------------------- +# Checks for linker and compiler features on darwin +m4_defun([_LT_DARWIN_LINKER_FEATURES], +[ + m4_require([_LT_REQUIRED_DARWIN_CHECKS]) + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_automatic, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + if test yes = "$lt_cv_ld_force_load"; then + _LT_TAGVAR(whole_archive_flag_spec, $1)='`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience $wl-force_load,$conv\"; done; func_echo_all \"$new_convenience\"`' + m4_case([$1], [F77], [_LT_TAGVAR(compiler_needs_object, $1)=yes], + [FC], [_LT_TAGVAR(compiler_needs_object, $1)=yes]) + else + _LT_TAGVAR(whole_archive_flag_spec, $1)='' + fi + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=$_lt_dar_allow_undefined + case $cc_basename in + ifort*|nagfor*) _lt_dar_can_shared=yes ;; + *) _lt_dar_can_shared=$GCC ;; + esac + if test yes = "$_lt_dar_can_shared"; then + output_verbose_link_cmd=func_echo_all + _LT_TAGVAR(archive_cmds, $1)="\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dsymutil" + _LT_TAGVAR(module_cmds, $1)="\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dsymutil" + _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$libobjs \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring $_lt_dar_single_mod$_lt_dar_export_syms$_lt_dsymutil" + _LT_TAGVAR(module_expsym_cmds, $1)="sed -e 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC \$allow_undefined_flag -o \$lib -bundle \$libobjs \$deplibs \$compiler_flags$_lt_dar_export_syms$_lt_dsymutil" + m4_if([$1], [CXX], +[ if test yes != "$lt_cv_apple_cc_single_mod"; then + _LT_TAGVAR(archive_cmds, $1)="\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dsymutil" + _LT_TAGVAR(archive_expsym_cmds, $1)="sed 's|^|_|' < \$export_symbols > \$output_objdir/\$libname-symbols.expsym~\$CC -r -keep_private_externs -nostdlib -o \$lib-master.o \$libobjs~\$CC -dynamiclib \$allow_undefined_flag -o \$lib \$lib-master.o \$deplibs \$compiler_flags -install_name \$rpath/\$soname \$verstring$_lt_dar_export_syms$_lt_dsymutil" + fi +],[]) + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi +]) + +# _LT_SYS_MODULE_PATH_AIX([TAGNAME]) +# ---------------------------------- +# Links a minimal program and checks the executable +# for the system default hardcoded library path. In most cases, +# this is /usr/lib:/lib, but when the MPI compilers are used +# the location of the communication and MPI libs are included too. +# If we don't find anything, use the default library path according +# to the aix ld manual. +# Store the results from the different compilers for each TAGNAME. +# Allow to override them for all tags through lt_cv_aix_libpath. +m4_defun([_LT_SYS_MODULE_PATH_AIX], +[m4_require([_LT_DECL_SED])dnl +if test set = "${lt_cv_aix_libpath+set}"; then + aix_libpath=$lt_cv_aix_libpath +else + AC_CACHE_VAL([_LT_TAGVAR([lt_cv_aix_libpath_], [$1])], + [AC_LINK_IFELSE([AC_LANG_PROGRAM],[ + lt_aix_libpath_sed='[ + /Import File Strings/,/^$/ { + /^0/ { + s/^0 *\([^ ]*\) *$/\1/ + p + } + }]' + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -H conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + # Check for a 64-bit object if we didn't find anything. + if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=`dump -HX64 conftest$ac_exeext 2>/dev/null | $SED -n -e "$lt_aix_libpath_sed"` + fi],[]) + if test -z "$_LT_TAGVAR([lt_cv_aix_libpath_], [$1])"; then + _LT_TAGVAR([lt_cv_aix_libpath_], [$1])=/usr/lib:/lib + fi + ]) + aix_libpath=$_LT_TAGVAR([lt_cv_aix_libpath_], [$1]) +fi +])# _LT_SYS_MODULE_PATH_AIX + + +# _LT_SHELL_INIT(ARG) +# ------------------- +m4_define([_LT_SHELL_INIT], +[m4_divert_text([M4SH-INIT], [$1 +])])# _LT_SHELL_INIT + + + +# _LT_PROG_ECHO_BACKSLASH +# ----------------------- +# Find how we can fake an echo command that does not interpret backslash. +# In particular, with Autoconf 2.60 or later we add some code to the start +# of the generated configure script that will find a shell with a builtin +# printf (that we can use as an echo command). +m4_defun([_LT_PROG_ECHO_BACKSLASH], +[ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO +ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + +AC_MSG_CHECKING([how to print strings]) +# Test print first, because it will be a builtin if present. +if test "X`( print -r -- -n ) 2>/dev/null`" = X-n && \ + test "X`print -r -- $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='print -r --' +elif test "X`printf %s $ECHO 2>/dev/null`" = "X$ECHO"; then + ECHO='printf %s\n' +else + # Use this function as a fallback that always works. + func_fallback_echo () + { + eval 'cat <<_LTECHO_EOF +$[]1 +_LTECHO_EOF' + } + ECHO='func_fallback_echo' +fi + +# func_echo_all arg... +# Invoke $ECHO with all args, space-separated. +func_echo_all () +{ + $ECHO "$*" +} + +case $ECHO in + printf*) AC_MSG_RESULT([printf]) ;; + print*) AC_MSG_RESULT([print -r]) ;; + *) AC_MSG_RESULT([cat]) ;; +esac + +m4_ifdef([_AS_DETECT_SUGGESTED], +[_AS_DETECT_SUGGESTED([ + test -n "${ZSH_VERSION+set}${BASH_VERSION+set}" || ( + ECHO='\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\' + ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO + ECHO=$ECHO$ECHO$ECHO$ECHO$ECHO$ECHO + PATH=/empty FPATH=/empty; export PATH FPATH + test "X`printf %s $ECHO`" = "X$ECHO" \ + || test "X`print -r -- $ECHO`" = "X$ECHO" )])]) + +_LT_DECL([], [SHELL], [1], [Shell to use when invoking shell scripts]) +_LT_DECL([], [ECHO], [1], [An echo program that protects backslashes]) +])# _LT_PROG_ECHO_BACKSLASH + + +# _LT_WITH_SYSROOT +# ---------------- +AC_DEFUN([_LT_WITH_SYSROOT], +[AC_MSG_CHECKING([for sysroot]) +AC_ARG_WITH([sysroot], +[AS_HELP_STRING([--with-sysroot@<:@=DIR@:>@], + [Search for dependent libraries within DIR (or the compiler's sysroot + if not specified).])], +[], [with_sysroot=no]) + +dnl lt_sysroot will always be passed unquoted. We quote it here +dnl in case the user passed a directory name. +lt_sysroot= +case $with_sysroot in #( + yes) + if test yes = "$GCC"; then + lt_sysroot=`$CC --print-sysroot 2>/dev/null` + fi + ;; #( + /*) + lt_sysroot=`echo "$with_sysroot" | sed -e "$sed_quote_subst"` + ;; #( + no|'') + ;; #( + *) + AC_MSG_RESULT([$with_sysroot]) + AC_MSG_ERROR([The sysroot must be an absolute path.]) + ;; +esac + + AC_MSG_RESULT([${lt_sysroot:-no}]) +_LT_DECL([], [lt_sysroot], [0], [The root where to search for ]dnl +[dependent libraries, and where our libraries should be installed.])]) + +# _LT_ENABLE_LOCK +# --------------- +m4_defun([_LT_ENABLE_LOCK], +[AC_ARG_ENABLE([libtool-lock], + [AS_HELP_STRING([--disable-libtool-lock], + [avoid locking (might break parallel builds)])]) +test no = "$enable_libtool_lock" || enable_libtool_lock=yes + +# Some flags need to be propagated to the compiler or linker for good +# libtool support. +case $host in +ia64-*-hpux*) + # Find out what ABI is being produced by ac_compile, and set mode + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.$ac_objext` in + *ELF-32*) + HPUX_IA64_MODE=32 + ;; + *ELF-64*) + HPUX_IA64_MODE=64 + ;; + esac + fi + rm -rf conftest* + ;; +*-*-irix6*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + if test yes = "$lt_cv_prog_gnu_ld"; then + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -melf32bsmip" + ;; + *N32*) + LD="${LD-ld} -melf32bmipn32" + ;; + *64-bit*) + LD="${LD-ld} -melf64bmip" + ;; + esac + else + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + LD="${LD-ld} -32" + ;; + *N32*) + LD="${LD-ld} -n32" + ;; + *64-bit*) + LD="${LD-ld} -64" + ;; + esac + fi + fi + rm -rf conftest* + ;; + +mips64*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo '[#]line '$LINENO' "configure"' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + emul=elf + case `/usr/bin/file conftest.$ac_objext` in + *32-bit*) + emul="${emul}32" + ;; + *64-bit*) + emul="${emul}64" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *MSB*) + emul="${emul}btsmip" + ;; + *LSB*) + emul="${emul}ltsmip" + ;; + esac + case `/usr/bin/file conftest.$ac_objext` in + *N32*) + emul="${emul}n32" + ;; + esac + LD="${LD-ld} -m $emul" + fi + rm -rf conftest* + ;; + +x86_64-*kfreebsd*-gnu|x86_64-*linux*|powerpc*-*linux*| \ +s390*-*linux*|s390*-*tpf*|sparc*-*linux*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. Note that the listed cases only cover the + # situations where additional linker options are needed (such as when + # doing 32-bit compilation for a host where ld defaults to 64-bit, or + # vice versa); the common cases where no linker options are needed do + # not appear in the list. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.o` in + *32-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_i386_fbsd" + ;; + x86_64-*linux*) + case `/usr/bin/file conftest.o` in + *x86-64*) + LD="${LD-ld} -m elf32_x86_64" + ;; + *) + LD="${LD-ld} -m elf_i386" + ;; + esac + ;; + powerpc64le-*linux*) + LD="${LD-ld} -m elf32lppclinux" + ;; + powerpc64-*linux*) + LD="${LD-ld} -m elf32ppclinux" + ;; + s390x-*linux*) + LD="${LD-ld} -m elf_s390" + ;; + sparc64-*linux*) + LD="${LD-ld} -m elf32_sparc" + ;; + esac + ;; + *64-bit*) + case $host in + x86_64-*kfreebsd*-gnu) + LD="${LD-ld} -m elf_x86_64_fbsd" + ;; + x86_64-*linux*) + LD="${LD-ld} -m elf_x86_64" + ;; + powerpcle-*linux*) + LD="${LD-ld} -m elf64lppc" + ;; + powerpc-*linux*) + LD="${LD-ld} -m elf64ppc" + ;; + s390*-*linux*|s390*-*tpf*) + LD="${LD-ld} -m elf64_s390" + ;; + sparc*-*linux*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; + +*-*-sco3.2v5*) + # On SCO OpenServer 5, we need -belf to get full-featured binaries. + SAVE_CFLAGS=$CFLAGS + CFLAGS="$CFLAGS -belf" + AC_CACHE_CHECK([whether the C compiler needs -belf], lt_cv_cc_needs_belf, + [AC_LANG_PUSH(C) + AC_LINK_IFELSE([AC_LANG_PROGRAM([[]],[[]])],[lt_cv_cc_needs_belf=yes],[lt_cv_cc_needs_belf=no]) + AC_LANG_POP]) + if test yes != "$lt_cv_cc_needs_belf"; then + # this is probably gcc 2.8.0, egcs 1.0 or newer; no need for -belf + CFLAGS=$SAVE_CFLAGS + fi + ;; +*-*solaris*) + # Find out what ABI is being produced by ac_compile, and set linker + # options accordingly. + echo 'int i;' > conftest.$ac_ext + if AC_TRY_EVAL(ac_compile); then + case `/usr/bin/file conftest.o` in + *64-bit*) + case $lt_cv_prog_gnu_ld in + yes*) + case $host in + i?86-*-solaris*|x86_64-*-solaris*) + LD="${LD-ld} -m elf_x86_64" + ;; + sparc*-*-solaris*) + LD="${LD-ld} -m elf64_sparc" + ;; + esac + # GNU ld 2.21 introduced _sol2 emulations. Use them if available. + if ${LD-ld} -V | grep _sol2 >/dev/null 2>&1; then + LD=${LD-ld}_sol2 + fi + ;; + *) + if ${LD-ld} -64 -r -o conftest2.o conftest.o >/dev/null 2>&1; then + LD="${LD-ld} -64" + fi + ;; + esac + ;; + esac + fi + rm -rf conftest* + ;; +esac + +need_locks=$enable_libtool_lock +])# _LT_ENABLE_LOCK + + +# _LT_PROG_AR +# ----------- +m4_defun([_LT_PROG_AR], +[AC_CHECK_TOOLS(AR, [ar], false) +: ${AR=ar} +: ${AR_FLAGS=cru} +_LT_DECL([], [AR], [1], [The archiver]) +_LT_DECL([], [AR_FLAGS], [1], [Flags to create an archive]) + +AC_CACHE_CHECK([for archiver @FILE support], [lt_cv_ar_at_file], + [lt_cv_ar_at_file=no + AC_COMPILE_IFELSE([AC_LANG_PROGRAM], + [echo conftest.$ac_objext > conftest.lst + lt_ar_try='$AR $AR_FLAGS libconftest.a @conftest.lst >&AS_MESSAGE_LOG_FD' + AC_TRY_EVAL([lt_ar_try]) + if test 0 -eq "$ac_status"; then + # Ensure the archiver fails upon bogus file names. + rm -f conftest.$ac_objext libconftest.a + AC_TRY_EVAL([lt_ar_try]) + if test 0 -ne "$ac_status"; then + lt_cv_ar_at_file=@ + fi + fi + rm -f conftest.* libconftest.a + ]) + ]) + +if test no = "$lt_cv_ar_at_file"; then + archiver_list_spec= +else + archiver_list_spec=$lt_cv_ar_at_file +fi +_LT_DECL([], [archiver_list_spec], [1], + [How to feed a file listing to the archiver]) +])# _LT_PROG_AR + + +# _LT_CMD_OLD_ARCHIVE +# ------------------- +m4_defun([_LT_CMD_OLD_ARCHIVE], +[_LT_PROG_AR + +AC_CHECK_TOOL(STRIP, strip, :) +test -z "$STRIP" && STRIP=: +_LT_DECL([], [STRIP], [1], [A symbol stripping program]) + +AC_CHECK_TOOL(RANLIB, ranlib, :) +test -z "$RANLIB" && RANLIB=: +_LT_DECL([], [RANLIB], [1], + [Commands used to install an old-style archive]) + +# Determine commands to create old-style static archives. +old_archive_cmds='$AR $AR_FLAGS $oldlib$oldobjs' +old_postinstall_cmds='chmod 644 $oldlib' +old_postuninstall_cmds= + +if test -n "$RANLIB"; then + case $host_os in + bitrig* | openbsd*) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB -t \$tool_oldlib" + ;; + *) + old_postinstall_cmds="$old_postinstall_cmds~\$RANLIB \$tool_oldlib" + ;; + esac + old_archive_cmds="$old_archive_cmds~\$RANLIB \$tool_oldlib" +fi + +case $host_os in + darwin*) + lock_old_archive_extraction=yes ;; + *) + lock_old_archive_extraction=no ;; +esac +_LT_DECL([], [old_postinstall_cmds], [2]) +_LT_DECL([], [old_postuninstall_cmds], [2]) +_LT_TAGDECL([], [old_archive_cmds], [2], + [Commands used to build an old-style archive]) +_LT_DECL([], [lock_old_archive_extraction], [0], + [Whether to use a lock for old archive extraction]) +])# _LT_CMD_OLD_ARCHIVE + + +# _LT_COMPILER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [OUTPUT-FILE], [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------------------- +# Check whether the given compiler option works +AC_DEFUN([_LT_COMPILER_OPTION], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_SED])dnl +AC_CACHE_CHECK([$1], [$2], + [$2=no + m4_if([$4], , [ac_outfile=conftest.$ac_objext], [ac_outfile=$4]) + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + lt_compiler_flag="$3" ## exclude from sc_useless_quotes_in_assignment + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + # The option is referenced via a variable to avoid confusing sed. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>conftest.err) + ac_status=$? + cat conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s "$ac_outfile"; then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings other than the usual output. + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' >conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if test ! -s conftest.er2 || diff conftest.exp conftest.er2 >/dev/null; then + $2=yes + fi + fi + $RM conftest* +]) + +if test yes = "[$]$2"; then + m4_if([$5], , :, [$5]) +else + m4_if([$6], , :, [$6]) +fi +])# _LT_COMPILER_OPTION + +# Old name: +AU_ALIAS([AC_LIBTOOL_COMPILER_OPTION], [_LT_COMPILER_OPTION]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_COMPILER_OPTION], []) + + +# _LT_LINKER_OPTION(MESSAGE, VARIABLE-NAME, FLAGS, +# [ACTION-SUCCESS], [ACTION-FAILURE]) +# ---------------------------------------------------- +# Check whether the given linker option works +AC_DEFUN([_LT_LINKER_OPTION], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_SED])dnl +AC_CACHE_CHECK([$1], [$2], + [$2=no + save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS $3" + echo "$lt_simple_link_test_code" > conftest.$ac_ext + if (eval $ac_link 2>conftest.err) && test -s conftest$ac_exeext; then + # The linker can only warn and ignore the option if not recognized + # So say no if there are warnings + if test -s conftest.err; then + # Append any errors to the config.log. + cat conftest.err 1>&AS_MESSAGE_LOG_FD + $ECHO "$_lt_linker_boilerplate" | $SED '/^$/d' > conftest.exp + $SED '/^$/d; /^ *+/d' conftest.err >conftest.er2 + if diff conftest.exp conftest.er2 >/dev/null; then + $2=yes + fi + else + $2=yes + fi + fi + $RM -r conftest* + LDFLAGS=$save_LDFLAGS +]) + +if test yes = "[$]$2"; then + m4_if([$4], , :, [$4]) +else + m4_if([$5], , :, [$5]) +fi +])# _LT_LINKER_OPTION + +# Old name: +AU_ALIAS([AC_LIBTOOL_LINKER_OPTION], [_LT_LINKER_OPTION]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_LINKER_OPTION], []) + + +# LT_CMD_MAX_LEN +#--------------- +AC_DEFUN([LT_CMD_MAX_LEN], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +# find the maximum length of command line arguments +AC_MSG_CHECKING([the maximum length of command line arguments]) +AC_CACHE_VAL([lt_cv_sys_max_cmd_len], [dnl + i=0 + teststring=ABCD + + case $build_os in + msdosdjgpp*) + # On DJGPP, this test can blow up pretty badly due to problems in libc + # (any single argument exceeding 2000 bytes causes a buffer overrun + # during glob expansion). Even if it were fixed, the result of this + # check would be larger than it should be. + lt_cv_sys_max_cmd_len=12288; # 12K is about right + ;; + + gnu*) + # Under GNU Hurd, this test is not required because there is + # no limit to the length of command line arguments. + # Libtool will interpret -1 as no limit whatsoever + lt_cv_sys_max_cmd_len=-1; + ;; + + cygwin* | mingw* | cegcc*) + # On Win9x/ME, this test blows up -- it succeeds, but takes + # about 5 minutes as the teststring grows exponentially. + # Worse, since 9x/ME are not pre-emptively multitasking, + # you end up with a "frozen" computer, even though with patience + # the test eventually succeeds (with a max line length of 256k). + # Instead, let's just punt: use the minimum linelength reported by + # all of the supported platforms: 8192 (on NT/2K/XP). + lt_cv_sys_max_cmd_len=8192; + ;; + + mint*) + # On MiNT this can take a long time and run out of memory. + lt_cv_sys_max_cmd_len=8192; + ;; + + amigaos*) + # On AmigaOS with pdksh, this test takes hours, literally. + # So we just punt and use a minimum line length of 8192. + lt_cv_sys_max_cmd_len=8192; + ;; + + bitrig* | darwin* | dragonfly* | freebsd* | netbsd* | openbsd*) + # This has been around since 386BSD, at least. Likely further. + if test -x /sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/sbin/sysctl -n kern.argmax` + elif test -x /usr/sbin/sysctl; then + lt_cv_sys_max_cmd_len=`/usr/sbin/sysctl -n kern.argmax` + else + lt_cv_sys_max_cmd_len=65536 # usable default for all BSDs + fi + # And add a safety zone + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + ;; + + interix*) + # We know the value 262144 and hardcode it with a safety zone (like BSD) + lt_cv_sys_max_cmd_len=196608 + ;; + + os2*) + # The test takes a long time on OS/2. + lt_cv_sys_max_cmd_len=8192 + ;; + + osf*) + # Dr. Hans Ekkehard Plesser reports seeing a kernel panic running configure + # due to this test when exec_disable_arg_limit is 1 on Tru64. It is not + # nice to cause kernel panics so lets avoid the loop below. + # First set a reasonable default. + lt_cv_sys_max_cmd_len=16384 + # + if test -x /sbin/sysconfig; then + case `/sbin/sysconfig -q proc exec_disable_arg_limit` in + *1*) lt_cv_sys_max_cmd_len=-1 ;; + esac + fi + ;; + sco3.2v5*) + lt_cv_sys_max_cmd_len=102400 + ;; + sysv5* | sco5v6* | sysv4.2uw2*) + kargmax=`grep ARG_MAX /etc/conf/cf.d/stune 2>/dev/null` + if test -n "$kargmax"; then + lt_cv_sys_max_cmd_len=`echo $kargmax | sed 's/.*[[ ]]//'` + else + lt_cv_sys_max_cmd_len=32768 + fi + ;; + *) + lt_cv_sys_max_cmd_len=`(getconf ARG_MAX) 2> /dev/null` + if test -n "$lt_cv_sys_max_cmd_len" && \ + test undefined != "$lt_cv_sys_max_cmd_len"; then + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 4` + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \* 3` + else + # Make teststring a little bigger before we do anything with it. + # a 1K string should be a reasonable start. + for i in 1 2 3 4 5 6 7 8; do + teststring=$teststring$teststring + done + SHELL=${SHELL-${CONFIG_SHELL-/bin/sh}} + # If test is not a shell built-in, we'll probably end up computing a + # maximum length that is only half of the actual maximum length, but + # we can't tell. + while { test X`env echo "$teststring$teststring" 2>/dev/null` \ + = "X$teststring$teststring"; } >/dev/null 2>&1 && + test 17 != "$i" # 1/2 MB should be enough + do + i=`expr $i + 1` + teststring=$teststring$teststring + done + # Only check the string length outside the loop. + lt_cv_sys_max_cmd_len=`expr "X$teststring" : ".*" 2>&1` + teststring= + # Add a significant safety factor because C++ compilers can tack on + # massive amounts of additional arguments before passing them to the + # linker. It appears as though 1/2 is a usable value. + lt_cv_sys_max_cmd_len=`expr $lt_cv_sys_max_cmd_len \/ 2` + fi + ;; + esac +]) +if test -n "$lt_cv_sys_max_cmd_len"; then + AC_MSG_RESULT($lt_cv_sys_max_cmd_len) +else + AC_MSG_RESULT(none) +fi +max_cmd_len=$lt_cv_sys_max_cmd_len +_LT_DECL([], [max_cmd_len], [0], + [What is the maximum length of a command?]) +])# LT_CMD_MAX_LEN + +# Old name: +AU_ALIAS([AC_LIBTOOL_SYS_MAX_CMD_LEN], [LT_CMD_MAX_LEN]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_SYS_MAX_CMD_LEN], []) + + +# _LT_HEADER_DLFCN +# ---------------- +m4_defun([_LT_HEADER_DLFCN], +[AC_CHECK_HEADERS([dlfcn.h], [], [], [AC_INCLUDES_DEFAULT])dnl +])# _LT_HEADER_DLFCN + + +# _LT_TRY_DLOPEN_SELF (ACTION-IF-TRUE, ACTION-IF-TRUE-W-USCORE, +# ACTION-IF-FALSE, ACTION-IF-CROSS-COMPILING) +# ---------------------------------------------------------------- +m4_defun([_LT_TRY_DLOPEN_SELF], +[m4_require([_LT_HEADER_DLFCN])dnl +if test yes = "$cross_compiling"; then : + [$4] +else + lt_dlunknown=0; lt_dlno_uscore=1; lt_dlneed_uscore=2 + lt_status=$lt_dlunknown + cat > conftest.$ac_ext <<_LT_EOF +[#line $LINENO "configure" +#include "confdefs.h" + +#if HAVE_DLFCN_H +#include +#endif + +#include + +#ifdef RTLD_GLOBAL +# define LT_DLGLOBAL RTLD_GLOBAL +#else +# ifdef DL_GLOBAL +# define LT_DLGLOBAL DL_GLOBAL +# else +# define LT_DLGLOBAL 0 +# endif +#endif + +/* We may have to define LT_DLLAZY_OR_NOW in the command line if we + find out it does not work in some platform. */ +#ifndef LT_DLLAZY_OR_NOW +# ifdef RTLD_LAZY +# define LT_DLLAZY_OR_NOW RTLD_LAZY +# else +# ifdef DL_LAZY +# define LT_DLLAZY_OR_NOW DL_LAZY +# else +# ifdef RTLD_NOW +# define LT_DLLAZY_OR_NOW RTLD_NOW +# else +# ifdef DL_NOW +# define LT_DLLAZY_OR_NOW DL_NOW +# else +# define LT_DLLAZY_OR_NOW 0 +# endif +# endif +# endif +# endif +#endif + +/* When -fvisibility=hidden is used, assume the code has been annotated + correspondingly for the symbols needed. */ +#if defined __GNUC__ && (((__GNUC__ == 3) && (__GNUC_MINOR__ >= 3)) || (__GNUC__ > 3)) +int fnord () __attribute__((visibility("default"))); +#endif + +int fnord () { return 42; } +int main () +{ + void *self = dlopen (0, LT_DLGLOBAL|LT_DLLAZY_OR_NOW); + int status = $lt_dlunknown; + + if (self) + { + if (dlsym (self,"fnord")) status = $lt_dlno_uscore; + else + { + if (dlsym( self,"_fnord")) status = $lt_dlneed_uscore; + else puts (dlerror ()); + } + /* dlclose (self); */ + } + else + puts (dlerror ()); + + return status; +}] +_LT_EOF + if AC_TRY_EVAL(ac_link) && test -s "conftest$ac_exeext" 2>/dev/null; then + (./conftest; exit; ) >&AS_MESSAGE_LOG_FD 2>/dev/null + lt_status=$? + case x$lt_status in + x$lt_dlno_uscore) $1 ;; + x$lt_dlneed_uscore) $2 ;; + x$lt_dlunknown|x*) $3 ;; + esac + else : + # compilation failed + $3 + fi +fi +rm -fr conftest* +])# _LT_TRY_DLOPEN_SELF + + +# LT_SYS_DLOPEN_SELF +# ------------------ +AC_DEFUN([LT_SYS_DLOPEN_SELF], +[m4_require([_LT_HEADER_DLFCN])dnl +if test yes != "$enable_dlopen"; then + enable_dlopen=unknown + enable_dlopen_self=unknown + enable_dlopen_self_static=unknown +else + lt_cv_dlopen=no + lt_cv_dlopen_libs= + + case $host_os in + beos*) + lt_cv_dlopen=load_add_on + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ;; + + mingw* | pw32* | cegcc*) + lt_cv_dlopen=LoadLibrary + lt_cv_dlopen_libs= + ;; + + cygwin*) + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + ;; + + darwin*) + # if libdl is installed we need to link against it + AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl],[ + lt_cv_dlopen=dyld + lt_cv_dlopen_libs= + lt_cv_dlopen_self=yes + ]) + ;; + + tpf*) + # Don't try to run any link tests for TPF. We know it's impossible + # because TPF is a cross-compiler, and we know how we open DSOs. + lt_cv_dlopen=dlopen + lt_cv_dlopen_libs= + lt_cv_dlopen_self=no + ;; + + *) + AC_CHECK_FUNC([shl_load], + [lt_cv_dlopen=shl_load], + [AC_CHECK_LIB([dld], [shl_load], + [lt_cv_dlopen=shl_load lt_cv_dlopen_libs=-ldld], + [AC_CHECK_FUNC([dlopen], + [lt_cv_dlopen=dlopen], + [AC_CHECK_LIB([dl], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-ldl], + [AC_CHECK_LIB([svld], [dlopen], + [lt_cv_dlopen=dlopen lt_cv_dlopen_libs=-lsvld], + [AC_CHECK_LIB([dld], [dld_link], + [lt_cv_dlopen=dld_link lt_cv_dlopen_libs=-ldld]) + ]) + ]) + ]) + ]) + ]) + ;; + esac + + if test no = "$lt_cv_dlopen"; then + enable_dlopen=no + else + enable_dlopen=yes + fi + + case $lt_cv_dlopen in + dlopen) + save_CPPFLAGS=$CPPFLAGS + test yes = "$ac_cv_header_dlfcn_h" && CPPFLAGS="$CPPFLAGS -DHAVE_DLFCN_H" + + save_LDFLAGS=$LDFLAGS + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $export_dynamic_flag_spec\" + + save_LIBS=$LIBS + LIBS="$lt_cv_dlopen_libs $LIBS" + + AC_CACHE_CHECK([whether a program can dlopen itself], + lt_cv_dlopen_self, [dnl + _LT_TRY_DLOPEN_SELF( + lt_cv_dlopen_self=yes, lt_cv_dlopen_self=yes, + lt_cv_dlopen_self=no, lt_cv_dlopen_self=cross) + ]) + + if test yes = "$lt_cv_dlopen_self"; then + wl=$lt_prog_compiler_wl eval LDFLAGS=\"\$LDFLAGS $lt_prog_compiler_static\" + AC_CACHE_CHECK([whether a statically linked program can dlopen itself], + lt_cv_dlopen_self_static, [dnl + _LT_TRY_DLOPEN_SELF( + lt_cv_dlopen_self_static=yes, lt_cv_dlopen_self_static=yes, + lt_cv_dlopen_self_static=no, lt_cv_dlopen_self_static=cross) + ]) + fi + + CPPFLAGS=$save_CPPFLAGS + LDFLAGS=$save_LDFLAGS + LIBS=$save_LIBS + ;; + esac + + case $lt_cv_dlopen_self in + yes|no) enable_dlopen_self=$lt_cv_dlopen_self ;; + *) enable_dlopen_self=unknown ;; + esac + + case $lt_cv_dlopen_self_static in + yes|no) enable_dlopen_self_static=$lt_cv_dlopen_self_static ;; + *) enable_dlopen_self_static=unknown ;; + esac +fi +_LT_DECL([dlopen_support], [enable_dlopen], [0], + [Whether dlopen is supported]) +_LT_DECL([dlopen_self], [enable_dlopen_self], [0], + [Whether dlopen of programs is supported]) +_LT_DECL([dlopen_self_static], [enable_dlopen_self_static], [0], + [Whether dlopen of statically linked programs is supported]) +])# LT_SYS_DLOPEN_SELF + +# Old name: +AU_ALIAS([AC_LIBTOOL_DLOPEN_SELF], [LT_SYS_DLOPEN_SELF]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_DLOPEN_SELF], []) + + +# _LT_COMPILER_C_O([TAGNAME]) +# --------------------------- +# Check to see if options -c and -o are simultaneously supported by compiler. +# This macro does not hard code the compiler like AC_PROG_CC_C_O. +m4_defun([_LT_COMPILER_C_O], +[m4_require([_LT_DECL_SED])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_TAG_COMPILER])dnl +AC_CACHE_CHECK([if $compiler supports -c -o file.$ac_objext], + [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)], + [_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=no + $RM -r conftest 2>/dev/null + mkdir conftest + cd conftest + mkdir out + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + lt_compiler_flag="-o out/conftest2.$ac_objext" + # Insert the option either (1) after the last *FLAGS variable, or + # (2) before a word containing "conftest.", or (3) at the end. + # Note that $ac_compile itself does not contain backslashes and begins + # with a dollar sign (not a hyphen), so the echo should work correctly. + lt_compile=`echo "$ac_compile" | $SED \ + -e 's:.*FLAGS}\{0,1\} :&$lt_compiler_flag :; t' \ + -e 's: [[^ ]]*conftest\.: $lt_compiler_flag&:; t' \ + -e 's:$: $lt_compiler_flag:'` + (eval echo "\"\$as_me:$LINENO: $lt_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$lt_compile" 2>out/conftest.err) + ac_status=$? + cat out/conftest.err >&AS_MESSAGE_LOG_FD + echo "$as_me:$LINENO: \$? = $ac_status" >&AS_MESSAGE_LOG_FD + if (exit $ac_status) && test -s out/conftest2.$ac_objext + then + # The compiler can only warn and ignore the option if not recognized + # So say no if there are warnings + $ECHO "$_lt_compiler_boilerplate" | $SED '/^$/d' > out/conftest.exp + $SED '/^$/d; /^ *+/d' out/conftest.err >out/conftest.er2 + if test ! -s out/conftest.er2 || diff out/conftest.exp out/conftest.er2 >/dev/null; then + _LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + fi + fi + chmod u+w . 2>&AS_MESSAGE_LOG_FD + $RM conftest* + # SGI C++ compiler will create directory out/ii_files/ for + # template instantiation + test -d out/ii_files && $RM out/ii_files/* && rmdir out/ii_files + $RM out/* && rmdir out + cd .. + $RM -r conftest + $RM conftest* +]) +_LT_TAGDECL([compiler_c_o], [lt_cv_prog_compiler_c_o], [1], + [Does compiler simultaneously support -c and -o options?]) +])# _LT_COMPILER_C_O + + +# _LT_COMPILER_FILE_LOCKS([TAGNAME]) +# ---------------------------------- +# Check to see if we can do hard links to lock some files if needed +m4_defun([_LT_COMPILER_FILE_LOCKS], +[m4_require([_LT_ENABLE_LOCK])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +_LT_COMPILER_C_O([$1]) + +hard_links=nottested +if test no = "$_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)" && test no != "$need_locks"; then + # do not overwrite the value of need_locks provided by the user + AC_MSG_CHECKING([if we can lock with hard links]) + hard_links=yes + $RM conftest* + ln conftest.a conftest.b 2>/dev/null && hard_links=no + touch conftest.a + ln conftest.a conftest.b 2>&5 || hard_links=no + ln conftest.a conftest.b 2>/dev/null && hard_links=no + AC_MSG_RESULT([$hard_links]) + if test no = "$hard_links"; then + AC_MSG_WARN(['$CC' does not support '-c -o', so 'make -j' may be unsafe]) + need_locks=warn + fi +else + need_locks=no +fi +_LT_DECL([], [need_locks], [1], [Must we lock files when doing compilation?]) +])# _LT_COMPILER_FILE_LOCKS + + +# _LT_CHECK_OBJDIR +# ---------------- +m4_defun([_LT_CHECK_OBJDIR], +[AC_CACHE_CHECK([for objdir], [lt_cv_objdir], +[rm -f .libs 2>/dev/null +mkdir .libs 2>/dev/null +if test -d .libs; then + lt_cv_objdir=.libs +else + # MS-DOS does not allow filenames that begin with a dot. + lt_cv_objdir=_libs +fi +rmdir .libs 2>/dev/null]) +objdir=$lt_cv_objdir +_LT_DECL([], [objdir], [0], + [The name of the directory that contains temporary libtool files])dnl +m4_pattern_allow([LT_OBJDIR])dnl +AC_DEFINE_UNQUOTED([LT_OBJDIR], "$lt_cv_objdir/", + [Define to the sub-directory where libtool stores uninstalled libraries.]) +])# _LT_CHECK_OBJDIR + + +# _LT_LINKER_HARDCODE_LIBPATH([TAGNAME]) +# -------------------------------------- +# Check hardcoding attributes. +m4_defun([_LT_LINKER_HARDCODE_LIBPATH], +[AC_MSG_CHECKING([how to hardcode library paths into programs]) +_LT_TAGVAR(hardcode_action, $1)= +if test -n "$_LT_TAGVAR(hardcode_libdir_flag_spec, $1)" || + test -n "$_LT_TAGVAR(runpath_var, $1)" || + test yes = "$_LT_TAGVAR(hardcode_automatic, $1)"; then + + # We can hardcode non-existent directories. + if test no != "$_LT_TAGVAR(hardcode_direct, $1)" && + # If the only mechanism to avoid hardcoding is shlibpath_var, we + # have to relink, otherwise we might link with an installed library + # when we should be linking with a yet-to-be-installed one + ## test no != "$_LT_TAGVAR(hardcode_shlibpath_var, $1)" && + test no != "$_LT_TAGVAR(hardcode_minus_L, $1)"; then + # Linking always hardcodes the temporary library directory. + _LT_TAGVAR(hardcode_action, $1)=relink + else + # We can link without hardcoding, and we can hardcode nonexisting dirs. + _LT_TAGVAR(hardcode_action, $1)=immediate + fi +else + # We cannot hardcode anything, or else we can only hardcode existing + # directories. + _LT_TAGVAR(hardcode_action, $1)=unsupported +fi +AC_MSG_RESULT([$_LT_TAGVAR(hardcode_action, $1)]) + +if test relink = "$_LT_TAGVAR(hardcode_action, $1)" || + test yes = "$_LT_TAGVAR(inherit_rpath, $1)"; then + # Fast installation is not supported + enable_fast_install=no +elif test yes = "$shlibpath_overrides_runpath" || + test no = "$enable_shared"; then + # Fast installation is not necessary + enable_fast_install=needless +fi +_LT_TAGDECL([], [hardcode_action], [0], + [How to hardcode a shared library path into an executable]) +])# _LT_LINKER_HARDCODE_LIBPATH + + +# _LT_CMD_STRIPLIB +# ---------------- +m4_defun([_LT_CMD_STRIPLIB], +[m4_require([_LT_DECL_EGREP]) +striplib= +old_striplib= +AC_MSG_CHECKING([whether stripping libraries is possible]) +if test -n "$STRIP" && $STRIP -V 2>&1 | $GREP "GNU strip" >/dev/null; then + test -z "$old_striplib" && old_striplib="$STRIP --strip-debug" + test -z "$striplib" && striplib="$STRIP --strip-unneeded" + AC_MSG_RESULT([yes]) +else +# FIXME - insert some real tests, host_os isn't really good enough + case $host_os in + darwin*) + if test -n "$STRIP"; then + striplib="$STRIP -x" + old_striplib="$STRIP -S" + AC_MSG_RESULT([yes]) + else + AC_MSG_RESULT([no]) + fi + ;; + *) + AC_MSG_RESULT([no]) + ;; + esac +fi +_LT_DECL([], [old_striplib], [1], [Commands to strip libraries]) +_LT_DECL([], [striplib], [1]) +])# _LT_CMD_STRIPLIB + + +# _LT_PREPARE_MUNGE_PATH_LIST +# --------------------------- +# Make sure func_munge_path_list() is defined correctly. +m4_defun([_LT_PREPARE_MUNGE_PATH_LIST], +[[# func_munge_path_list VARIABLE PATH +# ----------------------------------- +# VARIABLE is name of variable containing _space_ separated list of +# directories to be munged by the contents of PATH, which is string +# having a format: +# "DIR[:DIR]:" +# string "DIR[ DIR]" will be prepended to VARIABLE +# ":DIR[:DIR]" +# string "DIR[ DIR]" will be appended to VARIABLE +# "DIRP[:DIRP]::[DIRA:]DIRA" +# string "DIRP[ DIRP]" will be prepended to VARIABLE and string +# "DIRA[ DIRA]" will be appended to VARIABLE +# "DIR[:DIR]" +# VARIABLE will be replaced by "DIR[ DIR]" +func_munge_path_list () +{ + case x@S|@2 in + x) + ;; + *:) + eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'` \@S|@@S|@1\" + ;; + x:*) + eval @S|@1=\"\@S|@@S|@1 `$ECHO @S|@2 | $SED 's/:/ /g'`\" + ;; + *::*) + eval @S|@1=\"\@S|@@S|@1\ `$ECHO @S|@2 | $SED -e 's/.*:://' -e 's/:/ /g'`\" + eval @S|@1=\"`$ECHO @S|@2 | $SED -e 's/::.*//' -e 's/:/ /g'`\ \@S|@@S|@1\" + ;; + *) + eval @S|@1=\"`$ECHO @S|@2 | $SED 's/:/ /g'`\" + ;; + esac +} +]])# _LT_PREPARE_PATH_LIST + + +# _LT_SYS_DYNAMIC_LINKER([TAG]) +# ----------------------------- +# PORTME Fill in your ld.so characteristics +m4_defun([_LT_SYS_DYNAMIC_LINKER], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_OBJDUMP])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_CHECK_SHELL_FEATURES])dnl +m4_require([_LT_PREPARE_MUNGE_PATH_LIST])dnl +AC_MSG_CHECKING([dynamic linker characteristics]) +m4_if([$1], + [], [ +if test yes = "$GCC"; then + case $host_os in + darwin*) lt_awk_arg='/^libraries:/,/LR/' ;; + *) lt_awk_arg='/^libraries:/' ;; + esac + case $host_os in + mingw* | cegcc*) lt_sed_strip_eq='s|=\([[A-Za-z]]:\)|\1|g' ;; + *) lt_sed_strip_eq='s|=/|/|g' ;; + esac + lt_search_path_spec=`$CC -print-search-dirs | awk $lt_awk_arg | $SED -e "s/^libraries://" -e $lt_sed_strip_eq` + case $lt_search_path_spec in + *\;*) + # if the path contains ";" then we assume it to be the separator + # otherwise default to the standard path separator (i.e. ":") - it is + # assumed that no part of a normal pathname contains ";" but that should + # okay in the real world where ";" in dirpaths is itself problematic. + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED 's/;/ /g'` + ;; + *) + lt_search_path_spec=`$ECHO "$lt_search_path_spec" | $SED "s/$PATH_SEPARATOR/ /g"` + ;; + esac + # Ok, now we have the path, separated by spaces, we can step through it + # and add multilib dir if necessary... + lt_tmp_lt_search_path_spec= + lt_multi_os_dir=/`$CC $CPPFLAGS $CFLAGS $LDFLAGS -print-multi-os-directory 2>/dev/null` + # ...but if some path component already ends with the multilib dir we assume + # that all is fine and trust -print-search-dirs as is (GCC 4.2? or newer). + case "$lt_multi_os_dir; $lt_search_path_spec " in + "/; "* | "/.; "* | "/./; "* | *"$lt_multi_os_dir "* | *"$lt_multi_os_dir/ "*) + lt_multi_os_dir= + ;; + esac + for lt_sys_path in $lt_search_path_spec; do + if test -d "$lt_sys_path$lt_multi_os_dir"; then + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path$lt_multi_os_dir" + elif test -n "$lt_multi_os_dir"; then + test -d "$lt_sys_path" && \ + lt_tmp_lt_search_path_spec="$lt_tmp_lt_search_path_spec $lt_sys_path" + fi + done + lt_search_path_spec=`$ECHO "$lt_tmp_lt_search_path_spec" | awk ' +BEGIN {RS = " "; FS = "/|\n";} { + lt_foo = ""; + lt_count = 0; + for (lt_i = NF; lt_i > 0; lt_i--) { + if ($lt_i != "" && $lt_i != ".") { + if ($lt_i == "..") { + lt_count++; + } else { + if (lt_count == 0) { + lt_foo = "/" $lt_i lt_foo; + } else { + lt_count--; + } + } + } + } + if (lt_foo != "") { lt_freq[[lt_foo]]++; } + if (lt_freq[[lt_foo]] == 1) { print lt_foo; } +}'` + # AWK program above erroneously prepends '/' to C:/dos/paths + # for these hosts. + case $host_os in + mingw* | cegcc*) lt_search_path_spec=`$ECHO "$lt_search_path_spec" |\ + $SED 's|/\([[A-Za-z]]:\)|\1|g'` ;; + esac + sys_lib_search_path_spec=`$ECHO "$lt_search_path_spec" | $lt_NL2SP` +else + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" +fi]) +library_names_spec= +libname_spec='lib$name' +soname_spec= +shrext_cmds=.so +postinstall_cmds= +postuninstall_cmds= +finish_cmds= +finish_eval= +shlibpath_var= +shlibpath_overrides_runpath=unknown +version_type=none +dynamic_linker="$host_os ld.so" +sys_lib_dlsearch_path_spec="/lib /usr/lib" +need_lib_prefix=unknown +hardcode_into_libs=no + +# when you set need_version to no, make sure it does not cause -set_version +# flags to be left without arguments +need_version=unknown + +AC_ARG_VAR([LT_SYS_LIBRARY_PATH], +[User-defined run-time library search path.]) + +case $host_os in +aix3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname.a' + shlibpath_var=LIBPATH + + # AIX 3 has no versioning support, so we append a major version to the name. + soname_spec='$libname$release$shared_ext$major' + ;; + +aix[[4-9]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + hardcode_into_libs=yes + if test ia64 = "$host_cpu"; then + # AIX 5 supports IA64 + library_names_spec='$libname$release$shared_ext$major $libname$release$shared_ext$versuffix $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + else + # With GCC up to 2.95.x, collect2 would create an import file + # for dependence libraries. The import file would start with + # the line '#! .'. This would cause the generated library to + # depend on '.', always an invalid library. This was fixed in + # development snapshots of GCC prior to 3.0. + case $host_os in + aix4 | aix4.[[01]] | aix4.[[01]].*) + if { echo '#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 97)' + echo ' yes ' + echo '#endif'; } | $CC -E - | $GREP yes > /dev/null; then + : + else + can_build_shared=no + fi + ;; + esac + # Using Import Files as archive members, it is possible to support + # filename-based versioning of shared library archives on AIX. While + # this would work for both with and without runtime linking, it will + # prevent static linking of such archives. So we do filename-based + # shared library versioning with .so extension only, which is used + # when both runtime linking and shared linking is enabled. + # Unfortunately, runtime linking may impact performance, so we do + # not want this to be the default eventually. Also, we use the + # versioned .so libs for executables only if there is the -brtl + # linker flag in LDFLAGS as well, or --with-aix-soname=svr4 only. + # To allow for filename-based versioning support, we need to create + # libNAME.so.V as an archive file, containing: + # *) an Import File, referring to the versioned filename of the + # archive as well as the shared archive member, telling the + # bitwidth (32 or 64) of that shared object, and providing the + # list of exported symbols of that shared object, eventually + # decorated with the 'weak' keyword + # *) the shared object with the F_LOADONLY flag set, to really avoid + # it being seen by the linker. + # At run time we better use the real file rather than another symlink, + # but for link time we create the symlink libNAME.so -> libNAME.so.V + + case $with_aix_soname,$aix_use_runtimelinking in + # AIX (on Power*) has no versioning support, so currently we cannot hardcode correct + # soname into executable. Probably we can add versioning support to + # collect2, so additional links can be useful in future. + aix,yes) # traditional libtool + dynamic_linker='AIX unversionable lib.so' + # If using run time linking (on AIX 4.2 or later) use lib.so + # instead of lib.a to let people know that these are not + # typical AIX shared libraries. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + aix,no) # traditional AIX only + dynamic_linker='AIX lib.a[(]lib.so.V[)]' + # We preserve .a as extension for shared libraries through AIX4.2 + # and later when we are not doing run time linking. + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + ;; + svr4,*) # full svr4 only + dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)]" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,yes) # both, prefer svr4 + dynamic_linker="AIX lib.so.V[(]$shared_archive_member_spec.o[)], lib.a[(]lib.so.V[)]" + library_names_spec='$libname$release$shared_ext$major $libname$shared_ext' + # unpreferred sharedlib libNAME.a needs extra handling + postinstall_cmds='test -n "$linkname" || linkname="$realname"~func_stripname "" ".so" "$linkname"~$install_shared_prog "$dir/$func_stripname_result.$libext" "$destdir/$func_stripname_result.$libext"~test -z "$tstripme" || test -z "$striplib" || $striplib "$destdir/$func_stripname_result.$libext"' + postuninstall_cmds='for n in $library_names $old_library; do :; done~func_stripname "" ".so" "$n"~test "$func_stripname_result" = "$n" || func_append rmfiles " $odir/$func_stripname_result.$libext"' + # We do not specify a path in Import Files, so LIBPATH fires. + shlibpath_overrides_runpath=yes + ;; + *,no) # both, prefer aix + dynamic_linker="AIX lib.a[(]lib.so.V[)], lib.so.V[(]$shared_archive_member_spec.o[)]" + library_names_spec='$libname$release.a $libname.a' + soname_spec='$libname$release$shared_ext$major' + # unpreferred sharedlib libNAME.so.V and symlink libNAME.so need extra handling + postinstall_cmds='test -z "$dlname" || $install_shared_prog $dir/$dlname $destdir/$dlname~test -z "$tstripme" || test -z "$striplib" || $striplib $destdir/$dlname~test -n "$linkname" || linkname=$realname~func_stripname "" ".a" "$linkname"~(cd "$destdir" && $LN_S -f $dlname $func_stripname_result.so)' + postuninstall_cmds='test -z "$dlname" || func_append rmfiles " $odir/$dlname"~for n in $old_library $library_names; do :; done~func_stripname "" ".a" "$n"~func_append rmfiles " $odir/$func_stripname_result.so"' + ;; + esac + shlibpath_var=LIBPATH + fi + ;; + +amigaos*) + case $host_cpu in + powerpc) + # Since July 2007 AmigaOS4 officially supports .so libraries. + # When compiling the executable, add -use-dynld -Lsobjs: to the compileline. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + ;; + m68k) + library_names_spec='$libname.ixlibrary $libname.a' + # Create ${libname}_ixlibrary.a entries in /sys/libs. + finish_eval='for lib in `ls $libdir/*.ixlibrary 2>/dev/null`; do libname=`func_echo_all "$lib" | $SED '\''s%^.*/\([[^/]]*\)\.ixlibrary$%\1%'\''`; $RM /sys/libs/${libname}_ixlibrary.a; $show "cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a"; cd /sys/libs && $LN_S $lib ${libname}_ixlibrary.a || exit 1; done' + ;; + esac + ;; + +beos*) + library_names_spec='$libname$shared_ext' + dynamic_linker="$host_os ld.so" + shlibpath_var=LIBRARY_PATH + ;; + +bsdi[[45]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/shlib /usr/lib /usr/X11/lib /usr/contrib/lib /lib /usr/local/lib" + sys_lib_dlsearch_path_spec="/shlib /usr/lib /usr/local/lib" + # the default ld.so.conf also contains /usr/contrib/lib and + # /usr/X11R6/lib (/usr/X11 is a link to /usr/X11R6), but let us allow + # libtool to hard-code these into programs + ;; + +cygwin* | mingw* | pw32* | cegcc*) + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + + case $GCC,$cc_basename in + yes,*) + # gcc + library_names_spec='$libname.dll.a' + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + + case $host_os in + cygwin*) + # Cygwin DLLs use 'cyg' prefix rather than 'lib' + soname_spec='`echo $libname | sed -e 's/^lib/cyg/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' +m4_if([$1], [],[ + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/lib/w32api"]) + ;; + mingw* | cegcc*) + # MinGW DLLs use traditional 'lib' prefix + soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + ;; + pw32*) + # pw32 DLLs use 'pw' prefix rather than 'lib' + library_names_spec='`echo $libname | sed -e 's/^lib/pw/'``echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + ;; + esac + dynamic_linker='Win32 ld.exe' + ;; + + *,cl*) + # Native MSVC + libname_spec='$name' + soname_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext' + library_names_spec='$libname.dll.lib' + + case $build_os in + mingw*) + sys_lib_search_path_spec= + lt_save_ifs=$IFS + IFS=';' + for lt_path in $LIB + do + IFS=$lt_save_ifs + # Let DOS variable expansion print the short 8.3 style file name. + lt_path=`cd "$lt_path" 2>/dev/null && cmd //C "for %i in (".") do @echo %~si"` + sys_lib_search_path_spec="$sys_lib_search_path_spec $lt_path" + done + IFS=$lt_save_ifs + # Convert to MSYS style. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | sed -e 's|\\\\|/|g' -e 's| \\([[a-zA-Z]]\\):| /\\1|g' -e 's|^ ||'` + ;; + cygwin*) + # Convert to unix form, then to dos form, then back to unix form + # but this time dos style (no spaces!) so that the unix form looks + # like /cygdrive/c/PROGRA~1:/cygdr... + sys_lib_search_path_spec=`cygpath --path --unix "$LIB"` + sys_lib_search_path_spec=`cygpath --path --dos "$sys_lib_search_path_spec" 2>/dev/null` + sys_lib_search_path_spec=`cygpath --path --unix "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + ;; + *) + sys_lib_search_path_spec=$LIB + if $ECHO "$sys_lib_search_path_spec" | [$GREP ';[c-zC-Z]:/' >/dev/null]; then + # It is most probably a Windows format PATH. + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e 's/;/ /g'` + else + sys_lib_search_path_spec=`$ECHO "$sys_lib_search_path_spec" | $SED -e "s/$PATH_SEPARATOR/ /g"` + fi + # FIXME: find the short name or the path components, as spaces are + # common. (e.g. "Program Files" -> "PROGRA~1") + ;; + esac + + # DLL is installed to $(libdir)/../bin by postinstall_cmds + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; echo \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; echo \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + shlibpath_overrides_runpath=yes + dynamic_linker='Win32 link.exe' + ;; + + *) + # Assume MSVC wrapper + library_names_spec='$libname`echo $release | $SED -e 's/[[.]]/-/g'`$versuffix$shared_ext $libname.lib' + dynamic_linker='Win32 ld.exe' + ;; + esac + # FIXME: first we should search . and the directory the executable is in + shlibpath_var=PATH + ;; + +darwin* | rhapsody*) + dynamic_linker="$host_os dyld" + version_type=darwin + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$major$shared_ext $libname$shared_ext' + soname_spec='$libname$release$major$shared_ext' + shlibpath_overrides_runpath=yes + shlibpath_var=DYLD_LIBRARY_PATH + shrext_cmds='`test .$module = .yes && echo .so || echo .dylib`' +m4_if([$1], [],[ + sys_lib_search_path_spec="$sys_lib_search_path_spec /usr/local/lib"]) + sys_lib_dlsearch_path_spec='/usr/local/lib /lib /usr/lib' + ;; + +dgux*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +freebsd* | dragonfly*) + # DragonFly does not have aout. When/if they implement a new + # versioning mechanism, adjust this. + if test -x /usr/bin/objformat; then + objformat=`/usr/bin/objformat` + else + case $host_os in + freebsd[[23]].*) objformat=aout ;; + *) objformat=elf ;; + esac + fi + version_type=freebsd-$objformat + case $version_type in + freebsd-elf*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + need_version=no + need_lib_prefix=no + ;; + freebsd-*) + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + need_version=yes + ;; + esac + shlibpath_var=LD_LIBRARY_PATH + case $host_os in + freebsd2.*) + shlibpath_overrides_runpath=yes + ;; + freebsd3.[[01]]* | freebsdelf3.[[01]]*) + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + freebsd3.[[2-9]]* | freebsdelf3.[[2-9]]* | \ + freebsd4.[[0-5]] | freebsdelf4.[[0-5]] | freebsd4.1.1 | freebsdelf4.1.1) + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + *) # from 4.6 on, and DragonFly + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + esac + ;; + +haiku*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + dynamic_linker="$host_os runtime_loader" + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LIBRARY_PATH + shlibpath_overrides_runpath=no + sys_lib_dlsearch_path_spec='/boot/home/config/lib /boot/common/lib /boot/system/lib' + hardcode_into_libs=yes + ;; + +hpux9* | hpux10* | hpux11*) + # Give a soname corresponding to the major version so that dld.sl refuses to + # link against other versions. + version_type=sunos + need_lib_prefix=no + need_version=no + case $host_cpu in + ia64*) + shrext_cmds='.so' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.so" + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + if test 32 = "$HPUX_IA64_MODE"; then + sys_lib_search_path_spec="/usr/lib/hpux32 /usr/local/lib/hpux32 /usr/local/lib" + sys_lib_dlsearch_path_spec=/usr/lib/hpux32 + else + sys_lib_search_path_spec="/usr/lib/hpux64 /usr/local/lib/hpux64" + sys_lib_dlsearch_path_spec=/usr/lib/hpux64 + fi + ;; + hppa*64*) + shrext_cmds='.sl' + hardcode_into_libs=yes + dynamic_linker="$host_os dld.sl" + shlibpath_var=LD_LIBRARY_PATH # How should we handle SHLIB_PATH + shlibpath_overrides_runpath=yes # Unless +noenvvar is specified. + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + sys_lib_search_path_spec="/usr/lib/pa20_64 /usr/ccs/lib/pa20_64" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + *) + shrext_cmds='.sl' + dynamic_linker="$host_os dld.sl" + shlibpath_var=SHLIB_PATH + shlibpath_overrides_runpath=no # +s is required to enable SHLIB_PATH + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + ;; + esac + # HP-UX runs *really* slowly unless shared libraries are mode 555, ... + postinstall_cmds='chmod 555 $lib' + # or fails outright, so override atomically: + install_override_mode=555 + ;; + +interix[[3-9]]*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='Interix 3.x ld.so.1 (PE, like ELF)' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +irix5* | irix6* | nonstopux*) + case $host_os in + nonstopux*) version_type=nonstopux ;; + *) + if test yes = "$lt_cv_prog_gnu_ld"; then + version_type=linux # correct to gnu/linux during the next big refactor + else + version_type=irix + fi ;; + esac + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$release$shared_ext $libname$shared_ext' + case $host_os in + irix5* | nonstopux*) + libsuff= shlibsuff= + ;; + *) + case $LD in # libtool.m4 will add one of these switches to LD + *-32|*"-32 "|*-melf32bsmip|*"-melf32bsmip ") + libsuff= shlibsuff= libmagic=32-bit;; + *-n32|*"-n32 "|*-melf32bmipn32|*"-melf32bmipn32 ") + libsuff=32 shlibsuff=N32 libmagic=N32;; + *-64|*"-64 "|*-melf64bmip|*"-melf64bmip ") + libsuff=64 shlibsuff=64 libmagic=64-bit;; + *) libsuff= shlibsuff= libmagic=never-match;; + esac + ;; + esac + shlibpath_var=LD_LIBRARY${shlibsuff}_PATH + shlibpath_overrides_runpath=no + sys_lib_search_path_spec="/usr/lib$libsuff /lib$libsuff /usr/local/lib$libsuff" + sys_lib_dlsearch_path_spec="/usr/lib$libsuff /lib$libsuff" + hardcode_into_libs=yes + ;; + +# No shared lib support for Linux oldld, aout, or coff. +linux*oldld* | linux*aout* | linux*coff*) + dynamic_linker=no + ;; + +linux*android*) + version_type=none # Android doesn't support versioned libraries. + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext' + soname_spec='$libname$release$shared_ext' + finish_cmds= + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + dynamic_linker='Android linker' + # Don't embed -rpath directories since the linker doesn't support them. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -n $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + + # Some binutils ld are patched to set DT_RUNPATH + AC_CACHE_VAL([lt_cv_shlibpath_overrides_runpath], + [lt_cv_shlibpath_overrides_runpath=no + save_LDFLAGS=$LDFLAGS + save_libdir=$libdir + eval "libdir=/foo; wl=\"$_LT_TAGVAR(lt_prog_compiler_wl, $1)\"; \ + LDFLAGS=\"\$LDFLAGS $_LT_TAGVAR(hardcode_libdir_flag_spec, $1)\"" + AC_LINK_IFELSE([AC_LANG_PROGRAM([],[])], + [AS_IF([ ($OBJDUMP -p conftest$ac_exeext) 2>/dev/null | grep "RUNPATH.*$libdir" >/dev/null], + [lt_cv_shlibpath_overrides_runpath=yes])]) + LDFLAGS=$save_LDFLAGS + libdir=$save_libdir + ]) + shlibpath_overrides_runpath=$lt_cv_shlibpath_overrides_runpath + + # This implies no fast_install, which is unacceptable. + # Some rework will be needed to allow for fast_install + # before this can be enabled. + hardcode_into_libs=yes + + # Ideally, we could use ldconfig to report *all* directores which are + # searched for libraries, however this is still not possible. Aside from not + # being certain /sbin/ldconfig is available, command + # 'ldconfig -N -X -v | grep ^/' on 64bit Fedora does not report /usr/lib64, + # even though it is searched at run-time. Try to do the best guess by + # appending ld.so.conf contents (and includes) to the search path. + if test -f /etc/ld.so.conf; then + lt_ld_extra=`awk '/^include / { system(sprintf("cd /etc; cat %s 2>/dev/null", \[$]2)); skip = 1; } { if (!skip) print \[$]0; skip = 0; }' < /etc/ld.so.conf | $SED -e 's/#.*//;/^[ ]*hwcap[ ]/d;s/[:, ]/ /g;s/=[^=]*$//;s/=[^= ]* / /g;s/"//g;/^$/d' | tr '\n' ' '` + sys_lib_dlsearch_path_spec="/lib /usr/lib $lt_ld_extra" + fi + + # We used to test for /lib/ld.so.1 and disable shared libraries on + # powerpc, because MkLinux only supported shared libraries with the + # GNU dynamic linker. Since this was broken with cross compilers, + # most powerpc-linux boxes support dynamic linking these days and + # people can always --disable-shared, the test was removed, and we + # assume the GNU/Linux dynamic linker is in use. + dynamic_linker='GNU/Linux ld.so' + ;; + +netbsdelf*-gnu) + version_type=linux + need_lib_prefix=no + need_version=no + library_names_spec='${libname}${release}${shared_ext}$versuffix ${libname}${release}${shared_ext}$major ${libname}${shared_ext}' + soname_spec='${libname}${release}${shared_ext}$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='NetBSD ld.elf_so' + ;; + +netbsd*) + version_type=sunos + need_lib_prefix=no + need_version=no + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + dynamic_linker='NetBSD (a.out) ld.so' + else + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + dynamic_linker='NetBSD ld.elf_so' + fi + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + ;; + +newsos6) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +*nto* | *qnx*) + version_type=qnx + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + dynamic_linker='ldqnx.so' + ;; + +openbsd* | bitrig*) + version_type=sunos + sys_lib_dlsearch_path_spec=/usr/lib + need_lib_prefix=no + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + need_version=no + else + need_version=yes + fi + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/sbin" ldconfig -m $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + ;; + +os2*) + libname_spec='$name' + version_type=windows + shrext_cmds=.dll + need_version=no + need_lib_prefix=no + # OS/2 can only load a DLL with a base name of 8 characters or less. + soname_spec='`test -n "$os2dllname" && libname="$os2dllname"; + v=$($ECHO $release$versuffix | tr -d .-); + n=$($ECHO $libname | cut -b -$((8 - ${#v})) | tr . _); + $ECHO $n$v`$shared_ext' + library_names_spec='${libname}_dll.$libext' + dynamic_linker='OS/2 ld.exe' + shlibpath_var=BEGINLIBPATH + sys_lib_search_path_spec="/lib /usr/lib /usr/local/lib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + postinstall_cmds='base_file=`basename \$file`~ + dlpath=`$SHELL 2>&1 -c '\''. $dir/'\''\$base_file'\''i; $ECHO \$dlname'\''`~ + dldir=$destdir/`dirname \$dlpath`~ + test -d \$dldir || mkdir -p \$dldir~ + $install_prog $dir/$dlname \$dldir/$dlname~ + chmod a+x \$dldir/$dlname~ + if test -n '\''$stripme'\'' && test -n '\''$striplib'\''; then + eval '\''$striplib \$dldir/$dlname'\'' || exit \$?; + fi' + postuninstall_cmds='dldll=`$SHELL 2>&1 -c '\''. $file; $ECHO \$dlname'\''`~ + dlpath=$dir/\$dldll~ + $RM \$dlpath' + ;; + +osf3* | osf4* | osf5*) + version_type=osf + need_lib_prefix=no + need_version=no + soname_spec='$libname$release$shared_ext$major' + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + sys_lib_search_path_spec="/usr/shlib /usr/ccs/lib /usr/lib/cmplrs/cc /usr/lib /usr/local/lib /var/shlib" + sys_lib_dlsearch_path_spec=$sys_lib_search_path_spec + ;; + +rdos*) + dynamic_linker=no + ;; + +solaris*) + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + # ldd complains unless libraries are executable + postinstall_cmds='chmod +x $lib' + ;; + +sunos4*) + version_type=sunos + library_names_spec='$libname$release$shared_ext$versuffix $libname$shared_ext$versuffix' + finish_cmds='PATH="\$PATH:/usr/etc" ldconfig $libdir' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + if test yes = "$with_gnu_ld"; then + need_lib_prefix=no + fi + need_version=yes + ;; + +sysv4 | sysv4.3*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + case $host_vendor in + sni) + shlibpath_overrides_runpath=no + need_lib_prefix=no + runpath_var=LD_RUN_PATH + ;; + siemens) + need_lib_prefix=no + ;; + motorola) + need_lib_prefix=no + need_version=no + shlibpath_overrides_runpath=no + sys_lib_search_path_spec='/lib /usr/lib /usr/ccs/lib' + ;; + esac + ;; + +sysv4*MP*) + if test -d /usr/nec; then + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$shared_ext.$versuffix $libname$shared_ext.$major $libname$shared_ext' + soname_spec='$libname$shared_ext.$major' + shlibpath_var=LD_LIBRARY_PATH + fi + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + version_type=sco + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=yes + hardcode_into_libs=yes + if test yes = "$with_gnu_ld"; then + sys_lib_search_path_spec='/usr/local/lib /usr/gnu/lib /usr/ccs/lib /usr/lib /lib' + else + sys_lib_search_path_spec='/usr/ccs/lib /usr/lib' + case $host_os in + sco3.2v5*) + sys_lib_search_path_spec="$sys_lib_search_path_spec /lib" + ;; + esac + fi + sys_lib_dlsearch_path_spec='/usr/lib' + ;; + +tpf*) + # TPF is a cross-target only. Preferred cross-host = GNU/Linux. + version_type=linux # correct to gnu/linux during the next big refactor + need_lib_prefix=no + need_version=no + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + shlibpath_var=LD_LIBRARY_PATH + shlibpath_overrides_runpath=no + hardcode_into_libs=yes + ;; + +uts4*) + version_type=linux # correct to gnu/linux during the next big refactor + library_names_spec='$libname$release$shared_ext$versuffix $libname$release$shared_ext$major $libname$shared_ext' + soname_spec='$libname$release$shared_ext$major' + shlibpath_var=LD_LIBRARY_PATH + ;; + +*) + dynamic_linker=no + ;; +esac +AC_MSG_RESULT([$dynamic_linker]) +test no = "$dynamic_linker" && can_build_shared=no + +variables_saved_for_relink="PATH $shlibpath_var $runpath_var" +if test yes = "$GCC"; then + variables_saved_for_relink="$variables_saved_for_relink GCC_EXEC_PREFIX COMPILER_PATH LIBRARY_PATH" +fi + +if test set = "${lt_cv_sys_lib_search_path_spec+set}"; then + sys_lib_search_path_spec=$lt_cv_sys_lib_search_path_spec +fi + +if test set = "${lt_cv_sys_lib_dlsearch_path_spec+set}"; then + sys_lib_dlsearch_path_spec=$lt_cv_sys_lib_dlsearch_path_spec +fi + +# remember unaugmented sys_lib_dlsearch_path content for libtool script decls... +configure_time_dlsearch_path=$sys_lib_dlsearch_path_spec + +# ... but it needs LT_SYS_LIBRARY_PATH munging for other configure-time code +func_munge_path_list sys_lib_dlsearch_path_spec "$LT_SYS_LIBRARY_PATH" + +# to be used as default LT_SYS_LIBRARY_PATH value in generated libtool +configure_time_lt_sys_library_path=$LT_SYS_LIBRARY_PATH + +_LT_DECL([], [variables_saved_for_relink], [1], + [Variables whose values should be saved in libtool wrapper scripts and + restored at link time]) +_LT_DECL([], [need_lib_prefix], [0], + [Do we need the "lib" prefix for modules?]) +_LT_DECL([], [need_version], [0], [Do we need a version for libraries?]) +_LT_DECL([], [version_type], [0], [Library versioning type]) +_LT_DECL([], [runpath_var], [0], [Shared library runtime path variable]) +_LT_DECL([], [shlibpath_var], [0],[Shared library path variable]) +_LT_DECL([], [shlibpath_overrides_runpath], [0], + [Is shlibpath searched before the hard-coded library search path?]) +_LT_DECL([], [libname_spec], [1], [Format of library name prefix]) +_LT_DECL([], [library_names_spec], [1], + [[List of archive names. First name is the real one, the rest are links. + The last name is the one that the linker finds with -lNAME]]) +_LT_DECL([], [soname_spec], [1], + [[The coded name of the library, if different from the real name]]) +_LT_DECL([], [install_override_mode], [1], + [Permission mode override for installation of shared libraries]) +_LT_DECL([], [postinstall_cmds], [2], + [Command to use after installation of a shared archive]) +_LT_DECL([], [postuninstall_cmds], [2], + [Command to use after uninstallation of a shared archive]) +_LT_DECL([], [finish_cmds], [2], + [Commands used to finish a libtool library installation in a directory]) +_LT_DECL([], [finish_eval], [1], + [[As "finish_cmds", except a single script fragment to be evaled but + not shown]]) +_LT_DECL([], [hardcode_into_libs], [0], + [Whether we should hardcode library paths into libraries]) +_LT_DECL([], [sys_lib_search_path_spec], [2], + [Compile-time system search path for libraries]) +_LT_DECL([sys_lib_dlsearch_path_spec], [configure_time_dlsearch_path], [2], + [Detected run-time system search path for libraries]) +_LT_DECL([], [configure_time_lt_sys_library_path], [2], + [Explicit LT_SYS_LIBRARY_PATH set during ./configure time]) +])# _LT_SYS_DYNAMIC_LINKER + + +# _LT_PATH_TOOL_PREFIX(TOOL) +# -------------------------- +# find a file program that can recognize shared library +AC_DEFUN([_LT_PATH_TOOL_PREFIX], +[m4_require([_LT_DECL_EGREP])dnl +AC_MSG_CHECKING([for $1]) +AC_CACHE_VAL(lt_cv_path_MAGIC_CMD, +[case $MAGIC_CMD in +[[\\/*] | ?:[\\/]*]) + lt_cv_path_MAGIC_CMD=$MAGIC_CMD # Let the user override the test with a path. + ;; +*) + lt_save_MAGIC_CMD=$MAGIC_CMD + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR +dnl $ac_dummy forces splitting on constant user-supplied paths. +dnl POSIX.2 word splitting is done only on the output of word expansions, +dnl not every word. This closes a longstanding sh security hole. + ac_dummy="m4_if([$2], , $PATH, [$2])" + for ac_dir in $ac_dummy; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$1"; then + lt_cv_path_MAGIC_CMD=$ac_dir/"$1" + if test -n "$file_magic_test_file"; then + case $deplibs_check_method in + "file_magic "*) + file_magic_regex=`expr "$deplibs_check_method" : "file_magic \(.*\)"` + MAGIC_CMD=$lt_cv_path_MAGIC_CMD + if eval $file_magic_cmd \$file_magic_test_file 2> /dev/null | + $EGREP "$file_magic_regex" > /dev/null; then + : + else + cat <<_LT_EOF 1>&2 + +*** Warning: the command libtool uses to detect shared libraries, +*** $file_magic_cmd, produces output that libtool cannot recognize. +*** The result is that libtool may fail to recognize shared libraries +*** as such. This will affect the creation of libtool libraries that +*** depend on shared libraries, but programs linked with such libtool +*** libraries will work regardless of this problem. Nevertheless, you +*** may want to report the problem to your system manager and/or to +*** bug-libtool@gnu.org + +_LT_EOF + fi ;; + esac + fi + break + fi + done + IFS=$lt_save_ifs + MAGIC_CMD=$lt_save_MAGIC_CMD + ;; +esac]) +MAGIC_CMD=$lt_cv_path_MAGIC_CMD +if test -n "$MAGIC_CMD"; then + AC_MSG_RESULT($MAGIC_CMD) +else + AC_MSG_RESULT(no) +fi +_LT_DECL([], [MAGIC_CMD], [0], + [Used to examine libraries when file_magic_cmd begins with "file"])dnl +])# _LT_PATH_TOOL_PREFIX + +# Old name: +AU_ALIAS([AC_PATH_TOOL_PREFIX], [_LT_PATH_TOOL_PREFIX]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_PATH_TOOL_PREFIX], []) + + +# _LT_PATH_MAGIC +# -------------- +# find a file program that can recognize a shared library +m4_defun([_LT_PATH_MAGIC], +[_LT_PATH_TOOL_PREFIX(${ac_tool_prefix}file, /usr/bin$PATH_SEPARATOR$PATH) +if test -z "$lt_cv_path_MAGIC_CMD"; then + if test -n "$ac_tool_prefix"; then + _LT_PATH_TOOL_PREFIX(file, /usr/bin$PATH_SEPARATOR$PATH) + else + MAGIC_CMD=: + fi +fi +])# _LT_PATH_MAGIC + + +# LT_PATH_LD +# ---------- +# find the pathname to the GNU or non-GNU linker +AC_DEFUN([LT_PATH_LD], +[AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_PROG_ECHO_BACKSLASH])dnl + +AC_ARG_WITH([gnu-ld], + [AS_HELP_STRING([--with-gnu-ld], + [assume the C compiler uses GNU ld @<:@default=no@:>@])], + [test no = "$withval" || with_gnu_ld=yes], + [with_gnu_ld=no])dnl + +ac_prog=ld +if test yes = "$GCC"; then + # Check if gcc -print-prog-name=ld gives a path. + AC_MSG_CHECKING([for ld used by $CC]) + case $host in + *-*-mingw*) + # gcc leaves a trailing carriage return, which upsets mingw + ac_prog=`($CC -print-prog-name=ld) 2>&5 | tr -d '\015'` ;; + *) + ac_prog=`($CC -print-prog-name=ld) 2>&5` ;; + esac + case $ac_prog in + # Accept absolute paths. + [[\\/]]* | ?:[[\\/]]*) + re_direlt='/[[^/]][[^/]]*/\.\./' + # Canonicalize the pathname of ld + ac_prog=`$ECHO "$ac_prog"| $SED 's%\\\\%/%g'` + while $ECHO "$ac_prog" | $GREP "$re_direlt" > /dev/null 2>&1; do + ac_prog=`$ECHO $ac_prog| $SED "s%$re_direlt%/%"` + done + test -z "$LD" && LD=$ac_prog + ;; + "") + # If it fails, then pretend we aren't using GCC. + ac_prog=ld + ;; + *) + # If it is relative, then search for the first ld in PATH. + with_gnu_ld=unknown + ;; + esac +elif test yes = "$with_gnu_ld"; then + AC_MSG_CHECKING([for GNU ld]) +else + AC_MSG_CHECKING([for non-GNU ld]) +fi +AC_CACHE_VAL(lt_cv_path_LD, +[if test -z "$LD"; then + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + if test -f "$ac_dir/$ac_prog" || test -f "$ac_dir/$ac_prog$ac_exeext"; then + lt_cv_path_LD=$ac_dir/$ac_prog + # Check to see if the program is GNU ld. I'd rather use --version, + # but apparently some variants of GNU ld only accept -v. + # Break only if it was the GNU/non-GNU ld that we prefer. + case `"$lt_cv_path_LD" -v 2>&1 &1 conftest.i +cat conftest.i conftest.i >conftest2.i +: ${lt_DD:=$DD} +AC_PATH_PROGS_FEATURE_CHECK([lt_DD], [dd], +[if "$ac_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && ac_cv_path_lt_DD="$ac_path_lt_DD" ac_path_lt_DD_found=: +fi]) +rm -f conftest.i conftest2.i conftest.out]) +])# _LT_PATH_DD + + +# _LT_CMD_TRUNCATE +# ---------------- +# find command to truncate a binary pipe +m4_defun([_LT_CMD_TRUNCATE], +[m4_require([_LT_PATH_DD]) +AC_CACHE_CHECK([how to truncate binary pipes], [lt_cv_truncate_bin], +[printf 0123456789abcdef0123456789abcdef >conftest.i +cat conftest.i conftest.i >conftest2.i +lt_cv_truncate_bin= +if "$ac_cv_path_lt_DD" bs=32 count=1 conftest.out 2>/dev/null; then + cmp -s conftest.i conftest.out \ + && lt_cv_truncate_bin="$ac_cv_path_lt_DD bs=4096 count=1" +fi +rm -f conftest.i conftest2.i conftest.out +test -z "$lt_cv_truncate_bin" && lt_cv_truncate_bin="$SED -e 4q"]) +_LT_DECL([lt_truncate_bin], [lt_cv_truncate_bin], [1], + [Command to truncate a binary pipe]) +])# _LT_CMD_TRUNCATE + + +# _LT_CHECK_MAGIC_METHOD +# ---------------------- +# how to check for library dependencies +# -- PORTME fill in with the dynamic library characteristics +m4_defun([_LT_CHECK_MAGIC_METHOD], +[m4_require([_LT_DECL_EGREP]) +m4_require([_LT_DECL_OBJDUMP]) +AC_CACHE_CHECK([how to recognize dependent libraries], +lt_cv_deplibs_check_method, +[lt_cv_file_magic_cmd='$MAGIC_CMD' +lt_cv_file_magic_test_file= +lt_cv_deplibs_check_method='unknown' +# Need to set the preceding variable on all platforms that support +# interlibrary dependencies. +# 'none' -- dependencies not supported. +# 'unknown' -- same as none, but documents that we really don't know. +# 'pass_all' -- all dependencies passed with no checks. +# 'test_compile' -- check by making test program. +# 'file_magic [[regex]]' -- check by looking for files in library path +# that responds to the $file_magic_cmd with a given extended regex. +# If you have 'file' or equivalent on your system and you're not sure +# whether 'pass_all' will *always* work, you probably want this one. + +case $host_os in +aix[[4-9]]*) + lt_cv_deplibs_check_method=pass_all + ;; + +beos*) + lt_cv_deplibs_check_method=pass_all + ;; + +bsdi[[45]]*) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib)' + lt_cv_file_magic_cmd='/usr/bin/file -L' + lt_cv_file_magic_test_file=/shlib/libc.so + ;; + +cygwin*) + # func_win32_libid is a shell function defined in ltmain.sh + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + ;; + +mingw* | pw32*) + # Base MSYS/MinGW do not provide the 'file' command needed by + # func_win32_libid shell function, so use a weaker test based on 'objdump', + # unless we find 'file', for example because we are cross-compiling. + if ( file / ) >/dev/null 2>&1; then + lt_cv_deplibs_check_method='file_magic ^x86 archive import|^x86 DLL' + lt_cv_file_magic_cmd='func_win32_libid' + else + # Keep this pattern in sync with the one in func_win32_libid. + lt_cv_deplibs_check_method='file_magic file format (pei*-i386(.*architecture: i386)?|pe-arm-wince|pe-x86-64)' + lt_cv_file_magic_cmd='$OBJDUMP -f' + fi + ;; + +cegcc*) + # use the weaker test based on 'objdump'. See mingw*. + lt_cv_deplibs_check_method='file_magic file format pe-arm-.*little(.*architecture: arm)?' + lt_cv_file_magic_cmd='$OBJDUMP -f' + ;; + +darwin* | rhapsody*) + lt_cv_deplibs_check_method=pass_all + ;; + +freebsd* | dragonfly*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + case $host_cpu in + i*86 ) + # Not sure whether the presence of OpenBSD here was a mistake. + # Let's accept both of them until this is cleared up. + lt_cv_deplibs_check_method='file_magic (FreeBSD|OpenBSD|DragonFly)/i[[3-9]]86 (compact )?demand paged shared library' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so.*` + ;; + esac + else + lt_cv_deplibs_check_method=pass_all + fi + ;; + +haiku*) + lt_cv_deplibs_check_method=pass_all + ;; + +hpux10.20* | hpux11*) + lt_cv_file_magic_cmd=/usr/bin/file + case $host_cpu in + ia64*) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|ELF-[[0-9]][[0-9]]) shared object file - IA64' + lt_cv_file_magic_test_file=/usr/lib/hpux32/libc.so + ;; + hppa*64*) + [lt_cv_deplibs_check_method='file_magic (s[0-9][0-9][0-9]|ELF[ -][0-9][0-9])(-bit)?( [LM]SB)? shared object( file)?[, -]* PA-RISC [0-9]\.[0-9]'] + lt_cv_file_magic_test_file=/usr/lib/pa20_64/libc.sl + ;; + *) + lt_cv_deplibs_check_method='file_magic (s[[0-9]][[0-9]][[0-9]]|PA-RISC[[0-9]]\.[[0-9]]) shared library' + lt_cv_file_magic_test_file=/usr/lib/libc.sl + ;; + esac + ;; + +interix[[3-9]]*) + # PIC code is broken on Interix 3.x, that's why |\.a not |_pic\.a here + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|\.a)$' + ;; + +irix5* | irix6* | nonstopux*) + case $LD in + *-32|*"-32 ") libmagic=32-bit;; + *-n32|*"-n32 ") libmagic=N32;; + *-64|*"-64 ") libmagic=64-bit;; + *) libmagic=never-match;; + esac + lt_cv_deplibs_check_method=pass_all + ;; + +# This must be glibc/ELF. +linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + lt_cv_deplibs_check_method=pass_all + ;; + +netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ > /dev/null; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so|_pic\.a)$' + fi + ;; + +newos6*) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (executable|dynamic lib)' + lt_cv_file_magic_cmd=/usr/bin/file + lt_cv_file_magic_test_file=/usr/lib/libnls.so + ;; + +*nto* | *qnx*) + lt_cv_deplibs_check_method=pass_all + ;; + +openbsd* | bitrig*) + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|\.so|_pic\.a)$' + else + lt_cv_deplibs_check_method='match_pattern /lib[[^/]]+(\.so\.[[0-9]]+\.[[0-9]]+|_pic\.a)$' + fi + ;; + +osf3* | osf4* | osf5*) + lt_cv_deplibs_check_method=pass_all + ;; + +rdos*) + lt_cv_deplibs_check_method=pass_all + ;; + +solaris*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX* | sysv4*uw2*) + lt_cv_deplibs_check_method=pass_all + ;; + +sysv4 | sysv4.3*) + case $host_vendor in + motorola) + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[ML]]SB (shared object|dynamic lib) M[[0-9]][[0-9]]* Version [[0-9]]' + lt_cv_file_magic_test_file=`echo /usr/lib/libc.so*` + ;; + ncr) + lt_cv_deplibs_check_method=pass_all + ;; + sequent) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method='file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB (shared object|dynamic lib )' + ;; + sni) + lt_cv_file_magic_cmd='/bin/file' + lt_cv_deplibs_check_method="file_magic ELF [[0-9]][[0-9]]*-bit [[LM]]SB dynamic lib" + lt_cv_file_magic_test_file=/lib/libc.so + ;; + siemens) + lt_cv_deplibs_check_method=pass_all + ;; + pc) + lt_cv_deplibs_check_method=pass_all + ;; + esac + ;; + +tpf*) + lt_cv_deplibs_check_method=pass_all + ;; +os2*) + lt_cv_deplibs_check_method=pass_all + ;; +esac +]) + +file_magic_glob= +want_nocaseglob=no +if test "$build" = "$host"; then + case $host_os in + mingw* | pw32*) + if ( shopt | grep nocaseglob ) >/dev/null 2>&1; then + want_nocaseglob=yes + else + file_magic_glob=`echo aAbBcCdDeEfFgGhHiIjJkKlLmMnNoOpPqQrRsStTuUvVwWxXyYzZ | $SED -e "s/\(..\)/s\/[[\1]]\/[[\1]]\/g;/g"` + fi + ;; + esac +fi + +file_magic_cmd=$lt_cv_file_magic_cmd +deplibs_check_method=$lt_cv_deplibs_check_method +test -z "$deplibs_check_method" && deplibs_check_method=unknown + +_LT_DECL([], [deplibs_check_method], [1], + [Method to check whether dependent libraries are shared objects]) +_LT_DECL([], [file_magic_cmd], [1], + [Command to use when deplibs_check_method = "file_magic"]) +_LT_DECL([], [file_magic_glob], [1], + [How to find potential files when deplibs_check_method = "file_magic"]) +_LT_DECL([], [want_nocaseglob], [1], + [Find potential files using nocaseglob when deplibs_check_method = "file_magic"]) +])# _LT_CHECK_MAGIC_METHOD + + +# LT_PATH_NM +# ---------- +# find the pathname to a BSD- or MS-compatible name lister +AC_DEFUN([LT_PATH_NM], +[AC_REQUIRE([AC_PROG_CC])dnl +AC_CACHE_CHECK([for BSD- or MS-compatible name lister (nm)], lt_cv_path_NM, +[if test -n "$NM"; then + # Let the user override the test. + lt_cv_path_NM=$NM +else + lt_nm_to_check=${ac_tool_prefix}nm + if test -n "$ac_tool_prefix" && test "$build" = "$host"; then + lt_nm_to_check="$lt_nm_to_check nm" + fi + for lt_tmp_nm in $lt_nm_to_check; do + lt_save_ifs=$IFS; IFS=$PATH_SEPARATOR + for ac_dir in $PATH /usr/ccs/bin/elf /usr/ccs/bin /usr/ucb /bin; do + IFS=$lt_save_ifs + test -z "$ac_dir" && ac_dir=. + tmp_nm=$ac_dir/$lt_tmp_nm + if test -f "$tmp_nm" || test -f "$tmp_nm$ac_exeext"; then + # Check to see if the nm accepts a BSD-compat flag. + # Adding the 'sed 1q' prevents false positives on HP-UX, which says: + # nm: unknown option "B" ignored + # Tru64's nm complains that /dev/null is an invalid object file + # MSYS converts /dev/null to NUL, MinGW nm treats NUL as empty + case $build_os in + mingw*) lt_bad_file=conftest.nm/nofile ;; + *) lt_bad_file=/dev/null ;; + esac + case `"$tmp_nm" -B $lt_bad_file 2>&1 | sed '1q'` in + *$lt_bad_file* | *'Invalid file or object type'*) + lt_cv_path_NM="$tmp_nm -B" + break 2 + ;; + *) + case `"$tmp_nm" -p /dev/null 2>&1 | sed '1q'` in + */dev/null*) + lt_cv_path_NM="$tmp_nm -p" + break 2 + ;; + *) + lt_cv_path_NM=${lt_cv_path_NM="$tmp_nm"} # keep the first match, but + continue # so that we can try to find one that supports BSD flags + ;; + esac + ;; + esac + fi + done + IFS=$lt_save_ifs + done + : ${lt_cv_path_NM=no} +fi]) +if test no != "$lt_cv_path_NM"; then + NM=$lt_cv_path_NM +else + # Didn't find any BSD compatible name lister, look for dumpbin. + if test -n "$DUMPBIN"; then : + # Let the user override the test. + else + AC_CHECK_TOOLS(DUMPBIN, [dumpbin "link -dump"], :) + case `$DUMPBIN -symbols -headers /dev/null 2>&1 | sed '1q'` in + *COFF*) + DUMPBIN="$DUMPBIN -symbols -headers" + ;; + *) + DUMPBIN=: + ;; + esac + fi + AC_SUBST([DUMPBIN]) + if test : != "$DUMPBIN"; then + NM=$DUMPBIN + fi +fi +test -z "$NM" && NM=nm +AC_SUBST([NM]) +_LT_DECL([], [NM], [1], [A BSD- or MS-compatible name lister])dnl + +AC_CACHE_CHECK([the name lister ($NM) interface], [lt_cv_nm_interface], + [lt_cv_nm_interface="BSD nm" + echo "int some_variable = 0;" > conftest.$ac_ext + (eval echo "\"\$as_me:$LINENO: $ac_compile\"" >&AS_MESSAGE_LOG_FD) + (eval "$ac_compile" 2>conftest.err) + cat conftest.err >&AS_MESSAGE_LOG_FD + (eval echo "\"\$as_me:$LINENO: $NM \\\"conftest.$ac_objext\\\"\"" >&AS_MESSAGE_LOG_FD) + (eval "$NM \"conftest.$ac_objext\"" 2>conftest.err > conftest.out) + cat conftest.err >&AS_MESSAGE_LOG_FD + (eval echo "\"\$as_me:$LINENO: output\"" >&AS_MESSAGE_LOG_FD) + cat conftest.out >&AS_MESSAGE_LOG_FD + if $GREP 'External.*some_variable' conftest.out > /dev/null; then + lt_cv_nm_interface="MS dumpbin" + fi + rm -f conftest*]) +])# LT_PATH_NM + +# Old names: +AU_ALIAS([AM_PROG_NM], [LT_PATH_NM]) +AU_ALIAS([AC_PROG_NM], [LT_PATH_NM]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_PROG_NM], []) +dnl AC_DEFUN([AC_PROG_NM], []) + +# _LT_CHECK_SHAREDLIB_FROM_LINKLIB +# -------------------------------- +# how to determine the name of the shared library +# associated with a specific link library. +# -- PORTME fill in with the dynamic library characteristics +m4_defun([_LT_CHECK_SHAREDLIB_FROM_LINKLIB], +[m4_require([_LT_DECL_EGREP]) +m4_require([_LT_DECL_OBJDUMP]) +m4_require([_LT_DECL_DLLTOOL]) +AC_CACHE_CHECK([how to associate runtime and link libraries], +lt_cv_sharedlib_from_linklib_cmd, +[lt_cv_sharedlib_from_linklib_cmd='unknown' + +case $host_os in +cygwin* | mingw* | pw32* | cegcc*) + # two different shell functions defined in ltmain.sh; + # decide which one to use based on capabilities of $DLLTOOL + case `$DLLTOOL --help 2>&1` in + *--identify-strict*) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib + ;; + *) + lt_cv_sharedlib_from_linklib_cmd=func_cygming_dll_for_implib_fallback + ;; + esac + ;; +*) + # fallback: assume linklib IS sharedlib + lt_cv_sharedlib_from_linklib_cmd=$ECHO + ;; +esac +]) +sharedlib_from_linklib_cmd=$lt_cv_sharedlib_from_linklib_cmd +test -z "$sharedlib_from_linklib_cmd" && sharedlib_from_linklib_cmd=$ECHO + +_LT_DECL([], [sharedlib_from_linklib_cmd], [1], + [Command to associate shared and link libraries]) +])# _LT_CHECK_SHAREDLIB_FROM_LINKLIB + + +# _LT_PATH_MANIFEST_TOOL +# ---------------------- +# locate the manifest tool +m4_defun([_LT_PATH_MANIFEST_TOOL], +[AC_CHECK_TOOL(MANIFEST_TOOL, mt, :) +test -z "$MANIFEST_TOOL" && MANIFEST_TOOL=mt +AC_CACHE_CHECK([if $MANIFEST_TOOL is a manifest tool], [lt_cv_path_mainfest_tool], + [lt_cv_path_mainfest_tool=no + echo "$as_me:$LINENO: $MANIFEST_TOOL '-?'" >&AS_MESSAGE_LOG_FD + $MANIFEST_TOOL '-?' 2>conftest.err > conftest.out + cat conftest.err >&AS_MESSAGE_LOG_FD + if $GREP 'Manifest Tool' conftest.out > /dev/null; then + lt_cv_path_mainfest_tool=yes + fi + rm -f conftest*]) +if test yes != "$lt_cv_path_mainfest_tool"; then + MANIFEST_TOOL=: +fi +_LT_DECL([], [MANIFEST_TOOL], [1], [Manifest tool])dnl +])# _LT_PATH_MANIFEST_TOOL + + +# _LT_DLL_DEF_P([FILE]) +# --------------------- +# True iff FILE is a Windows DLL '.def' file. +# Keep in sync with func_dll_def_p in the libtool script +AC_DEFUN([_LT_DLL_DEF_P], +[dnl + test DEF = "`$SED -n dnl + -e '\''s/^[[ ]]*//'\'' dnl Strip leading whitespace + -e '\''/^\(;.*\)*$/d'\'' dnl Delete empty lines and comments + -e '\''s/^\(EXPORTS\|LIBRARY\)\([[ ]].*\)*$/DEF/p'\'' dnl + -e q dnl Only consider the first "real" line + $1`" dnl +])# _LT_DLL_DEF_P + + +# LT_LIB_M +# -------- +# check for math library +AC_DEFUN([LT_LIB_M], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +LIBM= +case $host in +*-*-beos* | *-*-cegcc* | *-*-cygwin* | *-*-haiku* | *-*-pw32* | *-*-darwin*) + # These system don't have libm, or don't need it + ;; +*-ncr-sysv4.3*) + AC_CHECK_LIB(mw, _mwvalidcheckl, LIBM=-lmw) + AC_CHECK_LIB(m, cos, LIBM="$LIBM -lm") + ;; +*) + AC_CHECK_LIB(m, cos, LIBM=-lm) + ;; +esac +AC_SUBST([LIBM]) +])# LT_LIB_M + +# Old name: +AU_ALIAS([AC_CHECK_LIBM], [LT_LIB_M]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_CHECK_LIBM], []) + + +# _LT_COMPILER_NO_RTTI([TAGNAME]) +# ------------------------------- +m4_defun([_LT_COMPILER_NO_RTTI], +[m4_require([_LT_TAG_COMPILER])dnl + +_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + +if test yes = "$GCC"; then + case $cc_basename in + nvcc*) + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -Xcompiler -fno-builtin' ;; + *) + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' ;; + esac + + _LT_COMPILER_OPTION([if $compiler supports -fno-rtti -fno-exceptions], + lt_cv_prog_compiler_rtti_exceptions, + [-fno-rtti -fno-exceptions], [], + [_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)="$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1) -fno-rtti -fno-exceptions"]) +fi +_LT_TAGDECL([no_builtin_flag], [lt_prog_compiler_no_builtin_flag], [1], + [Compiler flag to turn off builtin functions]) +])# _LT_COMPILER_NO_RTTI + + +# _LT_CMD_GLOBAL_SYMBOLS +# ---------------------- +m4_defun([_LT_CMD_GLOBAL_SYMBOLS], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_PROG_CC])dnl +AC_REQUIRE([AC_PROG_AWK])dnl +AC_REQUIRE([LT_PATH_NM])dnl +AC_REQUIRE([LT_PATH_LD])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_TAG_COMPILER])dnl + +# Check for command to grab the raw symbol name followed by C symbol from nm. +AC_MSG_CHECKING([command to parse $NM output from $compiler object]) +AC_CACHE_VAL([lt_cv_sys_global_symbol_pipe], +[ +# These are sane defaults that work on at least a few old systems. +# [They come from Ultrix. What could be older than Ultrix?!! ;)] + +# Character class describing NM global symbol codes. +symcode='[[BCDEGRST]]' + +# Regexp to match symbols that can be accessed directly from C. +sympat='\([[_A-Za-z]][[_A-Za-z0-9]]*\)' + +# Define system-specific variables. +case $host_os in +aix*) + symcode='[[BCDT]]' + ;; +cygwin* | mingw* | pw32* | cegcc*) + symcode='[[ABCDGISTW]]' + ;; +hpux*) + if test ia64 = "$host_cpu"; then + symcode='[[ABCDEGRST]]' + fi + ;; +irix* | nonstopux*) + symcode='[[BCDEGRST]]' + ;; +osf*) + symcode='[[BCDEGQRST]]' + ;; +solaris*) + symcode='[[BDRT]]' + ;; +sco3.2v5*) + symcode='[[DT]]' + ;; +sysv4.2uw2*) + symcode='[[DT]]' + ;; +sysv5* | sco5v6* | unixware* | OpenUNIX*) + symcode='[[ABDT]]' + ;; +sysv4) + symcode='[[DFNSTU]]' + ;; +esac + +# If we're using GNU nm, then use its standard symbol codes. +case `$NM -V 2>&1` in +*GNU* | *'with BFD'*) + symcode='[[ABCDGIRSTW]]' ;; +esac + +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Gets list of data symbols to import. + lt_cv_sys_global_symbol_to_import="sed -n -e 's/^I .* \(.*\)$/\1/p'" + # Adjust the below global symbol transforms to fixup imported variables. + lt_cdecl_hook=" -e 's/^I .* \(.*\)$/extern __declspec(dllimport) char \1;/p'" + lt_c_name_hook=" -e 's/^I .* \(.*\)$/ {\"\1\", (void *) 0},/p'" + lt_c_name_lib_hook="\ + -e 's/^I .* \(lib.*\)$/ {\"\1\", (void *) 0},/p'\ + -e 's/^I .* \(.*\)$/ {\"lib\1\", (void *) 0},/p'" +else + # Disable hooks by default. + lt_cv_sys_global_symbol_to_import= + lt_cdecl_hook= + lt_c_name_hook= + lt_c_name_lib_hook= +fi + +# Transform an extracted symbol line into a proper C declaration. +# Some systems (esp. on ia64) link data and code symbols differently, +# so use this general approach. +lt_cv_sys_global_symbol_to_cdecl="sed -n"\ +$lt_cdecl_hook\ +" -e 's/^T .* \(.*\)$/extern int \1();/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/extern char \1;/p'" + +# Transform an extracted symbol line into symbol name and symbol address +lt_cv_sys_global_symbol_to_c_name_address="sed -n"\ +$lt_c_name_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/p'" + +# Transform an extracted symbol line into symbol name with lib prefix and +# symbol address. +lt_cv_sys_global_symbol_to_c_name_address_lib_prefix="sed -n"\ +$lt_c_name_lib_hook\ +" -e 's/^: \(.*\) .*$/ {\"\1\", (void *) 0},/p'"\ +" -e 's/^$symcode$symcode* .* \(lib.*\)$/ {\"\1\", (void *) \&\1},/p'"\ +" -e 's/^$symcode$symcode* .* \(.*\)$/ {\"lib\1\", (void *) \&\1},/p'" + +# Handle CRLF in mingw tool chain +opt_cr= +case $build_os in +mingw*) + opt_cr=`$ECHO 'x\{0,1\}' | tr x '\015'` # option cr in regexp + ;; +esac + +# Try without a prefix underscore, then with it. +for ac_symprfx in "" "_"; do + + # Transform symcode, sympat, and symprfx into a raw symbol and a C symbol. + symxfrm="\\1 $ac_symprfx\\2 \\2" + + # Write the raw and C identifiers. + if test "$lt_cv_nm_interface" = "MS dumpbin"; then + # Fake it for dumpbin and say T for any non-static function, + # D for any global variable and I for any imported variable. + # Also find C++ and __fastcall symbols from MSVC++, + # which start with @ or ?. + lt_cv_sys_global_symbol_pipe="$AWK ['"\ +" {last_section=section; section=\$ 3};"\ +" /^COFF SYMBOL TABLE/{for(i in hide) delete hide[i]};"\ +" /Section length .*#relocs.*(pick any)/{hide[last_section]=1};"\ +" /^ *Symbol name *: /{split(\$ 0,sn,\":\"); si=substr(sn[2],2)};"\ +" /^ *Type *: code/{print \"T\",si,substr(si,length(prfx))};"\ +" /^ *Type *: data/{print \"I\",si,substr(si,length(prfx))};"\ +" \$ 0!~/External *\|/{next};"\ +" / 0+ UNDEF /{next}; / UNDEF \([^|]\)*()/{next};"\ +" {if(hide[section]) next};"\ +" {f=\"D\"}; \$ 0~/\(\).*\|/{f=\"T\"};"\ +" {split(\$ 0,a,/\||\r/); split(a[2],s)};"\ +" s[1]~/^[@?]/{print f,s[1],s[1]; next};"\ +" s[1]~prfx {split(s[1],t,\"@\"); print f,t[1],substr(t[1],length(prfx))}"\ +" ' prfx=^$ac_symprfx]" + else + lt_cv_sys_global_symbol_pipe="sed -n -e 's/^.*[[ ]]\($symcode$symcode*\)[[ ]][[ ]]*$ac_symprfx$sympat$opt_cr$/$symxfrm/p'" + fi + lt_cv_sys_global_symbol_pipe="$lt_cv_sys_global_symbol_pipe | sed '/ __gnu_lto/d'" + + # Check to see that the pipe works correctly. + pipe_works=no + + rm -f conftest* + cat > conftest.$ac_ext <<_LT_EOF +#ifdef __cplusplus +extern "C" { +#endif +char nm_test_var; +void nm_test_func(void); +void nm_test_func(void){} +#ifdef __cplusplus +} +#endif +int main(){nm_test_var='a';nm_test_func();return(0);} +_LT_EOF + + if AC_TRY_EVAL(ac_compile); then + # Now try to grab the symbols. + nlist=conftest.nm + if AC_TRY_EVAL(NM conftest.$ac_objext \| "$lt_cv_sys_global_symbol_pipe" \> $nlist) && test -s "$nlist"; then + # Try sorting and uniquifying the output. + if sort "$nlist" | uniq > "$nlist"T; then + mv -f "$nlist"T "$nlist" + else + rm -f "$nlist"T + fi + + # Make sure that we snagged all the symbols we need. + if $GREP ' nm_test_var$' "$nlist" >/dev/null; then + if $GREP ' nm_test_func$' "$nlist" >/dev/null; then + cat <<_LT_EOF > conftest.$ac_ext +/* Keep this code in sync between libtool.m4, ltmain, lt_system.h, and tests. */ +#if defined _WIN32 || defined __CYGWIN__ || defined _WIN32_WCE +/* DATA imports from DLLs on WIN32 can't be const, because runtime + relocations are performed -- see ld's documentation on pseudo-relocs. */ +# define LT@&t@_DLSYM_CONST +#elif defined __osf__ +/* This system does not cope well with relocations in const data. */ +# define LT@&t@_DLSYM_CONST +#else +# define LT@&t@_DLSYM_CONST const +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +_LT_EOF + # Now generate the symbol file. + eval "$lt_cv_sys_global_symbol_to_cdecl"' < "$nlist" | $GREP -v main >> conftest.$ac_ext' + + cat <<_LT_EOF >> conftest.$ac_ext + +/* The mapping between symbol names and symbols. */ +LT@&t@_DLSYM_CONST struct { + const char *name; + void *address; +} +lt__PROGRAM__LTX_preloaded_symbols[[]] = +{ + { "@PROGRAM@", (void *) 0 }, +_LT_EOF + $SED "s/^$symcode$symcode* .* \(.*\)$/ {\"\1\", (void *) \&\1},/" < "$nlist" | $GREP -v main >> conftest.$ac_ext + cat <<\_LT_EOF >> conftest.$ac_ext + {0, (void *) 0} +}; + +/* This works around a problem in FreeBSD linker */ +#ifdef FREEBSD_WORKAROUND +static const void *lt_preloaded_setup() { + return lt__PROGRAM__LTX_preloaded_symbols; +} +#endif + +#ifdef __cplusplus +} +#endif +_LT_EOF + # Now try linking the two files. + mv conftest.$ac_objext conftstm.$ac_objext + lt_globsym_save_LIBS=$LIBS + lt_globsym_save_CFLAGS=$CFLAGS + LIBS=conftstm.$ac_objext + CFLAGS="$CFLAGS$_LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)" + if AC_TRY_EVAL(ac_link) && test -s conftest$ac_exeext; then + pipe_works=yes + fi + LIBS=$lt_globsym_save_LIBS + CFLAGS=$lt_globsym_save_CFLAGS + else + echo "cannot find nm_test_func in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot find nm_test_var in $nlist" >&AS_MESSAGE_LOG_FD + fi + else + echo "cannot run $lt_cv_sys_global_symbol_pipe" >&AS_MESSAGE_LOG_FD + fi + else + echo "$progname: failed program was:" >&AS_MESSAGE_LOG_FD + cat conftest.$ac_ext >&5 + fi + rm -rf conftest* conftst* + + # Do not use the global_symbol_pipe unless it works. + if test yes = "$pipe_works"; then + break + else + lt_cv_sys_global_symbol_pipe= + fi +done +]) +if test -z "$lt_cv_sys_global_symbol_pipe"; then + lt_cv_sys_global_symbol_to_cdecl= +fi +if test -z "$lt_cv_sys_global_symbol_pipe$lt_cv_sys_global_symbol_to_cdecl"; then + AC_MSG_RESULT(failed) +else + AC_MSG_RESULT(ok) +fi + +# Response file support. +if test "$lt_cv_nm_interface" = "MS dumpbin"; then + nm_file_list_spec='@' +elif $NM --help 2>/dev/null | grep '[[@]]FILE' >/dev/null; then + nm_file_list_spec='@' +fi + +_LT_DECL([global_symbol_pipe], [lt_cv_sys_global_symbol_pipe], [1], + [Take the output of nm and produce a listing of raw symbols and C names]) +_LT_DECL([global_symbol_to_cdecl], [lt_cv_sys_global_symbol_to_cdecl], [1], + [Transform the output of nm in a proper C declaration]) +_LT_DECL([global_symbol_to_import], [lt_cv_sys_global_symbol_to_import], [1], + [Transform the output of nm into a list of symbols to manually relocate]) +_LT_DECL([global_symbol_to_c_name_address], + [lt_cv_sys_global_symbol_to_c_name_address], [1], + [Transform the output of nm in a C name address pair]) +_LT_DECL([global_symbol_to_c_name_address_lib_prefix], + [lt_cv_sys_global_symbol_to_c_name_address_lib_prefix], [1], + [Transform the output of nm in a C name address pair when lib prefix is needed]) +_LT_DECL([nm_interface], [lt_cv_nm_interface], [1], + [The name lister interface]) +_LT_DECL([], [nm_file_list_spec], [1], + [Specify filename containing input files for $NM]) +]) # _LT_CMD_GLOBAL_SYMBOLS + + +# _LT_COMPILER_PIC([TAGNAME]) +# --------------------------- +m4_defun([_LT_COMPILER_PIC], +[m4_require([_LT_TAG_COMPILER])dnl +_LT_TAGVAR(lt_prog_compiler_wl, $1)= +_LT_TAGVAR(lt_prog_compiler_pic, $1)= +_LT_TAGVAR(lt_prog_compiler_static, $1)= + +m4_if([$1], [CXX], [ + # C++ specific cases for pic, static, wl, etc. + if test yes = "$GXX"; then + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + *djgpp*) + # DJGPP does not support shared libraries at all + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + _LT_TAGVAR(lt_prog_compiler_static, $1)= + ;; + interix[[3-9]]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + else + case $host_os in + aix[[4-9]]*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + chorus*) + case $cc_basename in + cxch68*) + # Green Hills C++ Compiler + # _LT_TAGVAR(lt_prog_compiler_static, $1)="--no_auto_instantiation -u __main -u __premain -u _abort -r $COOL_DIR/lib/libOrb.a $MVME_DIR/lib/CC/libC.a $MVME_DIR/lib/classix/libcx.s.a" + ;; + esac + ;; + mingw* | cygwin* | os2* | pw32* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + ;; + dgux*) + case $cc_basename in + ec++*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + ghcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + freebsd* | dragonfly*) + # FreeBSD uses GNU C++ + ;; + hpux9* | hpux10* | hpux11*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + if test ia64 != "$host_cpu"; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + fi + ;; + aCC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + ;; + *) + ;; + esac + ;; + interix*) + # This is c89, which is MS Visual C++ (no shared libs) + # Anyone wants to do a port? + ;; + irix5* | irix6* | nonstopux*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + # CC pic flag -KPIC is the default. + ;; + *) + ;; + esac + ;; + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + KCC*) + # KAI C++ Compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + ecpc* ) + # old Intel C++ for x86_64, which still supported -KPIC. + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + icpc* ) + # Intel C++, used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + cxx*) + # Compaq C++ + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + xlc* | xlC* | bgxl[[cC]]* | mpixl[[cC]]*) + # IBM XL 8.0, 9.0 on PPC and BlueGene + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + esac + ;; + esac + ;; + lynxos*) + ;; + m88k*) + ;; + mvs*) + case $cc_basename in + cxx*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-W c,exportall' + ;; + *) + ;; + esac + ;; + netbsd* | netbsdelf*-gnu) + ;; + *qnx* | *nto*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='--backend -Wl,' + ;; + RCC*) + # Rational C++ 2.4.1 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + cxx*) + # Digital/Compaq C++ + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # Make sure the PIC flag is empty. It appears that all Alpha + # Linux and Compaq Tru64 Unix objects are PIC. + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + *) + ;; + esac + ;; + psos*) + ;; + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + gcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + ;; + *) + ;; + esac + ;; + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + lcc*) + # Lucid + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + ;; + *) + ;; + esac + ;; + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + case $cc_basename in + CC*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + ;; + *) + ;; + esac + ;; + vxworks*) + ;; + *) + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +], +[ + if test yes = "$GCC"; then + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + + case $host_os in + aix*) + # All AIX code is PIC. + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + m68k) + # FIXME: we need at least 68020 code to build shared libraries, but + # adding the '-m68020' flag to GCC prevents building anything better, + # like '-m68040'. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-m68020 -resident32 -malways-restore-a4' + ;; + esac + ;; + + beos* | irix5* | irix6* | nonstopux* | osf3* | osf4* | osf5*) + # PIC is the default for these OSes. + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + # Although the cygwin gcc ignores -fPIC, still need this for old-style + # (--disable-auto-import) libraries + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + ;; + + haiku*) + # PIC is the default for Haiku. + # The "-static" flag exists, but is broken. + _LT_TAGVAR(lt_prog_compiler_static, $1)= + ;; + + hpux*) + # PIC is the default for 64-bit PA HP-UX, but not for 32-bit + # PA HP-UX. On IA64 HP-UX, PIC is the default but the pic flag + # sets the default TLS model and affects inlining. + case $host_cpu in + hppa*64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + ;; + + interix[[3-9]]*) + # Interix 3.x gcc -fpic/-fPIC options generate broken code. + # Instead, we relocate shared libraries at runtime. + ;; + + msdosdjgpp*) + # Just because we use GCC doesn't mean we suddenly get shared libraries + # on systems that don't support them. + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + enable_shared=no + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)=-Kconform_pic + fi + ;; + + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + ;; + esac + + case $cc_basename in + nvcc*) # Cuda Compiler Driver 2.2 + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Xlinker ' + if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)="-Xcompiler $_LT_TAGVAR(lt_prog_compiler_pic, $1)" + fi + ;; + esac + else + # PORTME Check for flag to pass linker flags through the system compiler. + case $host_os in + aix*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + if test ia64 = "$host_cpu"; then + # AIX 5 now supports IA64 processor + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + else + _LT_TAGVAR(lt_prog_compiler_static, $1)='-bnso -bI:/lib/syscalls.exp' + fi + ;; + + darwin* | rhapsody*) + # PIC is the default on this platform + # Common symbols not allowed in MH_DYLIB files + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fno-common' + case $cc_basename in + nagfor*) + # NAG Fortran compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + + mingw* | cygwin* | pw32* | os2* | cegcc*) + # This hack is so that the source file can tell whether it is being + # built for inclusion in a dll (and should export symbols for example). + m4_if([$1], [GCJ], [], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)='-DDLL_EXPORT']) + case $host_os in + os2*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-static' + ;; + esac + ;; + + hpux9* | hpux10* | hpux11*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC is the default for IA64 HP-UX and 64-bit HP-UX, but + # not for PA HP-UX. + case $host_cpu in + hppa*64*|ia64*) + # +Z the default + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='+Z' + ;; + esac + # Is there a better lt_prog_compiler_static that works with the bundled CC? + _LT_TAGVAR(lt_prog_compiler_static, $1)='$wl-a ${wl}archive' + ;; + + irix5* | irix6* | nonstopux*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # PIC (with -KPIC) is the default. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + # old Intel for x86_64, which still supported -KPIC. + ecc*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + # icc used to be incompatible with GCC. + # ICC 10 doesn't accept -KPIC any more. + icc* | ifort*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + # Lahey Fortran 8.1. + lf95*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='--shared' + _LT_TAGVAR(lt_prog_compiler_static, $1)='--static' + ;; + nagfor*) + # NAG Fortran compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,-Wl,,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + pgcc* | pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group compilers (*not* the Pentium gcc compiler, + # which looks to be a dead project) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + ccc*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All Alpha code is PIC. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + xl* | bgxl* | bgf* | mpixl*) + # IBM XL C 8.0/Fortran 10.1, 11.1 on PPC and BlueGene + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-qpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-qstaticlink' + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ Ceres\ Fortran* | *Sun*Fortran*\ [[1-7]].* | *Sun*Fortran*\ 8.[[0-3]]*) + # Sun Fortran 8.3 passes all unrecognized flags to the linker + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='' + ;; + *Sun\ F* | *Sun*Fortran*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + ;; + *Sun\ C*) + # Sun C 5.9 + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + ;; + *Intel*\ [[CF]]*Compiler*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-static' + ;; + *Portland\ Group*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fpic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + esac + ;; + esac + ;; + + newsos6) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *nto* | *qnx*) + # QNX uses GNU C++, but need to define -shared option too, otherwise + # it will coredump. + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-fPIC -shared' + ;; + + osf3* | osf4* | osf5*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + # All OSF/1 code is PIC. + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + rdos*) + _LT_TAGVAR(lt_prog_compiler_static, $1)='-non_shared' + ;; + + solaris*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + case $cc_basename in + f77* | f90* | f95* | sunf77* | sunf90* | sunf95*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ';; + *) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,';; + esac + ;; + + sunos4*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Qoption ld ' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-PIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4 | sysv4.2uw2* | sysv4.3*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-Kconform_pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + fi + ;; + + sysv5* | unixware* | sco3.2v5* | sco5v6* | OpenUNIX*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-KPIC' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + unicos*) + _LT_TAGVAR(lt_prog_compiler_wl, $1)='-Wl,' + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + + uts4*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)='-pic' + _LT_TAGVAR(lt_prog_compiler_static, $1)='-Bstatic' + ;; + + *) + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no + ;; + esac + fi +]) +case $host_os in + # For platforms that do not support PIC, -DPIC is meaningless: + *djgpp*) + _LT_TAGVAR(lt_prog_compiler_pic, $1)= + ;; + *) + _LT_TAGVAR(lt_prog_compiler_pic, $1)="$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])" + ;; +esac + +AC_CACHE_CHECK([for $compiler option to produce PIC], + [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)], + [_LT_TAGVAR(lt_cv_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_prog_compiler_pic, $1)]) +_LT_TAGVAR(lt_prog_compiler_pic, $1)=$_LT_TAGVAR(lt_cv_prog_compiler_pic, $1) + +# +# Check to make sure the PIC flag actually works. +# +if test -n "$_LT_TAGVAR(lt_prog_compiler_pic, $1)"; then + _LT_COMPILER_OPTION([if $compiler PIC flag $_LT_TAGVAR(lt_prog_compiler_pic, $1) works], + [_LT_TAGVAR(lt_cv_prog_compiler_pic_works, $1)], + [$_LT_TAGVAR(lt_prog_compiler_pic, $1)@&t@m4_if([$1],[],[ -DPIC],[m4_if([$1],[CXX],[ -DPIC],[])])], [], + [case $_LT_TAGVAR(lt_prog_compiler_pic, $1) in + "" | " "*) ;; + *) _LT_TAGVAR(lt_prog_compiler_pic, $1)=" $_LT_TAGVAR(lt_prog_compiler_pic, $1)" ;; + esac], + [_LT_TAGVAR(lt_prog_compiler_pic, $1)= + _LT_TAGVAR(lt_prog_compiler_can_build_shared, $1)=no]) +fi +_LT_TAGDECL([pic_flag], [lt_prog_compiler_pic], [1], + [Additional compiler flags for building library objects]) + +_LT_TAGDECL([wl], [lt_prog_compiler_wl], [1], + [How to pass a linker flag through the compiler]) +# +# Check to make sure the static flag actually works. +# +wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) eval lt_tmp_static_flag=\"$_LT_TAGVAR(lt_prog_compiler_static, $1)\" +_LT_LINKER_OPTION([if $compiler static flag $lt_tmp_static_flag works], + _LT_TAGVAR(lt_cv_prog_compiler_static_works, $1), + $lt_tmp_static_flag, + [], + [_LT_TAGVAR(lt_prog_compiler_static, $1)=]) +_LT_TAGDECL([link_static_flag], [lt_prog_compiler_static], [1], + [Compiler flag to prevent dynamic linking]) +])# _LT_COMPILER_PIC + + +# _LT_LINKER_SHLIBS([TAGNAME]) +# ---------------------------- +# See if the linker supports building shared libraries. +m4_defun([_LT_LINKER_SHLIBS], +[AC_REQUIRE([LT_PATH_LD])dnl +AC_REQUIRE([LT_PATH_NM])dnl +m4_require([_LT_PATH_MANIFEST_TOOL])dnl +m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_DECL_SED])dnl +m4_require([_LT_CMD_GLOBAL_SYMBOLS])dnl +m4_require([_LT_TAG_COMPILER])dnl +AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) +m4_if([$1], [CXX], [ + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] + case $host_os in + aix[[4-9]]*) + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + ;; + pw32*) + _LT_TAGVAR(export_symbols_cmds, $1)=$ltdll_cmds + ;; + cygwin* | mingw* | cegcc*) + case $cc_basename in + cl*) + _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + ;; + *) + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'] + ;; + esac + ;; + linux* | k*bsd*-gnu | gnu*) + _LT_TAGVAR(link_all_deplibs, $1)=no + ;; + *) + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + ;; + esac +], [ + runpath_var= + _LT_TAGVAR(allow_undefined_flag, $1)= + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(archive_cmds, $1)= + _LT_TAGVAR(archive_expsym_cmds, $1)= + _LT_TAGVAR(compiler_needs_object, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + _LT_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED '\''s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(hardcode_automatic, $1)=no + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_TAGVAR(hardcode_libdir_separator, $1)= + _LT_TAGVAR(hardcode_minus_L, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported + _LT_TAGVAR(inherit_rpath, $1)=no + _LT_TAGVAR(link_all_deplibs, $1)=unknown + _LT_TAGVAR(module_cmds, $1)= + _LT_TAGVAR(module_expsym_cmds, $1)= + _LT_TAGVAR(old_archive_from_new_cmds, $1)= + _LT_TAGVAR(old_archive_from_expsyms_cmds, $1)= + _LT_TAGVAR(thread_safe_flag_spec, $1)= + _LT_TAGVAR(whole_archive_flag_spec, $1)= + # include_expsyms should be a list of space-separated symbols to be *always* + # included in the symbol list + _LT_TAGVAR(include_expsyms, $1)= + # exclude_expsyms can be an extended regexp of symbols to exclude + # it will be wrapped by ' (' and ')$', so one must not match beginning or + # end of line. Example: 'a|bc|.*d.*' will exclude the symbols 'a' and 'bc', + # as well as any symbol that contains 'd'. + _LT_TAGVAR(exclude_expsyms, $1)=['_GLOBAL_OFFSET_TABLE_|_GLOBAL__F[ID]_.*'] + # Although _GLOBAL_OFFSET_TABLE_ is a valid symbol C name, most a.out + # platforms (ab)use it in PIC code, but their linkers get confused if + # the symbol is explicitly referenced. Since portable code cannot + # rely on this symbol name, it's probably fine to never include it in + # preloaded symbol tables. + # Exclude shared library initialization/finalization symbols. +dnl Note also adjust exclude_expsyms for C++ above. + extract_expsyms_cmds= + + case $host_os in + cygwin* | mingw* | pw32* | cegcc*) + # FIXME: the MSVC++ port hasn't been tested in a loooong time + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + if test yes != "$GCC"; then + with_gnu_ld=no + fi + ;; + interix*) + # we just hope/assume this is gcc and not c89 (= MSVC++) + with_gnu_ld=yes + ;; + openbsd* | bitrig*) + with_gnu_ld=no + ;; + linux* | k*bsd*-gnu | gnu*) + _LT_TAGVAR(link_all_deplibs, $1)=no + ;; + esac + + _LT_TAGVAR(ld_shlibs, $1)=yes + + # On some targets, GNU ld is compatible enough with the native linker + # that we're better off using the native interface for both. + lt_use_gnu_ld_interface=no + if test yes = "$with_gnu_ld"; then + case $host_os in + aix*) + # The AIX port of GNU ld has always aspired to compatibility + # with the native linker. However, as the warning in the GNU ld + # block says, versions before 2.19.5* couldn't really create working + # shared libraries, regardless of the interface used. + case `$LD -v 2>&1` in + *\ \(GNU\ Binutils\)\ 2.19.5*) ;; + *\ \(GNU\ Binutils\)\ 2.[[2-9]]*) ;; + *\ \(GNU\ Binutils\)\ [[3-9]]*) ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + ;; + *) + lt_use_gnu_ld_interface=yes + ;; + esac + fi + + if test yes = "$lt_use_gnu_ld_interface"; then + # If archive_cmds runs LD, not CC, wlarc should be empty + wlarc='$wl' + + # Set some defaults for GNU ld with shared library support. These + # are reset later if shared libraries are not supported. Putting them + # here allows them to be overridden if necessary. + runpath_var=LD_RUN_PATH + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + # ancient GNU ld didn't support --whole-archive et. al. + if $LD --help 2>&1 | $GREP 'no-whole-archive' > /dev/null; then + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + supports_anon_versioning=no + case `$LD -v | $SED -e 's/([^)]\+)\s\+//' 2>&1` in + *GNU\ gold*) supports_anon_versioning=yes ;; + *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.10.*) ;; # catch versions < 2.11 + *\ 2.11.93.0.2\ *) supports_anon_versioning=yes ;; # RH7.3 ... + *\ 2.11.92.0.12\ *) supports_anon_versioning=yes ;; # Mandrake 8.2 ... + *\ 2.11.*) ;; # other 2.11 versions + *) supports_anon_versioning=yes ;; + esac + + # See if GNU ld supports shared libraries. + case $host_os in + aix[[3-9]]*) + # On AIX/PPC, the GNU linker is very broken + if test ia64 != "$host_cpu"; then + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: the GNU linker, at least up to release 2.19, is reported +*** to be unable to reliably create shared libraries on AIX. +*** Therefore, libtool is disabling shared libraries support. If you +*** really care for shared libraries, you may want to install binutils +*** 2.20 or above, or modify your PATH so that a non-GNU linker is found. +*** You will then need to restart the configuration process. + +_LT_EOF + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='' + ;; + m68k) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1 DATA/;s/^.*[[ ]]__nm__\([[^ ]]*\)[[ ]][[^ ]]*/\1 DATA/;/^I[[ ]]/d;/^[[AITW]][[ ]]/s/.* //'\'' | sort | uniq > $export_symbols' + _LT_TAGVAR(exclude_expsyms, $1)=['[_]+GLOBAL_OFFSET_TABLE_|[_]+GLOBAL__[FID]_.*|[_]+head_[A-Za-z0-9_]+_dll|[A-Za-z0-9_]+_dll_iname'] + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared $output_objdir/$soname.def $libobjs $deplibs $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + haiku*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + interix[[3-9]]*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + + gnu* | linux* | tpf* | k*bsd*-gnu | kopensolaris*-gnu) + tmp_diet=no + if test linux-dietlibc = "$host_os"; then + case $cc_basename in + diet\ *) tmp_diet=yes;; # linux-dietlibc with static linking (!diet-dyn) + esac + fi + if $LD --help 2>&1 | $EGREP ': supported targets:.* elf' > /dev/null \ + && test no = "$tmp_diet" + then + tmp_addflag=' $pic_flag' + tmp_sharedflag='-shared' + case $cc_basename,$host_cpu in + pgcc*) # Portland Group C compiler + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag' + ;; + pgf77* | pgf90* | pgf95* | pgfortran*) + # Portland Group f77 and f90 compilers + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + tmp_addflag=' $pic_flag -Mnomain' ;; + ecc*,ia64* | icc*,ia64*) # Intel C compiler on ia64 + tmp_addflag=' -i_dynamic' ;; + efc*,ia64* | ifort*,ia64*) # Intel Fortran compiler on ia64 + tmp_addflag=' -i_dynamic -nofor_main' ;; + ifc* | ifort*) # Intel Fortran compiler + tmp_addflag=' -nofor_main' ;; + lf95*) # Lahey Fortran 8.1 + _LT_TAGVAR(whole_archive_flag_spec, $1)= + tmp_sharedflag='--shared' ;; + nagfor*) # NAGFOR 5.3 + tmp_sharedflag='-Wl,-shared' ;; + xl[[cC]]* | bgxl[[cC]]* | mpixl[[cC]]*) # IBM XL C 8.0 on PPC (deal with xlf below) + tmp_sharedflag='-qmkshrobj' + tmp_addflag= ;; + nvcc*) # Cuda Compiler Driver 2.2 + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + ;; + esac + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) # Sun C 5.9 + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + tmp_sharedflag='-G' ;; + *Sun\ F*) # Sun Fortran 8.3 + tmp_sharedflag='-G' ;; + esac + _LT_TAGVAR(archive_cmds, $1)='$CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC '"$tmp_sharedflag""$tmp_addflag"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + + case $cc_basename in + tcc*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='-rdynamic' + ;; + xlf* | bgf* | bgxlf* | mpixlf*) + # IBM XL Fortran 10.1 on PPC cannot create shared libs itself + _LT_TAGVAR(whole_archive_flag_spec, $1)='--whole-archive$convenience --no-whole-archive' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(archive_cmds, $1)='$LD -shared $libobjs $deplibs $linker_flags -soname $soname -o $lib' + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $LD -shared $libobjs $deplibs $linker_flags -soname $soname -version-script $output_objdir/$libname.ver -o $lib' + fi + ;; + esac + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable $libobjs $deplibs $linker_flags -o $lib' + wlarc= + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + fi + ;; + + solaris*) + if $LD -v 2>&1 | $GREP 'BFD 2\.8' > /dev/null; then + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: The releases 2.8.* of the GNU linker cannot reliably +*** create shared libraries on Solaris systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.9.1 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + elif $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + sysv5* | sco3.2v5* | sco5v6* | unixware* | OpenUNIX*) + case `$LD -v 2>&1` in + *\ [[01]].* | *\ 2.[[0-9]].* | *\ 2.1[[0-5]].*) + _LT_TAGVAR(ld_shlibs, $1)=no + cat <<_LT_EOF 1>&2 + +*** Warning: Releases of the GNU linker prior to 2.16.91.0.3 cannot +*** reliably create shared libraries on SCO systems. Therefore, libtool +*** is disabling shared libraries support. We urge you to upgrade GNU +*** binutils to release 2.16.91.0.3 or newer. Another option is to modify +*** your PATH or compiler configuration so that the native linker is +*** used, and then restart. + +_LT_EOF + ;; + *) + # For security reasons, it is highly recommended that you always + # use absolute paths for naming shared libraries, and exclude the + # DT_RUNPATH tag from executables and libraries. But doing so + # requires that you compile everything twice, which is a pain. + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + sunos4*) + _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bshareable -o $lib $libobjs $deplibs $linker_flags' + wlarc= + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + + if test no = "$_LT_TAGVAR(ld_shlibs, $1)"; then + runpath_var= + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)= + _LT_TAGVAR(export_dynamic_flag_spec, $1)= + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + # PORTME fill in a description of your system's linker (not GNU ld) + case $host_os in + aix3*) + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$LD -o $output_objdir/$soname $libobjs $deplibs $linker_flags -bE:$export_symbols -T512 -H512 -bM:SRE~$AR $AR_FLAGS $lib $output_objdir/$soname' + # Note: this linker hardcodes the directories in LIBPATH if there + # are no directories specified by -L. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + if test yes = "$GCC" && test -z "$lt_prog_compiler_static"; then + # Neither direct hardcoding nor static linking is supported with a + # broken collect2. + _LT_TAGVAR(hardcode_direct, $1)=unsupported + fi + ;; + + aix[[4-9]]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + # If we're using GNU nm, then we don't want the "-C" option. + # -C means demangle to GNU nm, but means don't demangle to AIX nm. + # Without the "-l" option, or with the "-B" option, AIX nm treats + # weak defined symbols like other global defined symbols, whereas + # GNU nm marks them as "W". + # While the 'weak' keyword is ignored in the Export File, we need + # it in the Import File for the 'aix-soname' feature, so we have + # to replace the "-B" option with "-P" for AIX nm. + if $NM -V 2>&1 | $GREP 'GNU' > /dev/null; then + _LT_TAGVAR(export_symbols_cmds, $1)='$NM -Bpg $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W")) && ([substr](\$ 3,1,1) != ".")) { if (\$ 2 == "W") { print \$ 3 " weak" } else { print \$ 3 } } }'\'' | sort -u > $export_symbols' + else + _LT_TAGVAR(export_symbols_cmds, $1)='`func_echo_all $NM | $SED -e '\''s/B\([[^B]]*\)$/P\1/'\''` -PCpgl $libobjs $convenience | awk '\''{ if (((\$ 2 == "T") || (\$ 2 == "D") || (\$ 2 == "B") || (\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) && ([substr](\$ 1,1,1) != ".")) { if ((\$ 2 == "W") || (\$ 2 == "V") || (\$ 2 == "Z")) { print \$ 1 " weak" } else { print \$ 1 } } }'\'' | sort -u > $export_symbols' + fi + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) + for ld_flag in $LDFLAGS; do + if (test x-brtl = "x$ld_flag" || test x-Wl,-brtl = "x$ld_flag"); then + aix_use_runtimelinking=yes + break + fi + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_TAGVAR(archive_cmds, $1)='' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # traditional, no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + ;; + esac + + if test yes = "$GCC"; then + case $host_os in aix4.[[012]]|aix4.[[012]].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + _LT_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)= + fi + ;; + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag="$shared_flag "'$wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to export. + _LT_TAGVAR(always_export_symbols, $1)=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(allow_undefined_flag, $1)='-berok' + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib' + _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok' + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared libraries. + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + amigaos*) + case $host_cpu in + powerpc) + # see comment about AmigaOS4 .so support + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='' + ;; + m68k) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/a2ixlibrary.data~$ECHO "#define NAME $libname" > $output_objdir/a2ixlibrary.data~$ECHO "#define LIBRARY_ID 1" >> $output_objdir/a2ixlibrary.data~$ECHO "#define VERSION $major" >> $output_objdir/a2ixlibrary.data~$ECHO "#define REVISION $revision" >> $output_objdir/a2ixlibrary.data~$AR $AR_FLAGS $lib $libobjs~$RANLIB $lib~(cd $output_objdir && a2ixlibrary -32)' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + ;; + + bsdi[[45]]*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)=-rdynamic + ;; + + cygwin* | mingw* | pw32* | cegcc*) + # When not using gcc, we currently assume that we are using + # Microsoft Visual C++. + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + case $cc_basename in + cl*) + # Native MSVC + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + _LT_TAGVAR(exclude_expsyms, $1)='_NULL_IMPORT_DESCRIPTOR|_IMPORT_DESCRIPTOR_.*' + _LT_TAGVAR(export_symbols_cmds, $1)='$NM $libobjs $convenience | $global_symbol_pipe | $SED -e '\''/^[[BCDGRS]][[ ]]/s/.*[[ ]]\([[^ ]]*\)/\1,DATA/'\'' | $SED -e '\''/^[[AITW]][[ ]]/s/.*[[ ]]//'\'' | sort | uniq > $export_symbols' + # Don't use ranlib + _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib' + _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # Assume MSVC wrapper + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $lib $libobjs $compiler_flags `func_echo_all "$deplibs" | $SED '\''s/ -lc$//'\''` -link -dll~linknames=' + # The linker will automatically build a .lib file if we build a DLL. + _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + # FIXME: Should let the user specify the lib program. + _LT_TAGVAR(old_archive_cmds, $1)='lib -OUT:$oldlib$oldobjs$old_deplibs' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + esac + ;; + + darwin* | rhapsody*) + _LT_DARWIN_LINKER_FEATURES($1) + ;; + + dgux*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # FreeBSD 2.2.[012] allows us to include c++rt0.o to get C++ constructor + # support. Future versions do this automatically, but an explicit c++rt0.o + # does not break anything, and helps significantly (at the cost of a little + # extra space). + freebsd2.2*) + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags /usr/lib/c++rt0.o' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # Unfortunately, older versions of FreeBSD 2 do not have this feature. + freebsd2.*) + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + # FreeBSD 3 and greater uses gcc -shared to do shared libraries. + freebsd* | dragonfly*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + hpux9*) + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $libobjs $deplibs $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$LD -b +b $install_libdir -o $output_objdir/$soname $libobjs $deplibs $linker_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_direct, $1)=yes + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + ;; + + hpux10*) + if test yes,no = "$GCC,$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags' + fi + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + fi + ;; + + hpux11*) + if test yes,no = "$GCC,$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + else + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + m4_if($1, [], [ + # Older versions of the 11.00 compiler do not understand -b yet + # (HP92453-01 A.11.01.20 doesn't, HP92453-01 B.11.X.35175-35176.GP does) + _LT_LINKER_OPTION([if $CC understands -b], + _LT_TAGVAR(lt_cv_prog_compiler__b, $1), [-b], + [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags'], + [_LT_TAGVAR(archive_cmds, $1)='$LD -b +h $soname +b $install_libdir -o $lib $libobjs $deplibs $linker_flags'])], + [_LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $libobjs $deplibs $compiler_flags']) + ;; + esac + fi + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + case $host_cpu in + hppa*64*|ia64*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + *) + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + + # hardcode_minus_L: Not really in the search PATH, + # but as the default location of the library. + _LT_TAGVAR(hardcode_minus_L, $1)=yes + ;; + esac + fi + ;; + + irix5* | irix6* | nonstopux*) + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + # Try to use the -exported_symbol ld option, if it does not + # work, assume that -exports_file does not work either and + # implicitly export all symbols. + # This should be the same for all languages, so no per-tag cache variable. + AC_CACHE_CHECK([whether the $host_os linker accepts -exported_symbol], + [lt_cv_irix_exported_symbol], + [save_LDFLAGS=$LDFLAGS + LDFLAGS="$LDFLAGS -shared $wl-exported_symbol ${wl}foo $wl-update_registry $wl/dev/null" + AC_LINK_IFELSE( + [AC_LANG_SOURCE( + [AC_LANG_CASE([C], [[int foo (void) { return 0; }]], + [C++], [[int foo (void) { return 0; }]], + [Fortran 77], [[ + subroutine foo + end]], + [Fortran], [[ + subroutine foo + end]])])], + [lt_cv_irix_exported_symbol=yes], + [lt_cv_irix_exported_symbol=no]) + LDFLAGS=$save_LDFLAGS]) + if test yes = "$lt_cv_irix_exported_symbol"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations $wl-exports_file $wl$export_symbols -o $lib' + fi + _LT_TAGVAR(link_all_deplibs, $1)=no + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -exports_file $export_symbols -o $lib' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(inherit_rpath, $1)=yes + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + linux*) + case $cc_basename in + tcc*) + # Fabrice Bellard et al's Tiny C Compiler + _LT_TAGVAR(ld_shlibs, $1)=yes + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + netbsd* | netbsdelf*-gnu) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $libobjs $deplibs $linker_flags' # a.out + else + _LT_TAGVAR(archive_cmds, $1)='$LD -shared -o $lib $libobjs $deplibs $linker_flags' # ELF + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + newsos6) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *nto* | *qnx*) + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + if test -z "`echo __ELF__ | $CC -E - | $GREP __ELF__`"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags $wl-retain-symbols-file,$export_symbols' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + fi + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + osf3*) + if test yes = "$GCC"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + osf4* | osf5*) # as osf3* with the addition of -msym flag + if test yes = "$GCC"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $pic_flag $libobjs $deplibs $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + else + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $libobjs $deplibs $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done; printf "%s\\n" "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $wl-input $wl$lib.exp $compiler_flags $libobjs $deplibs -soname $soname `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~$RM $lib.exp' + + # Both c and cxx compiler support -rpath directly + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)='no' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + ;; + + solaris*) + _LT_TAGVAR(no_undefined_flag, $1)=' -z defs' + if test yes = "$GCC"; then + wlarc='$wl' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $wl-z ${wl}text $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag $wl-z ${wl}text $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + else + case `$CC -V 2>&1` in + *"Compilers 5.0"*) + wlarc='' + _LT_TAGVAR(archive_cmds, $1)='$LD -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $LD -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $linker_flags~$RM $lib.exp' + ;; + *) + wlarc='$wl' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h $soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag -M $lib.exp -h $soname -o $lib $libobjs $deplibs $compiler_flags~$RM $lib.exp' + ;; + esac + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. GCC discards it without '$wl', + # but is careful enough not to reorder. + # Supported since Solaris 2.6 (maybe 2.5.1?) + if test yes = "$GCC"; then + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' + fi + ;; + esac + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + sunos4*) + if test sequent = "$host_vendor"; then + # Use $CC to link under sequent, because it throws in some extra .o + # files that make .init and .fini sections work. + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h $soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$LD -assert pure-text -Bstatic -o $lib $libobjs $deplibs $linker_flags' + fi + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4) + case $host_vendor in + sni) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=yes # is this really true??? + ;; + siemens) + ## LD is ld it makes a PLAMLIB + ## CC just makes a GrossModule. + _LT_TAGVAR(archive_cmds, $1)='$LD -G -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(reload_cmds, $1)='$CC -r -o $output$reload_objs' + _LT_TAGVAR(hardcode_direct, $1)=no + ;; + motorola) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_direct, $1)=no #Motorola manual says yes, but my tests say they lie + ;; + esac + runpath_var='LD_RUN_PATH' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + sysv4.3*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(export_dynamic_flag_spec, $1)='-Bexport' + ;; + + sysv4*MP*) + if test -d /usr/nec; then + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var=LD_RUN_PATH + hardcode_runpath_var=yes + _LT_TAGVAR(ld_shlibs, $1)=yes + fi + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + if test yes = "$GCC"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + fi + ;; + + uts4*) + _LT_TAGVAR(archive_cmds, $1)='$LD -G -h $soname -o $lib $libobjs $deplibs $linker_flags' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + + *) + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + + if test sni = "$host_vendor"; then + case $host in + sysv4 | sysv4.2uw2* | sysv4.3* | sysv5*) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Blargedynsym' + ;; + esac + fi + fi +]) +AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) +test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no + +_LT_TAGVAR(with_gnu_ld, $1)=$with_gnu_ld + +_LT_DECL([], [libext], [0], [Old archive suffix (normally "a")])dnl +_LT_DECL([], [shrext_cmds], [1], [Shared library suffix (normally ".so")])dnl +_LT_DECL([], [extract_expsyms_cmds], [2], + [The commands to extract the exported symbol list from a shared archive]) + +# +# Do we need to explicitly link libc? +# +case "x$_LT_TAGVAR(archive_cmds_need_lc, $1)" in +x|xyes) + # Assume -lc should be added + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + + if test yes,yes = "$GCC,$enable_shared"; then + case $_LT_TAGVAR(archive_cmds, $1) in + *'~'*) + # FIXME: we may have to deal with multi-command sequences. + ;; + '$CC '*) + # Test whether the compiler implicitly links with -lc since on some + # systems, -lgcc has to come before -lc. If gcc already passes -lc + # to ld, don't add -lc before -lgcc. + AC_CACHE_CHECK([whether -lc should be explicitly linked in], + [lt_cv_]_LT_TAGVAR(archive_cmds_need_lc, $1), + [$RM conftest* + echo "$lt_simple_compile_test_code" > conftest.$ac_ext + + if AC_TRY_EVAL(ac_compile) 2>conftest.err; then + soname=conftest + lib=conftest + libobjs=conftest.$ac_objext + deplibs= + wl=$_LT_TAGVAR(lt_prog_compiler_wl, $1) + pic_flag=$_LT_TAGVAR(lt_prog_compiler_pic, $1) + compiler_flags=-v + linker_flags=-v + verstring= + output_objdir=. + libname=conftest + lt_save_allow_undefined_flag=$_LT_TAGVAR(allow_undefined_flag, $1) + _LT_TAGVAR(allow_undefined_flag, $1)= + if AC_TRY_EVAL(_LT_TAGVAR(archive_cmds, $1) 2\>\&1 \| $GREP \" -lc \" \>/dev/null 2\>\&1) + then + lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=no + else + lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1)=yes + fi + _LT_TAGVAR(allow_undefined_flag, $1)=$lt_save_allow_undefined_flag + else + cat conftest.err 1>&5 + fi + $RM conftest* + ]) + _LT_TAGVAR(archive_cmds_need_lc, $1)=$lt_cv_[]_LT_TAGVAR(archive_cmds_need_lc, $1) + ;; + esac + fi + ;; +esac + +_LT_TAGDECL([build_libtool_need_lc], [archive_cmds_need_lc], [0], + [Whether or not to add -lc for building shared libraries]) +_LT_TAGDECL([allow_libtool_libs_with_static_runtimes], + [enable_shared_with_static_runtimes], [0], + [Whether or not to disallow shared libs when runtime libs are static]) +_LT_TAGDECL([], [export_dynamic_flag_spec], [1], + [Compiler flag to allow reflexive dlopens]) +_LT_TAGDECL([], [whole_archive_flag_spec], [1], + [Compiler flag to generate shared objects directly from archives]) +_LT_TAGDECL([], [compiler_needs_object], [1], + [Whether the compiler copes with passing no objects directly]) +_LT_TAGDECL([], [old_archive_from_new_cmds], [2], + [Create an old-style archive from a shared archive]) +_LT_TAGDECL([], [old_archive_from_expsyms_cmds], [2], + [Create a temporary old-style archive to link instead of a shared archive]) +_LT_TAGDECL([], [archive_cmds], [2], [Commands used to build a shared archive]) +_LT_TAGDECL([], [archive_expsym_cmds], [2]) +_LT_TAGDECL([], [module_cmds], [2], + [Commands used to build a loadable module if different from building + a shared archive.]) +_LT_TAGDECL([], [module_expsym_cmds], [2]) +_LT_TAGDECL([], [with_gnu_ld], [1], + [Whether we are building with GNU ld or not]) +_LT_TAGDECL([], [allow_undefined_flag], [1], + [Flag that allows shared libraries with undefined symbols to be built]) +_LT_TAGDECL([], [no_undefined_flag], [1], + [Flag that enforces no undefined symbols]) +_LT_TAGDECL([], [hardcode_libdir_flag_spec], [1], + [Flag to hardcode $libdir into a binary during linking. + This must work even if $libdir does not exist]) +_LT_TAGDECL([], [hardcode_libdir_separator], [1], + [Whether we need a single "-rpath" flag with a separated argument]) +_LT_TAGDECL([], [hardcode_direct], [0], + [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes + DIR into the resulting binary]) +_LT_TAGDECL([], [hardcode_direct_absolute], [0], + [Set to "yes" if using DIR/libNAME$shared_ext during linking hardcodes + DIR into the resulting binary and the resulting library dependency is + "absolute", i.e impossible to change by setting $shlibpath_var if the + library is relocated]) +_LT_TAGDECL([], [hardcode_minus_L], [0], + [Set to "yes" if using the -LDIR flag during linking hardcodes DIR + into the resulting binary]) +_LT_TAGDECL([], [hardcode_shlibpath_var], [0], + [Set to "yes" if using SHLIBPATH_VAR=DIR during linking hardcodes DIR + into the resulting binary]) +_LT_TAGDECL([], [hardcode_automatic], [0], + [Set to "yes" if building a shared library automatically hardcodes DIR + into the library and all subsequent libraries and executables linked + against it]) +_LT_TAGDECL([], [inherit_rpath], [0], + [Set to yes if linker adds runtime paths of dependent libraries + to runtime path list]) +_LT_TAGDECL([], [link_all_deplibs], [0], + [Whether libtool must link a program against all its dependency libraries]) +_LT_TAGDECL([], [always_export_symbols], [0], + [Set to "yes" if exported symbols are required]) +_LT_TAGDECL([], [export_symbols_cmds], [2], + [The commands to list exported symbols]) +_LT_TAGDECL([], [exclude_expsyms], [1], + [Symbols that should not be listed in the preloaded symbols]) +_LT_TAGDECL([], [include_expsyms], [1], + [Symbols that must always be exported]) +_LT_TAGDECL([], [prelink_cmds], [2], + [Commands necessary for linking programs (against libraries) with templates]) +_LT_TAGDECL([], [postlink_cmds], [2], + [Commands necessary for finishing linking programs]) +_LT_TAGDECL([], [file_list_spec], [1], + [Specify filename containing input files]) +dnl FIXME: Not yet implemented +dnl _LT_TAGDECL([], [thread_safe_flag_spec], [1], +dnl [Compiler flag to generate thread safe objects]) +])# _LT_LINKER_SHLIBS + + +# _LT_LANG_C_CONFIG([TAG]) +# ------------------------ +# Ensure that the configuration variables for a C compiler are suitably +# defined. These variables are subsequently used by _LT_CONFIG to write +# the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_C_CONFIG], +[m4_require([_LT_DECL_EGREP])dnl +lt_save_CC=$CC +AC_LANG_PUSH(C) + +# Source file extension for C test sources. +ac_ext=c + +# Object file extension for compiled C test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="int some_variable = 0;" + +# Code to be used in simple link tests +lt_simple_link_test_code='int main(){return(0);}' + +_LT_TAG_COMPILER +# Save the default compiler, since it gets overwritten when the other +# tags are being tested, and _LT_TAGVAR(compiler, []) is a NOP. +compiler_DEFAULT=$CC + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + LT_SYS_DLOPEN_SELF + _LT_CMD_STRIPLIB + + # Report what library types will actually be built + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_CONFIG($1) +fi +AC_LANG_POP +CC=$lt_save_CC +])# _LT_LANG_C_CONFIG + + +# _LT_LANG_CXX_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for a C++ compiler are suitably +# defined. These variables are subsequently used by _LT_CONFIG to write +# the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_CXX_CONFIG], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +m4_require([_LT_DECL_EGREP])dnl +m4_require([_LT_PATH_MANIFEST_TOOL])dnl +if test -n "$CXX" && ( test no != "$CXX" && + ( (test g++ = "$CXX" && `g++ -v >/dev/null 2>&1` ) || + (test g++ != "$CXX"))); then + AC_PROG_CXXCPP +else + _lt_caught_CXX_error=yes +fi + +AC_LANG_PUSH(C++) +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(compiler_needs_object, $1)=no +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_shlibpath_var, $1)=unsupported +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for C++ test sources. +ac_ext=cpp + +# Object file extension for compiled C++ test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the CXX compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_caught_CXX_error"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="int some_variable = 0;" + + # Code to be used in simple link tests + lt_simple_link_test_code='int main(int, char *[[]]) { return(0); }' + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_CFLAGS=$CFLAGS + lt_save_LD=$LD + lt_save_GCC=$GCC + GCC=$GXX + lt_save_with_gnu_ld=$with_gnu_ld + lt_save_path_LD=$lt_cv_path_LD + if test -n "${lt_cv_prog_gnu_ldcxx+set}"; then + lt_cv_prog_gnu_ld=$lt_cv_prog_gnu_ldcxx + else + $as_unset lt_cv_prog_gnu_ld + fi + if test -n "${lt_cv_path_LDCXX+set}"; then + lt_cv_path_LD=$lt_cv_path_LDCXX + else + $as_unset lt_cv_path_LD + fi + test -z "${LDCXX+set}" || LD=$LDCXX + CC=${CXX-"c++"} + CFLAGS=$CXXFLAGS + compiler=$CC + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + + if test -n "$compiler"; then + # We don't want -fno-exception when compiling C++ code, so set the + # no_builtin_flag separately + if test yes = "$GXX"; then + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)=' -fno-builtin' + else + _LT_TAGVAR(lt_prog_compiler_no_builtin_flag, $1)= + fi + + if test yes = "$GXX"; then + # Set up default GNU C++ configuration + + LT_PATH_LD + + # Check if GNU C++ uses GNU ld as the underlying linker, since the + # archiving commands below assume that GNU ld is being used. + if test yes = "$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC $pic_flag -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + + # If archive_cmds runs LD, not CC, wlarc should be empty + # XXX I think wlarc can be eliminated in ltcf-cxx, but I need to + # investigate it a little bit more. (MM) + wlarc='$wl' + + # ancient GNU ld didn't support --whole-archive et. al. + if eval "`$CC -print-prog-name=ld` --help 2>&1" | + $GREP 'no-whole-archive' > /dev/null; then + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + else + _LT_TAGVAR(whole_archive_flag_spec, $1)= + fi + else + with_gnu_ld=no + wlarc= + + # A generic and very simple default shared library creation + # command for GNU C++ for the case where it uses the native + # linker, instead of GNU ld. If possible, this setting should + # overridden to take advantage of the native linker features on + # the platform it is being used on. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + fi + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + + else + GXX=no + with_gnu_ld=no + wlarc= + fi + + # PORTME: fill in a description of your system's C++ link characteristics + AC_MSG_CHECKING([whether the $compiler linker ($LD) supports shared libraries]) + _LT_TAGVAR(ld_shlibs, $1)=yes + case $host_os in + aix3*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aix[[4-9]]*) + if test ia64 = "$host_cpu"; then + # On IA64, the linker does run time linking by default, so we don't + # have to do anything special. + aix_use_runtimelinking=no + exp_sym_flag='-Bexport' + no_entry_flag= + else + aix_use_runtimelinking=no + + # Test if we are trying to use run time linking or normal + # AIX style linking. If -brtl is somewhere in LDFLAGS, we + # have runtime linking enabled, and use it for executables. + # For shared libraries, we enable/disable runtime linking + # depending on the kind of the shared library created - + # when "with_aix_soname,aix_use_runtimelinking" is: + # "aix,no" lib.a(lib.so.V) shared, rtl:no, for executables + # "aix,yes" lib.so shared, rtl:yes, for executables + # lib.a static archive + # "both,no" lib.so.V(shr.o) shared, rtl:yes + # lib.a(lib.so.V) shared, rtl:no, for executables + # "both,yes" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a(lib.so.V) shared, rtl:no + # "svr4,*" lib.so.V(shr.o) shared, rtl:yes, for executables + # lib.a static archive + case $host_os in aix4.[[23]]|aix4.[[23]].*|aix[[5-9]]*) + for ld_flag in $LDFLAGS; do + case $ld_flag in + *-brtl*) + aix_use_runtimelinking=yes + break + ;; + esac + done + if test svr4,no = "$with_aix_soname,$aix_use_runtimelinking"; then + # With aix-soname=svr4, we create the lib.so.V shared archives only, + # so we don't have lib.a shared libs to link our executables. + # We have to force runtime linking in this case. + aix_use_runtimelinking=yes + LDFLAGS="$LDFLAGS -Wl,-brtl" + fi + ;; + esac + + exp_sym_flag='-bexport' + no_entry_flag='-bnoentry' + fi + + # When large executables or shared objects are built, AIX ld can + # have problems creating the table of contents. If linking a library + # or program results in "error TOC overflow" add -mminimal-toc to + # CXXFLAGS/CFLAGS for g++/gcc. In the cases where that is not + # enough to fix the problem, add -Wl,-bbigtoc to LDFLAGS. + + _LT_TAGVAR(archive_cmds, $1)='' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='$wl-f,' + case $with_aix_soname,$aix_use_runtimelinking in + aix,*) ;; # no import file + svr4,* | *,yes) # use import file + # The Import File defines what to hardcode. + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=no + ;; + esac + + if test yes = "$GXX"; then + case $host_os in aix4.[[012]]|aix4.[[012]].*) + # We only want to do this on AIX 4.2 and lower, the check + # below for broken collect2 doesn't work under 4.3+ + collect2name=`$CC -print-prog-name=collect2` + if test -f "$collect2name" && + strings "$collect2name" | $GREP resolve_lib_name >/dev/null + then + # We have reworked collect2 + : + else + # We have old collect2 + _LT_TAGVAR(hardcode_direct, $1)=unsupported + # It fails to find uninstalled libraries when the uninstalled + # path is not listed in the libpath. Setting hardcode_minus_L + # to unsupported forces relinking + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)= + fi + esac + shared_flag='-shared' + if test yes = "$aix_use_runtimelinking"; then + shared_flag=$shared_flag' $wl-G' + fi + # Need to ensure runtime linking is disabled for the traditional + # shared library, or the linker may eventually find shared libraries + # /with/ Import File - we do not want to mix them. + shared_flag_aix='-shared' + shared_flag_svr4='-shared $wl-G' + else + # not using gcc + if test ia64 = "$host_cpu"; then + # VisualAge C++, Version 5.5 for AIX 5L for IA-64, Beta 3 Release + # chokes on -Wl,-G. The following line is correct: + shared_flag='-G' + else + if test yes = "$aix_use_runtimelinking"; then + shared_flag='$wl-G' + else + shared_flag='$wl-bM:SRE' + fi + shared_flag_aix='$wl-bM:SRE' + shared_flag_svr4='$wl-G' + fi + fi + + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-bexpall' + # It seems that -bexpall does not export symbols beginning with + # underscore (_), so it is better to generate a list of symbols to + # export. + _LT_TAGVAR(always_export_symbols, $1)=yes + if test aix,yes = "$with_aix_soname,$aix_use_runtimelinking"; then + # Warning - without using the other runtime loading flags (-brtl), + # -berok will link without error, but may produce a broken library. + # The "-G" linker flag allows undefined symbols. + _LT_TAGVAR(no_undefined_flag, $1)='-bernotok' + # Determine the default libpath from the value encoded in an empty + # executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $deplibs $wl'$no_entry_flag' $compiler_flags `if test -n "$allow_undefined_flag"; then func_echo_all "$wl$allow_undefined_flag"; else :; fi` $wl'$exp_sym_flag:\$export_symbols' '$shared_flag + else + if test ia64 = "$host_cpu"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $libdir:/usr/lib:/lib' + _LT_TAGVAR(allow_undefined_flag, $1)="-z nodefs" + _LT_TAGVAR(archive_expsym_cmds, $1)="\$CC $shared_flag"' -o $output_objdir/$soname $libobjs $deplibs '"\$wl$no_entry_flag"' $compiler_flags $wl$allow_undefined_flag '"\$wl$exp_sym_flag:\$export_symbols" + else + # Determine the default libpath from the value encoded in an + # empty executable. + _LT_SYS_MODULE_PATH_AIX([$1]) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-blibpath:$libdir:'"$aix_libpath" + # Warning - without using the other run time loading flags, + # -berok will link without error, but may produce a broken library. + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-bernotok' + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-berok' + if test yes = "$with_gnu_ld"; then + # We only use this code for GNU lds that support --whole-archive. + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + else + # Exported symbols can be pulled into shared objects from archives + _LT_TAGVAR(whole_archive_flag_spec, $1)='$convenience' + fi + _LT_TAGVAR(archive_cmds_need_lc, $1)=yes + _LT_TAGVAR(archive_expsym_cmds, $1)='$RM -r $output_objdir/$realname.d~$MKDIR $output_objdir/$realname.d' + # -brtl affects multiple linker settings, -berok does not and is overridden later + compiler_flags_filtered='`func_echo_all "$compiler_flags " | $SED -e "s%-brtl\\([[, ]]\\)%-berok\\1%g"`' + if test svr4 != "$with_aix_soname"; then + # This is similar to how AIX traditionally builds its shared + # libraries. Need -bnortl late, we may have -brtl in LDFLAGS. + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_aix' -o $output_objdir/$realname.d/$soname $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$AR $AR_FLAGS $output_objdir/$libname$release.a $output_objdir/$realname.d/$soname' + fi + if test aix != "$with_aix_soname"; then + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$CC '$shared_flag_svr4' -o $output_objdir/$realname.d/$shared_archive_member_spec.o $libobjs $deplibs $wl-bnoentry '$compiler_flags_filtered'$wl-bE:$export_symbols$allow_undefined_flag~$STRIP -e $output_objdir/$realname.d/$shared_archive_member_spec.o~( func_echo_all "#! $soname($shared_archive_member_spec.o)"; if test shr_64 = "$shared_archive_member_spec"; then func_echo_all "# 64"; else func_echo_all "# 32"; fi; cat $export_symbols ) > $output_objdir/$realname.d/$shared_archive_member_spec.imp~$AR $AR_FLAGS $output_objdir/$soname $output_objdir/$realname.d/$shared_archive_member_spec.o $output_objdir/$realname.d/$shared_archive_member_spec.imp' + else + # used by -dlpreopen to get the symbols + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$MV $output_objdir/$realname.d/$soname $output_objdir' + fi + _LT_TAGVAR(archive_expsym_cmds, $1)="$_LT_TAGVAR(archive_expsym_cmds, $1)"'~$RM -r $output_objdir/$realname.d' + fi + fi + ;; + + beos*) + if $LD --help 2>&1 | $GREP ': supported targets:.* elf' > /dev/null; then + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + # Joseph Beckenbach says some releases of gcc + # support --undefined. This deserves some investigation. FIXME + _LT_TAGVAR(archive_cmds, $1)='$CC -nostart $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + chorus*) + case $cc_basename in + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + cygwin* | mingw* | pw32* | cegcc*) + case $GXX,$cc_basename in + ,cl* | no,cl*) + # Native MSVC + # hardcode_libdir_flag_spec is actually meaningless, as there is + # no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)=' ' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=yes + _LT_TAGVAR(file_list_spec, $1)='@' + # Tell ltmain to make .lib files, not .a files. + libext=lib + # Tell ltmain to make .dll files, not .so files. + shrext_cmds=.dll + # FIXME: Setting linknames here is a bad hack. + _LT_TAGVAR(archive_cmds, $1)='$CC -o $output_objdir/$soname $libobjs $compiler_flags $deplibs -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~linknames=' + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp "$export_symbols" "$output_objdir/$soname.def"; + echo "$tool_output_objdir$soname.def" > "$output_objdir/$soname.exp"; + else + $SED -e '\''s/^/-link -EXPORT:/'\'' < $export_symbols > $output_objdir/$soname.exp; + fi~ + $CC -o $tool_output_objdir$soname $libobjs $compiler_flags $deplibs "@$tool_output_objdir$soname.exp" -Wl,-DLL,-IMPLIB:"$tool_output_objdir$libname.dll.lib"~ + linknames=' + # The linker will not automatically build a static lib if we build a DLL. + # _LT_TAGVAR(old_archive_from_new_cmds, $1)='true' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + # Don't use ranlib + _LT_TAGVAR(old_postinstall_cmds, $1)='chmod 644 $oldlib' + _LT_TAGVAR(postlink_cmds, $1)='lt_outputfile="@OUTPUT@"~ + lt_tool_outputfile="@TOOL_OUTPUT@"~ + case $lt_outputfile in + *.exe|*.EXE) ;; + *) + lt_outputfile=$lt_outputfile.exe + lt_tool_outputfile=$lt_tool_outputfile.exe + ;; + esac~ + func_to_tool_file "$lt_outputfile"~ + if test : != "$MANIFEST_TOOL" && test -f "$lt_outputfile.manifest"; then + $MANIFEST_TOOL -manifest "$lt_tool_outputfile.manifest" -outputresource:"$lt_tool_outputfile" || exit 1; + $RM "$lt_outputfile.manifest"; + fi' + ;; + *) + # g++ + # _LT_TAGVAR(hardcode_libdir_flag_spec, $1) is actually meaningless, + # as there is no search path for DLLs. + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-all-symbols' + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + _LT_TAGVAR(always_export_symbols, $1)=no + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + + if $LD --help 2>&1 | $GREP 'auto-import' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + # If the export-symbols file already is a .def file, use it as + # is; otherwise, prepend EXPORTS... + _LT_TAGVAR(archive_expsym_cmds, $1)='if _LT_DLL_DEF_P([$export_symbols]); then + cp $export_symbols $output_objdir/$soname.def; + else + echo EXPORTS > $output_objdir/$soname.def; + cat $export_symbols >> $output_objdir/$soname.def; + fi~ + $CC -shared -nostdlib $output_objdir/$soname.def $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $output_objdir/$soname $wl--enable-auto-image-base -Xlinker --out-implib -Xlinker $lib' + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + darwin* | rhapsody*) + _LT_DARWIN_LINKER_FEATURES($1) + ;; + + os2*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-L$libdir' + _LT_TAGVAR(hardcode_minus_L, $1)=yes + _LT_TAGVAR(allow_undefined_flag, $1)=unsupported + shrext_cmds=.dll + _LT_TAGVAR(archive_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + emxexp $libobjs | $SED /"_DLL_InitTerm"/d >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(archive_expsym_cmds, $1)='$ECHO "LIBRARY ${soname%$shared_ext} INITINSTANCE TERMINSTANCE" > $output_objdir/$libname.def~ + $ECHO "DESCRIPTION \"$libname\"" >> $output_objdir/$libname.def~ + $ECHO "DATA MULTIPLE NONSHARED" >> $output_objdir/$libname.def~ + $ECHO EXPORTS >> $output_objdir/$libname.def~ + prefix_cmds="$SED"~ + if test EXPORTS = "`$SED 1q $export_symbols`"; then + prefix_cmds="$prefix_cmds -e 1d"; + fi~ + prefix_cmds="$prefix_cmds -e \"s/^\(.*\)$/_\1/g\""~ + cat $export_symbols | $prefix_cmds >> $output_objdir/$libname.def~ + $CC -Zdll -Zcrtdll -o $output_objdir/$soname $libobjs $deplibs $compiler_flags $output_objdir/$libname.def~ + emximp -o $lib $output_objdir/$libname.def' + _LT_TAGVAR(old_archive_From_new_cmds, $1)='emximp -o $output_objdir/${libname}_dll.a $output_objdir/$libname.def' + _LT_TAGVAR(enable_shared_with_static_runtimes, $1)=yes + ;; + + dgux*) + case $cc_basename in + ec++*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + ghcx*) + # Green Hills C++ Compiler + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + freebsd2.*) + # C++ shared libraries reported to be fairly broken before + # switch to ELF + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + freebsd-elf*) + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + ;; + + freebsd* | dragonfly*) + # FreeBSD 3 and later use GNU C++ and GNU ld with standard ELF + # conventions + _LT_TAGVAR(ld_shlibs, $1)=yes + ;; + + haiku*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + + hpux9*) + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aCC*) + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -b $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $EGREP "\-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes = "$GXX"; then + _LT_TAGVAR(archive_cmds, $1)='$RM $output_objdir/$soname~$CC -shared -nostdlib $pic_flag $wl+b $wl$install_libdir -o $output_objdir/$soname $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~test "x$output_objdir/$soname" = "x$lib" || mv $output_objdir/$soname $lib' + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + hpux10*|hpux11*) + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl+b $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + case $host_cpu in + hppa*64*|ia64*) + ;; + *) + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + ;; + esac + fi + case $host_cpu in + hppa*64*|ia64*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + ;; + *) + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(hardcode_minus_L, $1)=yes # Not in the search PATH, + # but as the default + # location of the library. + ;; + esac + + case $cc_basename in + CC*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + aCC*) + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -b $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`($CC -b $CFLAGS -v conftest.$objext 2>&1) | $GREP "\-L"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes = "$GXX"; then + if test no = "$with_gnu_ld"; then + case $host_cpu in + hppa*64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib -fPIC $wl+h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + ia64*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+nodefaultrpath -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $pic_flag $wl+h $wl$soname $wl+b $wl$install_libdir -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + ;; + esac + fi + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + interix[[3-9]]*) + _LT_TAGVAR(hardcode_direct, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + # Hack: On Interix 3.x, we cannot compile PIC because of a broken gcc. + # Instead, shared libraries are loaded at an image base (0x10000000 by + # default) and relocated if they conflict, which is a slow very memory + # consuming and fragmenting process. To avoid this, we pick a random, + # 256 KiB-aligned image base between 0x50000000 and 0x6FFC0000 at link + # time. Moving up from 0x10000000 also allows more sbrk(2) space. + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='sed "s|^|_|" $export_symbols >$output_objdir/$soname.expsym~$CC -shared $pic_flag $libobjs $deplibs $compiler_flags $wl-h,$soname $wl--retain-symbols-file,$output_objdir/$soname.expsym $wl--image-base,`expr ${RANDOM-$$} % 4096 / 2 \* 262144 + 1342177280` -o $lib' + ;; + irix5* | irix6*) + case $cc_basename in + CC*) + # SGI C++ + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -all -multigot $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + + # Archives containing C++ object files must be created using + # "CC -ar", where "CC" is the IRIX C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -ar -WR,-u -o $oldlib $oldobjs' + ;; + *) + if test yes = "$GXX"; then + if test no = "$with_gnu_ld"; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + else + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` -o $lib' + fi + fi + _LT_TAGVAR(link_all_deplibs, $1)=yes + ;; + esac + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + _LT_TAGVAR(inherit_rpath, $1)=yes + ;; + + linux* | k*bsd*-gnu | kopensolaris*-gnu | gnu*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo $lib | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib $wl-retain-symbols-file,$export_symbols; mv \$templib $lib' + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 | $GREP "ld"`; rm -f libconftest$shared_ext; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + + # Archives containing C++ object files must be created using + # "CC -Bstatic", where "CC" is the KAI C++ compiler. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' + ;; + icpc* | ecpc* ) + # Intel C++ + with_gnu_ld=yes + # version 8.0 and above of icpc choke on multiply defined symbols + # if we add $predep_objects and $postdep_objects, however 7.1 and + # earlier do not add the objects themselves. + case `$CC -V 2>&1` in + *"Version 7."*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 8.0 or newer + tmp_idyn= + case $host_cpu in + ia64*) tmp_idyn=' -i_dynamic';; + esac + _LT_TAGVAR(archive_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared'"$tmp_idyn"' $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive$convenience $wl--no-whole-archive' + ;; + pgCC* | pgcpp*) + # Portland Group C++ compiler + case `$CC -V` in + *pgCC\ [[1-5]].* | *pgcpp\ [[1-5]].*) + _LT_TAGVAR(prelink_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $objs $libobjs $compile_deplibs~ + compile_command="$compile_command `find $tpldir -name \*.o | sort | $NL2SP`"' + _LT_TAGVAR(old_archive_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $oldobjs$old_deplibs~ + $AR $AR_FLAGS $oldlib$oldobjs$old_deplibs `find $tpldir -name \*.o | sort | $NL2SP`~ + $RANLIB $oldlib' + _LT_TAGVAR(archive_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='tpldir=Template.dir~ + rm -rf $tpldir~ + $CC --prelink_objects --instantiation_dir $tpldir $predep_objects $libobjs $deplibs $convenience $postdep_objects~ + $CC -shared $pic_flag $predep_objects $libobjs $deplibs `find $tpldir -name \*.o | sort | $NL2SP` $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + *) # Version 6 and above use weak symbols + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname $wl-retain-symbols-file $wl$export_symbols -o $lib' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl--rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`for conv in $convenience\"\"; do test -n \"$conv\" && new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + ;; + cxx*) + # Compaq C++ + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname -o $lib $wl-retain-symbols-file $wl$export_symbols' + + runpath_var=LD_RUN_PATH + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld .*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "X$list" | $Xsed' + ;; + xl* | mpixl* | bgxl*) + # IBM XL 8.0 on PPC, with GNU ld + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl--export-dynamic' + _LT_TAGVAR(archive_cmds, $1)='$CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname -o $lib' + if test yes = "$supports_anon_versioning"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $output_objdir/$libname.ver~ + cat $export_symbols | sed -e "s/\(.*\)/\1;/" >> $output_objdir/$libname.ver~ + echo "local: *; };" >> $output_objdir/$libname.ver~ + $CC -qmkshrobj $libobjs $deplibs $compiler_flags $wl-soname $wl$soname $wl-version-script $wl$output_objdir/$libname.ver -o $lib' + fi + ;; + *) + case `$CC -V 2>&1 | sed 5q` in + *Sun\ C*) + # Sun C++ 5.9 + _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file $wl$export_symbols' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl--whole-archive`new_convenience=; for conv in $convenience\"\"; do test -z \"$conv\" || new_convenience=\"$new_convenience,$conv\"; done; func_echo_all \"$new_convenience\"` $wl--no-whole-archive' + _LT_TAGVAR(compiler_needs_object, $1)=yes + + # Not sure whether something based on + # $CC $CFLAGS -v conftest.$objext -o libconftest$shared_ext 2>&1 + # would be better. + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + esac + ;; + esac + ;; + + lynxos*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + m88k*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + mvs*) + case $cc_basename in + cxx*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + netbsd*) + if echo __ELF__ | $CC -E - | $GREP __ELF__ >/dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$LD -Bshareable -o $lib $predep_objects $libobjs $deplibs $postdep_objects $linker_flags' + wlarc= + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + fi + # Workaround some broken pre-1.5 toolchains + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP conftest.$objext | $SED -e "s:-lgcc -lc -lgcc::"' + ;; + + *nto* | *qnx*) + _LT_TAGVAR(ld_shlibs, $1)=yes + ;; + + openbsd* | bitrig*) + if test -f /usr/libexec/ld.so; then + _LT_TAGVAR(hardcode_direct, $1)=yes + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_direct_absolute, $1)=yes + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + if test -z "`echo __ELF__ | $CC -E - | grep __ELF__`"; then + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $pic_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-retain-symbols-file,$export_symbols -o $lib' + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-E' + _LT_TAGVAR(whole_archive_flag_spec, $1)=$wlarc'--whole-archive$convenience '$wlarc'--no-whole-archive' + fi + output_verbose_link_cmd=func_echo_all + else + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + + osf3* | osf4* | osf5*) + case $cc_basename in + KCC*) + # Kuck and Associates, Inc. (KAI) C++ Compiler + + # KCC will only create a shared library if the output file + # ends with ".so" (or ".sl" for HP-UX), so rename the library + # to its proper name (with version) after linking. + _LT_TAGVAR(archive_cmds, $1)='tempext=`echo $shared_ext | $SED -e '\''s/\([[^()0-9A-Za-z{}]]\)/\\\\\1/g'\''`; templib=`echo "$lib" | $SED -e "s/\$tempext\..*/.so/"`; $CC $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags --soname $soname -o \$templib; mv \$templib $lib' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Archives containing C++ object files must be created using + # the KAI C++ compiler. + case $host in + osf3*) _LT_TAGVAR(old_archive_cmds, $1)='$CC -Bstatic -o $oldlib $oldobjs' ;; + *) _LT_TAGVAR(old_archive_cmds, $1)='$CC -o $oldlib $oldobjs' ;; + esac + ;; + RCC*) + # Rational C++ 2.4.1 + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + cxx*) + case $host in + osf3*) + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $soname `test -n "$verstring" && func_echo_all "$wl-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + ;; + *) + _LT_TAGVAR(allow_undefined_flag, $1)=' -expect_unresolved \*' + _LT_TAGVAR(archive_cmds, $1)='$CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname `test -n "$verstring" && func_echo_all "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='for i in `cat $export_symbols`; do printf "%s %s\\n" -exported_symbol "\$i" >> $lib.exp; done~ + echo "-hidden">> $lib.exp~ + $CC -shared$allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags -msym -soname $soname $wl-input $wl$lib.exp `test -n "$verstring" && $ECHO "-set_version $verstring"` -update_registry $output_objdir/so_locations -o $lib~ + $RM $lib.exp' + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-rpath $libdir' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + # + # There doesn't appear to be a way to prevent this compiler from + # explicitly linking system object files so we need to strip them + # from the output so that they don't get included in the library + # dependencies. + output_verbose_link_cmd='templist=`$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP "ld" | $GREP -v "ld:"`; templist=`func_echo_all "$templist" | $SED "s/\(^.*ld.*\)\( .*ld.*$\)/\1/"`; list= ; for z in $templist; do case $z in conftest.$objext) list="$list $z";; *.$objext);; *) list="$list $z";;esac; done; func_echo_all "$list"' + ;; + *) + if test yes,no = "$GXX,$with_gnu_ld"; then + _LT_TAGVAR(allow_undefined_flag, $1)=' $wl-expect_unresolved $wl\*' + case $host in + osf3*) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $allow_undefined_flag $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-msym $wl-soname $wl$soname `test -n "$verstring" && func_echo_all "$wl-set_version $wl$verstring"` $wl-update_registry $wl$output_objdir/so_locations -o $lib' + ;; + esac + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-rpath $wl$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=: + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + + else + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + fi + ;; + esac + ;; + + psos*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + sunos4*) + case $cc_basename in + CC*) + # Sun C++ 4.x + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + lcc*) + # Lucid + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + solaris*) + case $cc_basename in + CC* | sunCC*) + # Sun C++ 4.2, 5.x and Centerline C++ + _LT_TAGVAR(archive_cmds_need_lc,$1)=yes + _LT_TAGVAR(no_undefined_flag, $1)=' -zdefs' + _LT_TAGVAR(archive_cmds, $1)='$CC -G$allow_undefined_flag -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G$allow_undefined_flag $wl-M $wl$lib.exp -h$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='-R$libdir' + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + # The compiler driver will combine and reorder linker options, + # but understands '-z linker_flag'. + # Supported since Solaris 2.6 (maybe 2.5.1?) + _LT_TAGVAR(whole_archive_flag_spec, $1)='-z allextract$convenience -z defaultextract' + ;; + esac + _LT_TAGVAR(link_all_deplibs, $1)=yes + + output_verbose_link_cmd='func_echo_all' + + # Archives containing C++ object files must be created using + # "CC -xar", where "CC" is the Sun C++ compiler. This is + # necessary to make sure instantiated templates are included + # in the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC -xar -o $oldlib $oldobjs' + ;; + gcx*) + # Green Hills C++ Compiler + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + + # The C++ compiler must be used to create the archive. + _LT_TAGVAR(old_archive_cmds, $1)='$CC $LDFLAGS -archive -o $oldlib $oldobjs' + ;; + *) + # GNU C++ compiler with Solaris linker + if test yes,no = "$GXX,$with_gnu_ld"; then + _LT_TAGVAR(no_undefined_flag, $1)=' $wl-z ${wl}defs' + if $CC --version | $GREP -v '^2\.7' > /dev/null; then + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $pic_flag -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -shared $pic_flag -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -shared $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + else + # g++ 2.7 appears to require '-G' NOT '-shared' on this + # platform. + _LT_TAGVAR(archive_cmds, $1)='$CC -G -nostdlib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags $wl-h $wl$soname -o $lib' + _LT_TAGVAR(archive_expsym_cmds, $1)='echo "{ global:" > $lib.exp~cat $export_symbols | $SED -e "s/\(.*\)/\1;/" >> $lib.exp~echo "local: *; };" >> $lib.exp~ + $CC -G -nostdlib $wl-M $wl$lib.exp $wl-h $wl$soname -o $lib $predep_objects $libobjs $deplibs $postdep_objects $compiler_flags~$RM $lib.exp' + + # Commands to make compiler produce verbose output that lists + # what "hidden" libraries, object files and flags are used when + # linking a shared library. + output_verbose_link_cmd='$CC -G $CFLAGS -v conftest.$objext 2>&1 | $GREP -v "^Configured with:" | $GREP "\-L"' + fi + + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R $wl$libdir' + case $host_os in + solaris2.[[0-5]] | solaris2.[[0-5]].*) ;; + *) + _LT_TAGVAR(whole_archive_flag_spec, $1)='$wl-z ${wl}allextract$convenience $wl-z ${wl}defaultextract' + ;; + esac + fi + ;; + esac + ;; + + sysv4*uw2* | sysv5OpenUNIX* | sysv5UnixWare7.[[01]].[[10]]* | unixware7* | sco3.2v5.0.[[024]]*) + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + sysv5* | sco3.2v5* | sco5v6*) + # Note: We CANNOT use -z defs as we might desire, because we do not + # link with -lc, and that would cause any symbols used from libc to + # always be unresolved, which means just about no library would + # ever link correctly. If we're not using GNU ld we use -z text + # though, which does catch some bad symbols but isn't as heavy-handed + # as -z defs. + _LT_TAGVAR(no_undefined_flag, $1)='$wl-z,text' + _LT_TAGVAR(allow_undefined_flag, $1)='$wl-z,nodefs' + _LT_TAGVAR(archive_cmds_need_lc, $1)=no + _LT_TAGVAR(hardcode_shlibpath_var, $1)=no + _LT_TAGVAR(hardcode_libdir_flag_spec, $1)='$wl-R,$libdir' + _LT_TAGVAR(hardcode_libdir_separator, $1)=':' + _LT_TAGVAR(link_all_deplibs, $1)=yes + _LT_TAGVAR(export_dynamic_flag_spec, $1)='$wl-Bexport' + runpath_var='LD_RUN_PATH' + + case $cc_basename in + CC*) + _LT_TAGVAR(archive_cmds, $1)='$CC -G $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -G $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(old_archive_cmds, $1)='$CC -Tprelink_objects $oldobjs~ + '"$_LT_TAGVAR(old_archive_cmds, $1)" + _LT_TAGVAR(reload_cmds, $1)='$CC -Tprelink_objects $reload_objs~ + '"$_LT_TAGVAR(reload_cmds, $1)" + ;; + *) + _LT_TAGVAR(archive_cmds, $1)='$CC -shared $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + _LT_TAGVAR(archive_expsym_cmds, $1)='$CC -shared $wl-Bexport:$export_symbols $wl-h,$soname -o $lib $libobjs $deplibs $compiler_flags' + ;; + esac + ;; + + tandem*) + case $cc_basename in + NCC*) + # NonStop-UX NCC 3.20 + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + ;; + + vxworks*) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + + *) + # FIXME: insert proper C++ library support + _LT_TAGVAR(ld_shlibs, $1)=no + ;; + esac + + AC_MSG_RESULT([$_LT_TAGVAR(ld_shlibs, $1)]) + test no = "$_LT_TAGVAR(ld_shlibs, $1)" && can_build_shared=no + + _LT_TAGVAR(GCC, $1)=$GXX + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_SYS_HIDDEN_LIBDEPS($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS + LDCXX=$LD + LD=$lt_save_LD + GCC=$lt_save_GCC + with_gnu_ld=$lt_save_with_gnu_ld + lt_cv_path_LDCXX=$lt_cv_path_LD + lt_cv_path_LD=$lt_save_path_LD + lt_cv_prog_gnu_ldcxx=$lt_cv_prog_gnu_ld + lt_cv_prog_gnu_ld=$lt_save_with_gnu_ld +fi # test yes != "$_lt_caught_CXX_error" + +AC_LANG_POP +])# _LT_LANG_CXX_CONFIG + + +# _LT_FUNC_STRIPNAME_CNF +# ---------------------- +# func_stripname_cnf prefix suffix name +# strip PREFIX and SUFFIX off of NAME. +# PREFIX and SUFFIX must not contain globbing or regex special +# characters, hashes, percent signs, but SUFFIX may contain a leading +# dot (in which case that matches only a dot). +# +# This function is identical to the (non-XSI) version of func_stripname, +# except this one can be used by m4 code that may be executed by configure, +# rather than the libtool script. +m4_defun([_LT_FUNC_STRIPNAME_CNF],[dnl +AC_REQUIRE([_LT_DECL_SED]) +AC_REQUIRE([_LT_PROG_ECHO_BACKSLASH]) +func_stripname_cnf () +{ + case @S|@2 in + .*) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%\\\\@S|@2\$%%"`;; + *) func_stripname_result=`$ECHO "@S|@3" | $SED "s%^@S|@1%%; s%@S|@2\$%%"`;; + esac +} # func_stripname_cnf +])# _LT_FUNC_STRIPNAME_CNF + + +# _LT_SYS_HIDDEN_LIBDEPS([TAGNAME]) +# --------------------------------- +# Figure out "hidden" library dependencies from verbose +# compiler output when linking a shared library. +# Parse the compiler output and extract the necessary +# objects, libraries and library flags. +m4_defun([_LT_SYS_HIDDEN_LIBDEPS], +[m4_require([_LT_FILEUTILS_DEFAULTS])dnl +AC_REQUIRE([_LT_FUNC_STRIPNAME_CNF])dnl +# Dependencies to place before and after the object being linked: +_LT_TAGVAR(predep_objects, $1)= +_LT_TAGVAR(postdep_objects, $1)= +_LT_TAGVAR(predeps, $1)= +_LT_TAGVAR(postdeps, $1)= +_LT_TAGVAR(compiler_lib_search_path, $1)= + +dnl we can't use the lt_simple_compile_test_code here, +dnl because it contains code intended for an executable, +dnl not a library. It's possible we should let each +dnl tag define a new lt_????_link_test_code variable, +dnl but it's only used here... +m4_if([$1], [], [cat > conftest.$ac_ext <<_LT_EOF +int a; +void foo (void) { a = 0; } +_LT_EOF +], [$1], [CXX], [cat > conftest.$ac_ext <<_LT_EOF +class Foo +{ +public: + Foo (void) { a = 0; } +private: + int a; +}; +_LT_EOF +], [$1], [F77], [cat > conftest.$ac_ext <<_LT_EOF + subroutine foo + implicit none + integer*4 a + a=0 + return + end +_LT_EOF +], [$1], [FC], [cat > conftest.$ac_ext <<_LT_EOF + subroutine foo + implicit none + integer a + a=0 + return + end +_LT_EOF +], [$1], [GCJ], [cat > conftest.$ac_ext <<_LT_EOF +public class foo { + private int a; + public void bar (void) { + a = 0; + } +}; +_LT_EOF +], [$1], [GO], [cat > conftest.$ac_ext <<_LT_EOF +package foo +func foo() { +} +_LT_EOF +]) + +_lt_libdeps_save_CFLAGS=$CFLAGS +case "$CC $CFLAGS " in #( +*\ -flto*\ *) CFLAGS="$CFLAGS -fno-lto" ;; +*\ -fwhopr*\ *) CFLAGS="$CFLAGS -fno-whopr" ;; +*\ -fuse-linker-plugin*\ *) CFLAGS="$CFLAGS -fno-use-linker-plugin" ;; +esac + +dnl Parse the compiler output and extract the necessary +dnl objects, libraries and library flags. +if AC_TRY_EVAL(ac_compile); then + # Parse the compiler output and extract the necessary + # objects, libraries and library flags. + + # Sentinel used to keep track of whether or not we are before + # the conftest object file. + pre_test_object_deps_done=no + + for p in `eval "$output_verbose_link_cmd"`; do + case $prev$p in + + -L* | -R* | -l*) + # Some compilers place space between "-{L,R}" and the path. + # Remove the space. + if test x-L = "$p" || + test x-R = "$p"; then + prev=$p + continue + fi + + # Expand the sysroot to ease extracting the directories later. + if test -z "$prev"; then + case $p in + -L*) func_stripname_cnf '-L' '' "$p"; prev=-L; p=$func_stripname_result ;; + -R*) func_stripname_cnf '-R' '' "$p"; prev=-R; p=$func_stripname_result ;; + -l*) func_stripname_cnf '-l' '' "$p"; prev=-l; p=$func_stripname_result ;; + esac + fi + case $p in + =*) func_stripname_cnf '=' '' "$p"; p=$lt_sysroot$func_stripname_result ;; + esac + if test no = "$pre_test_object_deps_done"; then + case $prev in + -L | -R) + # Internal compiler library paths should come after those + # provided the user. The postdeps already come after the + # user supplied libs so there is no need to process them. + if test -z "$_LT_TAGVAR(compiler_lib_search_path, $1)"; then + _LT_TAGVAR(compiler_lib_search_path, $1)=$prev$p + else + _LT_TAGVAR(compiler_lib_search_path, $1)="${_LT_TAGVAR(compiler_lib_search_path, $1)} $prev$p" + fi + ;; + # The "-l" case would never come before the object being + # linked, so don't bother handling this case. + esac + else + if test -z "$_LT_TAGVAR(postdeps, $1)"; then + _LT_TAGVAR(postdeps, $1)=$prev$p + else + _LT_TAGVAR(postdeps, $1)="${_LT_TAGVAR(postdeps, $1)} $prev$p" + fi + fi + prev= + ;; + + *.lto.$objext) ;; # Ignore GCC LTO objects + *.$objext) + # This assumes that the test object file only shows up + # once in the compiler output. + if test "$p" = "conftest.$objext"; then + pre_test_object_deps_done=yes + continue + fi + + if test no = "$pre_test_object_deps_done"; then + if test -z "$_LT_TAGVAR(predep_objects, $1)"; then + _LT_TAGVAR(predep_objects, $1)=$p + else + _LT_TAGVAR(predep_objects, $1)="$_LT_TAGVAR(predep_objects, $1) $p" + fi + else + if test -z "$_LT_TAGVAR(postdep_objects, $1)"; then + _LT_TAGVAR(postdep_objects, $1)=$p + else + _LT_TAGVAR(postdep_objects, $1)="$_LT_TAGVAR(postdep_objects, $1) $p" + fi + fi + ;; + + *) ;; # Ignore the rest. + + esac + done + + # Clean up. + rm -f a.out a.exe +else + echo "libtool.m4: error: problem compiling $1 test program" +fi + +$RM -f confest.$objext +CFLAGS=$_lt_libdeps_save_CFLAGS + +# PORTME: override above test on systems where it is broken +m4_if([$1], [CXX], +[case $host_os in +interix[[3-9]]*) + # Interix 3.5 installs completely hosed .la files for C++, so rather than + # hack all around it, let's just trust "g++" to DTRT. + _LT_TAGVAR(predep_objects,$1)= + _LT_TAGVAR(postdep_objects,$1)= + _LT_TAGVAR(postdeps,$1)= + ;; +esac +]) + +case " $_LT_TAGVAR(postdeps, $1) " in +*" -lc "*) _LT_TAGVAR(archive_cmds_need_lc, $1)=no ;; +esac + _LT_TAGVAR(compiler_lib_search_dirs, $1)= +if test -n "${_LT_TAGVAR(compiler_lib_search_path, $1)}"; then + _LT_TAGVAR(compiler_lib_search_dirs, $1)=`echo " ${_LT_TAGVAR(compiler_lib_search_path, $1)}" | $SED -e 's! -L! !g' -e 's!^ !!'` +fi +_LT_TAGDECL([], [compiler_lib_search_dirs], [1], + [The directories searched by this compiler when creating a shared library]) +_LT_TAGDECL([], [predep_objects], [1], + [Dependencies to place before and after the objects being linked to + create a shared library]) +_LT_TAGDECL([], [postdep_objects], [1]) +_LT_TAGDECL([], [predeps], [1]) +_LT_TAGDECL([], [postdeps], [1]) +_LT_TAGDECL([], [compiler_lib_search_path], [1], + [The library search path used internally by the compiler when linking + a shared library]) +])# _LT_SYS_HIDDEN_LIBDEPS + + +# _LT_LANG_F77_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for a Fortran 77 compiler are +# suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_F77_CONFIG], +[AC_LANG_PUSH(Fortran 77) +if test -z "$F77" || test no = "$F77"; then + _lt_disable_F77=yes +fi + +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for f77 test sources. +ac_ext=f + +# Object file extension for compiled f77 test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the F77 compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_disable_F77"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="\ + subroutine t + return + end +" + + # Code to be used in simple link tests + lt_simple_link_test_code="\ + program t + end +" + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_GCC=$GCC + lt_save_CFLAGS=$CFLAGS + CC=${F77-"f77"} + CFLAGS=$FFLAGS + compiler=$CC + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + GCC=$G77 + if test -n "$compiler"; then + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_TAGVAR(GCC, $1)=$G77 + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + GCC=$lt_save_GCC + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS +fi # test yes != "$_lt_disable_F77" + +AC_LANG_POP +])# _LT_LANG_F77_CONFIG + + +# _LT_LANG_FC_CONFIG([TAG]) +# ------------------------- +# Ensure that the configuration variables for a Fortran compiler are +# suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_FC_CONFIG], +[AC_LANG_PUSH(Fortran) + +if test -z "$FC" || test no = "$FC"; then + _lt_disable_FC=yes +fi + +_LT_TAGVAR(archive_cmds_need_lc, $1)=no +_LT_TAGVAR(allow_undefined_flag, $1)= +_LT_TAGVAR(always_export_symbols, $1)=no +_LT_TAGVAR(archive_expsym_cmds, $1)= +_LT_TAGVAR(export_dynamic_flag_spec, $1)= +_LT_TAGVAR(hardcode_direct, $1)=no +_LT_TAGVAR(hardcode_direct_absolute, $1)=no +_LT_TAGVAR(hardcode_libdir_flag_spec, $1)= +_LT_TAGVAR(hardcode_libdir_separator, $1)= +_LT_TAGVAR(hardcode_minus_L, $1)=no +_LT_TAGVAR(hardcode_automatic, $1)=no +_LT_TAGVAR(inherit_rpath, $1)=no +_LT_TAGVAR(module_cmds, $1)= +_LT_TAGVAR(module_expsym_cmds, $1)= +_LT_TAGVAR(link_all_deplibs, $1)=unknown +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds +_LT_TAGVAR(no_undefined_flag, $1)= +_LT_TAGVAR(whole_archive_flag_spec, $1)= +_LT_TAGVAR(enable_shared_with_static_runtimes, $1)=no + +# Source file extension for fc test sources. +ac_ext=${ac_fc_srcext-f} + +# Object file extension for compiled fc test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# No sense in running all these tests if we already determined that +# the FC compiler isn't working. Some variables (like enable_shared) +# are currently assumed to apply to all compilers on this platform, +# and will be corrupted by setting them based on a non-working compiler. +if test yes != "$_lt_disable_FC"; then + # Code to be used in simple compile tests + lt_simple_compile_test_code="\ + subroutine t + return + end +" + + # Code to be used in simple link tests + lt_simple_link_test_code="\ + program t + end +" + + # ltmain only uses $CC for tagged configurations so make sure $CC is set. + _LT_TAG_COMPILER + + # save warnings/boilerplate of simple test code + _LT_COMPILER_BOILERPLATE + _LT_LINKER_BOILERPLATE + + # Allow CC to be a program name with arguments. + lt_save_CC=$CC + lt_save_GCC=$GCC + lt_save_CFLAGS=$CFLAGS + CC=${FC-"f95"} + CFLAGS=$FCFLAGS + compiler=$CC + GCC=$ac_cv_fc_compiler_gnu + + _LT_TAGVAR(compiler, $1)=$CC + _LT_CC_BASENAME([$compiler]) + + if test -n "$compiler"; then + AC_MSG_CHECKING([if libtool supports shared libraries]) + AC_MSG_RESULT([$can_build_shared]) + + AC_MSG_CHECKING([whether to build shared libraries]) + test no = "$can_build_shared" && enable_shared=no + + # On AIX, shared libraries and static libraries use the same namespace, and + # are all built from PIC. + case $host_os in + aix3*) + test yes = "$enable_shared" && enable_static=no + if test -n "$RANLIB"; then + archive_cmds="$archive_cmds~\$RANLIB \$lib" + postinstall_cmds='$RANLIB $lib' + fi + ;; + aix[[4-9]]*) + if test ia64 != "$host_cpu"; then + case $enable_shared,$with_aix_soname,$aix_use_runtimelinking in + yes,aix,yes) ;; # shared object as lib.so file only + yes,svr4,*) ;; # shared object as lib.so archive member only + yes,*) enable_static=no ;; # shared object in lib.a archive as well + esac + fi + ;; + esac + AC_MSG_RESULT([$enable_shared]) + + AC_MSG_CHECKING([whether to build static libraries]) + # Make sure either enable_shared or enable_static is yes. + test yes = "$enable_shared" || enable_static=yes + AC_MSG_RESULT([$enable_static]) + + _LT_TAGVAR(GCC, $1)=$ac_cv_fc_compiler_gnu + _LT_TAGVAR(LD, $1)=$LD + + ## CAVEAT EMPTOR: + ## There is no encapsulation within the following macros, do not change + ## the running order or otherwise move them around unless you know exactly + ## what you are doing... + _LT_SYS_HIDDEN_LIBDEPS($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_SYS_DYNAMIC_LINKER($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) + fi # test -n "$compiler" + + GCC=$lt_save_GCC + CC=$lt_save_CC + CFLAGS=$lt_save_CFLAGS +fi # test yes != "$_lt_disable_FC" + +AC_LANG_POP +])# _LT_LANG_FC_CONFIG + + +# _LT_LANG_GCJ_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for the GNU Java Compiler compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_GCJ_CONFIG], +[AC_REQUIRE([LT_PROG_GCJ])dnl +AC_LANG_SAVE + +# Source file extension for Java test sources. +ac_ext=java + +# Object file extension for compiled Java test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="class foo {}" + +# Code to be used in simple link tests +lt_simple_link_test_code='public class conftest { public static void main(String[[]] argv) {}; }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC=yes +CC=${GCJ-"gcj"} +CFLAGS=$GCJFLAGS +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_TAGVAR(LD, $1)=$LD +_LT_CC_BASENAME([$compiler]) + +# GCJ did not exist at the time GCC didn't implicitly link libc in. +_LT_TAGVAR(archive_cmds_need_lc, $1)=no + +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) +fi + +AC_LANG_RESTORE + +GCC=$lt_save_GCC +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_GCJ_CONFIG + + +# _LT_LANG_GO_CONFIG([TAG]) +# -------------------------- +# Ensure that the configuration variables for the GNU Go compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_GO_CONFIG], +[AC_REQUIRE([LT_PROG_GO])dnl +AC_LANG_SAVE + +# Source file extension for Go test sources. +ac_ext=go + +# Object file extension for compiled Go test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code="package main; func main() { }" + +# Code to be used in simple link tests +lt_simple_link_test_code='package main; func main() { }' + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC=yes +CC=${GOC-"gccgo"} +CFLAGS=$GOFLAGS +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_TAGVAR(LD, $1)=$LD +_LT_CC_BASENAME([$compiler]) + +# Go did not exist at the time GCC didn't implicitly link libc in. +_LT_TAGVAR(archive_cmds_need_lc, $1)=no + +_LT_TAGVAR(old_archive_cmds, $1)=$old_archive_cmds +_LT_TAGVAR(reload_flag, $1)=$reload_flag +_LT_TAGVAR(reload_cmds, $1)=$reload_cmds + +## CAVEAT EMPTOR: +## There is no encapsulation within the following macros, do not change +## the running order or otherwise move them around unless you know exactly +## what you are doing... +if test -n "$compiler"; then + _LT_COMPILER_NO_RTTI($1) + _LT_COMPILER_PIC($1) + _LT_COMPILER_C_O($1) + _LT_COMPILER_FILE_LOCKS($1) + _LT_LINKER_SHLIBS($1) + _LT_LINKER_HARDCODE_LIBPATH($1) + + _LT_CONFIG($1) +fi + +AC_LANG_RESTORE + +GCC=$lt_save_GCC +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_GO_CONFIG + + +# _LT_LANG_RC_CONFIG([TAG]) +# ------------------------- +# Ensure that the configuration variables for the Windows resource compiler +# are suitably defined. These variables are subsequently used by _LT_CONFIG +# to write the compiler configuration to 'libtool'. +m4_defun([_LT_LANG_RC_CONFIG], +[AC_REQUIRE([LT_PROG_RC])dnl +AC_LANG_SAVE + +# Source file extension for RC test sources. +ac_ext=rc + +# Object file extension for compiled RC test sources. +objext=o +_LT_TAGVAR(objext, $1)=$objext + +# Code to be used in simple compile tests +lt_simple_compile_test_code='sample MENU { MENUITEM "&Soup", 100, CHECKED }' + +# Code to be used in simple link tests +lt_simple_link_test_code=$lt_simple_compile_test_code + +# ltmain only uses $CC for tagged configurations so make sure $CC is set. +_LT_TAG_COMPILER + +# save warnings/boilerplate of simple test code +_LT_COMPILER_BOILERPLATE +_LT_LINKER_BOILERPLATE + +# Allow CC to be a program name with arguments. +lt_save_CC=$CC +lt_save_CFLAGS=$CFLAGS +lt_save_GCC=$GCC +GCC= +CC=${RC-"windres"} +CFLAGS= +compiler=$CC +_LT_TAGVAR(compiler, $1)=$CC +_LT_CC_BASENAME([$compiler]) +_LT_TAGVAR(lt_cv_prog_compiler_c_o, $1)=yes + +if test -n "$compiler"; then + : + _LT_CONFIG($1) +fi + +GCC=$lt_save_GCC +AC_LANG_RESTORE +CC=$lt_save_CC +CFLAGS=$lt_save_CFLAGS +])# _LT_LANG_RC_CONFIG + + +# LT_PROG_GCJ +# ----------- +AC_DEFUN([LT_PROG_GCJ], +[m4_ifdef([AC_PROG_GCJ], [AC_PROG_GCJ], + [m4_ifdef([A][M_PROG_GCJ], [A][M_PROG_GCJ], + [AC_CHECK_TOOL(GCJ, gcj,) + test set = "${GCJFLAGS+set}" || GCJFLAGS="-g -O2" + AC_SUBST(GCJFLAGS)])])[]dnl +]) + +# Old name: +AU_ALIAS([LT_AC_PROG_GCJ], [LT_PROG_GCJ]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_GCJ], []) + + +# LT_PROG_GO +# ---------- +AC_DEFUN([LT_PROG_GO], +[AC_CHECK_TOOL(GOC, gccgo,) +]) + + +# LT_PROG_RC +# ---------- +AC_DEFUN([LT_PROG_RC], +[AC_CHECK_TOOL(RC, windres,) +]) + +# Old name: +AU_ALIAS([LT_AC_PROG_RC], [LT_PROG_RC]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_RC], []) + + +# _LT_DECL_EGREP +# -------------- +# If we don't have a new enough Autoconf to choose the best grep +# available, choose the one first in the user's PATH. +m4_defun([_LT_DECL_EGREP], +[AC_REQUIRE([AC_PROG_EGREP])dnl +AC_REQUIRE([AC_PROG_FGREP])dnl +test -z "$GREP" && GREP=grep +_LT_DECL([], [GREP], [1], [A grep program that handles long lines]) +_LT_DECL([], [EGREP], [1], [An ERE matcher]) +_LT_DECL([], [FGREP], [1], [A literal string matcher]) +dnl Non-bleeding-edge autoconf doesn't subst GREP, so do it here too +AC_SUBST([GREP]) +]) + + +# _LT_DECL_OBJDUMP +# -------------- +# If we don't have a new enough Autoconf to choose the best objdump +# available, choose the one first in the user's PATH. +m4_defun([_LT_DECL_OBJDUMP], +[AC_CHECK_TOOL(OBJDUMP, objdump, false) +test -z "$OBJDUMP" && OBJDUMP=objdump +_LT_DECL([], [OBJDUMP], [1], [An object symbol dumper]) +AC_SUBST([OBJDUMP]) +]) + +# _LT_DECL_DLLTOOL +# ---------------- +# Ensure DLLTOOL variable is set. +m4_defun([_LT_DECL_DLLTOOL], +[AC_CHECK_TOOL(DLLTOOL, dlltool, false) +test -z "$DLLTOOL" && DLLTOOL=dlltool +_LT_DECL([], [DLLTOOL], [1], [DLL creation program]) +AC_SUBST([DLLTOOL]) +]) + +# _LT_DECL_SED +# ------------ +# Check for a fully-functional sed program, that truncates +# as few characters as possible. Prefer GNU sed if found. +m4_defun([_LT_DECL_SED], +[AC_PROG_SED +test -z "$SED" && SED=sed +Xsed="$SED -e 1s/^X//" +_LT_DECL([], [SED], [1], [A sed program that does not truncate output]) +_LT_DECL([], [Xsed], ["\$SED -e 1s/^X//"], + [Sed that helps us avoid accidentally triggering echo(1) options like -n]) +])# _LT_DECL_SED + +m4_ifndef([AC_PROG_SED], [ +############################################################ +# NOTE: This macro has been submitted for inclusion into # +# GNU Autoconf as AC_PROG_SED. When it is available in # +# a released version of Autoconf we should remove this # +# macro and use it instead. # +############################################################ + +m4_defun([AC_PROG_SED], +[AC_MSG_CHECKING([for a sed that does not truncate output]) +AC_CACHE_VAL(lt_cv_path_SED, +[# Loop through the user's path and test for sed and gsed. +# Then use that list of sed's as ones to test for truncation. +as_save_IFS=$IFS; IFS=$PATH_SEPARATOR +for as_dir in $PATH +do + IFS=$as_save_IFS + test -z "$as_dir" && as_dir=. + for lt_ac_prog in sed gsed; do + for ac_exec_ext in '' $ac_executable_extensions; do + if $as_executable_p "$as_dir/$lt_ac_prog$ac_exec_ext"; then + lt_ac_sed_list="$lt_ac_sed_list $as_dir/$lt_ac_prog$ac_exec_ext" + fi + done + done +done +IFS=$as_save_IFS +lt_ac_max=0 +lt_ac_count=0 +# Add /usr/xpg4/bin/sed as it is typically found on Solaris +# along with /bin/sed that truncates output. +for lt_ac_sed in $lt_ac_sed_list /usr/xpg4/bin/sed; do + test ! -f "$lt_ac_sed" && continue + cat /dev/null > conftest.in + lt_ac_count=0 + echo $ECHO_N "0123456789$ECHO_C" >conftest.in + # Check for GNU sed and select it if it is found. + if "$lt_ac_sed" --version 2>&1 < /dev/null | grep 'GNU' > /dev/null; then + lt_cv_path_SED=$lt_ac_sed + break + fi + while true; do + cat conftest.in conftest.in >conftest.tmp + mv conftest.tmp conftest.in + cp conftest.in conftest.nl + echo >>conftest.nl + $lt_ac_sed -e 's/a$//' < conftest.nl >conftest.out || break + cmp -s conftest.out conftest.nl || break + # 10000 chars as input seems more than enough + test 10 -lt "$lt_ac_count" && break + lt_ac_count=`expr $lt_ac_count + 1` + if test "$lt_ac_count" -gt "$lt_ac_max"; then + lt_ac_max=$lt_ac_count + lt_cv_path_SED=$lt_ac_sed + fi + done +done +]) +SED=$lt_cv_path_SED +AC_SUBST([SED]) +AC_MSG_RESULT([$SED]) +])#AC_PROG_SED +])#m4_ifndef + +# Old name: +AU_ALIAS([LT_AC_PROG_SED], [AC_PROG_SED]) +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([LT_AC_PROG_SED], []) + + +# _LT_CHECK_SHELL_FEATURES +# ------------------------ +# Find out whether the shell is Bourne or XSI compatible, +# or has some other useful features. +m4_defun([_LT_CHECK_SHELL_FEATURES], +[if ( (MAIL=60; unset MAIL) || exit) >/dev/null 2>&1; then + lt_unset=unset +else + lt_unset=false +fi +_LT_DECL([], [lt_unset], [0], [whether the shell understands "unset"])dnl + +# test EBCDIC or ASCII +case `echo X|tr X '\101'` in + A) # ASCII based system + # \n is not interpreted correctly by Solaris 8 /usr/ucb/tr + lt_SP2NL='tr \040 \012' + lt_NL2SP='tr \015\012 \040\040' + ;; + *) # EBCDIC based system + lt_SP2NL='tr \100 \n' + lt_NL2SP='tr \r\n \100\100' + ;; +esac +_LT_DECL([SP2NL], [lt_SP2NL], [1], [turn spaces into newlines])dnl +_LT_DECL([NL2SP], [lt_NL2SP], [1], [turn newlines into spaces])dnl +])# _LT_CHECK_SHELL_FEATURES + + +# _LT_PATH_CONVERSION_FUNCTIONS +# ----------------------------- +# Determine what file name conversion functions should be used by +# func_to_host_file (and, implicitly, by func_to_host_path). These are needed +# for certain cross-compile configurations and native mingw. +m4_defun([_LT_PATH_CONVERSION_FUNCTIONS], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +AC_REQUIRE([AC_CANONICAL_BUILD])dnl +AC_MSG_CHECKING([how to convert $build file names to $host format]) +AC_CACHE_VAL(lt_cv_to_host_file_cmd, +[case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_w32 + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_cygwin_to_w32 + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_w32 + ;; + esac + ;; + *-*-cygwin* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_host_file_cmd=func_convert_file_msys_to_cygwin + ;; + *-*-cygwin* ) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; + * ) # otherwise, assume *nix + lt_cv_to_host_file_cmd=func_convert_file_nix_to_cygwin + ;; + esac + ;; + * ) # unhandled hosts (and "normal" native builds) + lt_cv_to_host_file_cmd=func_convert_file_noop + ;; +esac +]) +to_host_file_cmd=$lt_cv_to_host_file_cmd +AC_MSG_RESULT([$lt_cv_to_host_file_cmd]) +_LT_DECL([to_host_file_cmd], [lt_cv_to_host_file_cmd], + [0], [convert $build file names to $host format])dnl + +AC_MSG_CHECKING([how to convert $build file names to toolchain format]) +AC_CACHE_VAL(lt_cv_to_tool_file_cmd, +[#assume ordinary cross tools, or native build. +lt_cv_to_tool_file_cmd=func_convert_file_noop +case $host in + *-*-mingw* ) + case $build in + *-*-mingw* ) # actually msys + lt_cv_to_tool_file_cmd=func_convert_file_msys_to_w32 + ;; + esac + ;; +esac +]) +to_tool_file_cmd=$lt_cv_to_tool_file_cmd +AC_MSG_RESULT([$lt_cv_to_tool_file_cmd]) +_LT_DECL([to_tool_file_cmd], [lt_cv_to_tool_file_cmd], + [0], [convert $build files to toolchain format])dnl +])# _LT_PATH_CONVERSION_FUNCTIONS diff --git a/m4/ltoptions.m4 b/m4/ltoptions.m4 new file mode 100644 index 0000000..94b0829 --- /dev/null +++ b/m4/ltoptions.m4 @@ -0,0 +1,437 @@ +# Helper functions for option handling. -*- Autoconf -*- +# +# Copyright (C) 2004-2005, 2007-2009, 2011-2015 Free Software +# Foundation, Inc. +# Written by Gary V. Vaughan, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 8 ltoptions.m4 + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTOPTIONS_VERSION], [m4_if([1])]) + + +# _LT_MANGLE_OPTION(MACRO-NAME, OPTION-NAME) +# ------------------------------------------ +m4_define([_LT_MANGLE_OPTION], +[[_LT_OPTION_]m4_bpatsubst($1__$2, [[^a-zA-Z0-9_]], [_])]) + + +# _LT_SET_OPTION(MACRO-NAME, OPTION-NAME) +# --------------------------------------- +# Set option OPTION-NAME for macro MACRO-NAME, and if there is a +# matching handler defined, dispatch to it. Other OPTION-NAMEs are +# saved as a flag. +m4_define([_LT_SET_OPTION], +[m4_define(_LT_MANGLE_OPTION([$1], [$2]))dnl +m4_ifdef(_LT_MANGLE_DEFUN([$1], [$2]), + _LT_MANGLE_DEFUN([$1], [$2]), + [m4_warning([Unknown $1 option '$2'])])[]dnl +]) + + +# _LT_IF_OPTION(MACRO-NAME, OPTION-NAME, IF-SET, [IF-NOT-SET]) +# ------------------------------------------------------------ +# Execute IF-SET if OPTION is set, IF-NOT-SET otherwise. +m4_define([_LT_IF_OPTION], +[m4_ifdef(_LT_MANGLE_OPTION([$1], [$2]), [$3], [$4])]) + + +# _LT_UNLESS_OPTIONS(MACRO-NAME, OPTION-LIST, IF-NOT-SET) +# ------------------------------------------------------- +# Execute IF-NOT-SET unless all options in OPTION-LIST for MACRO-NAME +# are set. +m4_define([_LT_UNLESS_OPTIONS], +[m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), + [m4_ifdef(_LT_MANGLE_OPTION([$1], _LT_Option), + [m4_define([$0_found])])])[]dnl +m4_ifdef([$0_found], [m4_undefine([$0_found])], [$3 +])[]dnl +]) + + +# _LT_SET_OPTIONS(MACRO-NAME, OPTION-LIST) +# ---------------------------------------- +# OPTION-LIST is a space-separated list of Libtool options associated +# with MACRO-NAME. If any OPTION has a matching handler declared with +# LT_OPTION_DEFINE, dispatch to that macro; otherwise complain about +# the unknown option and exit. +m4_defun([_LT_SET_OPTIONS], +[# Set options +m4_foreach([_LT_Option], m4_split(m4_normalize([$2])), + [_LT_SET_OPTION([$1], _LT_Option)]) + +m4_if([$1],[LT_INIT],[ + dnl + dnl Simply set some default values (i.e off) if boolean options were not + dnl specified: + _LT_UNLESS_OPTIONS([LT_INIT], [dlopen], [enable_dlopen=no + ]) + _LT_UNLESS_OPTIONS([LT_INIT], [win32-dll], [enable_win32_dll=no + ]) + dnl + dnl If no reference was made to various pairs of opposing options, then + dnl we run the default mode handler for the pair. For example, if neither + dnl 'shared' nor 'disable-shared' was passed, we enable building of shared + dnl archives by default: + _LT_UNLESS_OPTIONS([LT_INIT], [shared disable-shared], [_LT_ENABLE_SHARED]) + _LT_UNLESS_OPTIONS([LT_INIT], [static disable-static], [_LT_ENABLE_STATIC]) + _LT_UNLESS_OPTIONS([LT_INIT], [pic-only no-pic], [_LT_WITH_PIC]) + _LT_UNLESS_OPTIONS([LT_INIT], [fast-install disable-fast-install], + [_LT_ENABLE_FAST_INSTALL]) + _LT_UNLESS_OPTIONS([LT_INIT], [aix-soname=aix aix-soname=both aix-soname=svr4], + [_LT_WITH_AIX_SONAME([aix])]) + ]) +])# _LT_SET_OPTIONS + + +## --------------------------------- ## +## Macros to handle LT_INIT options. ## +## --------------------------------- ## + +# _LT_MANGLE_DEFUN(MACRO-NAME, OPTION-NAME) +# ----------------------------------------- +m4_define([_LT_MANGLE_DEFUN], +[[_LT_OPTION_DEFUN_]m4_bpatsubst(m4_toupper([$1__$2]), [[^A-Z0-9_]], [_])]) + + +# LT_OPTION_DEFINE(MACRO-NAME, OPTION-NAME, CODE) +# ----------------------------------------------- +m4_define([LT_OPTION_DEFINE], +[m4_define(_LT_MANGLE_DEFUN([$1], [$2]), [$3])[]dnl +])# LT_OPTION_DEFINE + + +# dlopen +# ------ +LT_OPTION_DEFINE([LT_INIT], [dlopen], [enable_dlopen=yes +]) + +AU_DEFUN([AC_LIBTOOL_DLOPEN], +[_LT_SET_OPTION([LT_INIT], [dlopen]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the 'dlopen' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_DLOPEN], []) + + +# win32-dll +# --------- +# Declare package support for building win32 dll's. +LT_OPTION_DEFINE([LT_INIT], [win32-dll], +[enable_win32_dll=yes + +case $host in +*-*-cygwin* | *-*-mingw* | *-*-pw32* | *-*-cegcc*) + AC_CHECK_TOOL(AS, as, false) + AC_CHECK_TOOL(DLLTOOL, dlltool, false) + AC_CHECK_TOOL(OBJDUMP, objdump, false) + ;; +esac + +test -z "$AS" && AS=as +_LT_DECL([], [AS], [1], [Assembler program])dnl + +test -z "$DLLTOOL" && DLLTOOL=dlltool +_LT_DECL([], [DLLTOOL], [1], [DLL creation program])dnl + +test -z "$OBJDUMP" && OBJDUMP=objdump +_LT_DECL([], [OBJDUMP], [1], [Object dumper program])dnl +])# win32-dll + +AU_DEFUN([AC_LIBTOOL_WIN32_DLL], +[AC_REQUIRE([AC_CANONICAL_HOST])dnl +_LT_SET_OPTION([LT_INIT], [win32-dll]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the 'win32-dll' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_WIN32_DLL], []) + + +# _LT_ENABLE_SHARED([DEFAULT]) +# ---------------------------- +# implement the --enable-shared flag, and supports the 'shared' and +# 'disable-shared' LT_INIT options. +# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. +m4_define([_LT_ENABLE_SHARED], +[m4_define([_LT_ENABLE_SHARED_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([shared], + [AS_HELP_STRING([--enable-shared@<:@=PKGS@:>@], + [build shared libraries @<:@default=]_LT_ENABLE_SHARED_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_shared=yes ;; + no) enable_shared=no ;; + *) + enable_shared=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_shared=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [enable_shared=]_LT_ENABLE_SHARED_DEFAULT) + + _LT_DECL([build_libtool_libs], [enable_shared], [0], + [Whether or not to build shared libraries]) +])# _LT_ENABLE_SHARED + +LT_OPTION_DEFINE([LT_INIT], [shared], [_LT_ENABLE_SHARED([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-shared], [_LT_ENABLE_SHARED([no])]) + +# Old names: +AC_DEFUN([AC_ENABLE_SHARED], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[shared]) +]) + +AC_DEFUN([AC_DISABLE_SHARED], +[_LT_SET_OPTION([LT_INIT], [disable-shared]) +]) + +AU_DEFUN([AM_ENABLE_SHARED], [AC_ENABLE_SHARED($@)]) +AU_DEFUN([AM_DISABLE_SHARED], [AC_DISABLE_SHARED($@)]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_ENABLE_SHARED], []) +dnl AC_DEFUN([AM_DISABLE_SHARED], []) + + + +# _LT_ENABLE_STATIC([DEFAULT]) +# ---------------------------- +# implement the --enable-static flag, and support the 'static' and +# 'disable-static' LT_INIT options. +# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. +m4_define([_LT_ENABLE_STATIC], +[m4_define([_LT_ENABLE_STATIC_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([static], + [AS_HELP_STRING([--enable-static@<:@=PKGS@:>@], + [build static libraries @<:@default=]_LT_ENABLE_STATIC_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_static=yes ;; + no) enable_static=no ;; + *) + enable_static=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_static=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [enable_static=]_LT_ENABLE_STATIC_DEFAULT) + + _LT_DECL([build_old_libs], [enable_static], [0], + [Whether or not to build static libraries]) +])# _LT_ENABLE_STATIC + +LT_OPTION_DEFINE([LT_INIT], [static], [_LT_ENABLE_STATIC([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-static], [_LT_ENABLE_STATIC([no])]) + +# Old names: +AC_DEFUN([AC_ENABLE_STATIC], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[static]) +]) + +AC_DEFUN([AC_DISABLE_STATIC], +[_LT_SET_OPTION([LT_INIT], [disable-static]) +]) + +AU_DEFUN([AM_ENABLE_STATIC], [AC_ENABLE_STATIC($@)]) +AU_DEFUN([AM_DISABLE_STATIC], [AC_DISABLE_STATIC($@)]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AM_ENABLE_STATIC], []) +dnl AC_DEFUN([AM_DISABLE_STATIC], []) + + + +# _LT_ENABLE_FAST_INSTALL([DEFAULT]) +# ---------------------------------- +# implement the --enable-fast-install flag, and support the 'fast-install' +# and 'disable-fast-install' LT_INIT options. +# DEFAULT is either 'yes' or 'no'. If omitted, it defaults to 'yes'. +m4_define([_LT_ENABLE_FAST_INSTALL], +[m4_define([_LT_ENABLE_FAST_INSTALL_DEFAULT], [m4_if($1, no, no, yes)])dnl +AC_ARG_ENABLE([fast-install], + [AS_HELP_STRING([--enable-fast-install@<:@=PKGS@:>@], + [optimize for fast installation @<:@default=]_LT_ENABLE_FAST_INSTALL_DEFAULT[@:>@])], + [p=${PACKAGE-default} + case $enableval in + yes) enable_fast_install=yes ;; + no) enable_fast_install=no ;; + *) + enable_fast_install=no + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for pkg in $enableval; do + IFS=$lt_save_ifs + if test "X$pkg" = "X$p"; then + enable_fast_install=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [enable_fast_install=]_LT_ENABLE_FAST_INSTALL_DEFAULT) + +_LT_DECL([fast_install], [enable_fast_install], [0], + [Whether or not to optimize for fast installation])dnl +])# _LT_ENABLE_FAST_INSTALL + +LT_OPTION_DEFINE([LT_INIT], [fast-install], [_LT_ENABLE_FAST_INSTALL([yes])]) +LT_OPTION_DEFINE([LT_INIT], [disable-fast-install], [_LT_ENABLE_FAST_INSTALL([no])]) + +# Old names: +AU_DEFUN([AC_ENABLE_FAST_INSTALL], +[_LT_SET_OPTION([LT_INIT], m4_if([$1], [no], [disable-])[fast-install]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you put +the 'fast-install' option into LT_INIT's first parameter.]) +]) + +AU_DEFUN([AC_DISABLE_FAST_INSTALL], +[_LT_SET_OPTION([LT_INIT], [disable-fast-install]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you put +the 'disable-fast-install' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_ENABLE_FAST_INSTALL], []) +dnl AC_DEFUN([AM_DISABLE_FAST_INSTALL], []) + + +# _LT_WITH_AIX_SONAME([DEFAULT]) +# ---------------------------------- +# implement the --with-aix-soname flag, and support the `aix-soname=aix' +# and `aix-soname=both' and `aix-soname=svr4' LT_INIT options. DEFAULT +# is either `aix', `both' or `svr4'. If omitted, it defaults to `aix'. +m4_define([_LT_WITH_AIX_SONAME], +[m4_define([_LT_WITH_AIX_SONAME_DEFAULT], [m4_if($1, svr4, svr4, m4_if($1, both, both, aix))])dnl +shared_archive_member_spec= +case $host,$enable_shared in +power*-*-aix[[5-9]]*,yes) + AC_MSG_CHECKING([which variant of shared library versioning to provide]) + AC_ARG_WITH([aix-soname], + [AS_HELP_STRING([--with-aix-soname=aix|svr4|both], + [shared library versioning (aka "SONAME") variant to provide on AIX, @<:@default=]_LT_WITH_AIX_SONAME_DEFAULT[@:>@.])], + [case $withval in + aix|svr4|both) + ;; + *) + AC_MSG_ERROR([Unknown argument to --with-aix-soname]) + ;; + esac + lt_cv_with_aix_soname=$with_aix_soname], + [AC_CACHE_VAL([lt_cv_with_aix_soname], + [lt_cv_with_aix_soname=]_LT_WITH_AIX_SONAME_DEFAULT) + with_aix_soname=$lt_cv_with_aix_soname]) + AC_MSG_RESULT([$with_aix_soname]) + if test aix != "$with_aix_soname"; then + # For the AIX way of multilib, we name the shared archive member + # based on the bitwidth used, traditionally 'shr.o' or 'shr_64.o', + # and 'shr.imp' or 'shr_64.imp', respectively, for the Import File. + # Even when GNU compilers ignore OBJECT_MODE but need '-maix64' flag, + # the AIX toolchain works better with OBJECT_MODE set (default 32). + if test 64 = "${OBJECT_MODE-32}"; then + shared_archive_member_spec=shr_64 + else + shared_archive_member_spec=shr + fi + fi + ;; +*) + with_aix_soname=aix + ;; +esac + +_LT_DECL([], [shared_archive_member_spec], [0], + [Shared archive member basename, for filename based shared library versioning on AIX])dnl +])# _LT_WITH_AIX_SONAME + +LT_OPTION_DEFINE([LT_INIT], [aix-soname=aix], [_LT_WITH_AIX_SONAME([aix])]) +LT_OPTION_DEFINE([LT_INIT], [aix-soname=both], [_LT_WITH_AIX_SONAME([both])]) +LT_OPTION_DEFINE([LT_INIT], [aix-soname=svr4], [_LT_WITH_AIX_SONAME([svr4])]) + + +# _LT_WITH_PIC([MODE]) +# -------------------- +# implement the --with-pic flag, and support the 'pic-only' and 'no-pic' +# LT_INIT options. +# MODE is either 'yes' or 'no'. If omitted, it defaults to 'both'. +m4_define([_LT_WITH_PIC], +[AC_ARG_WITH([pic], + [AS_HELP_STRING([--with-pic@<:@=PKGS@:>@], + [try to use only PIC/non-PIC objects @<:@default=use both@:>@])], + [lt_p=${PACKAGE-default} + case $withval in + yes|no) pic_mode=$withval ;; + *) + pic_mode=default + # Look at the argument we got. We use all the common list separators. + lt_save_ifs=$IFS; IFS=$IFS$PATH_SEPARATOR, + for lt_pkg in $withval; do + IFS=$lt_save_ifs + if test "X$lt_pkg" = "X$lt_p"; then + pic_mode=yes + fi + done + IFS=$lt_save_ifs + ;; + esac], + [pic_mode=m4_default([$1], [default])]) + +_LT_DECL([], [pic_mode], [0], [What type of objects to build])dnl +])# _LT_WITH_PIC + +LT_OPTION_DEFINE([LT_INIT], [pic-only], [_LT_WITH_PIC([yes])]) +LT_OPTION_DEFINE([LT_INIT], [no-pic], [_LT_WITH_PIC([no])]) + +# Old name: +AU_DEFUN([AC_LIBTOOL_PICMODE], +[_LT_SET_OPTION([LT_INIT], [pic-only]) +AC_DIAGNOSE([obsolete], +[$0: Remove this warning and the call to _LT_SET_OPTION when you +put the 'pic-only' option into LT_INIT's first parameter.]) +]) + +dnl aclocal-1.4 backwards compatibility: +dnl AC_DEFUN([AC_LIBTOOL_PICMODE], []) + +## ----------------- ## +## LTDL_INIT Options ## +## ----------------- ## + +m4_define([_LTDL_MODE], []) +LT_OPTION_DEFINE([LTDL_INIT], [nonrecursive], + [m4_define([_LTDL_MODE], [nonrecursive])]) +LT_OPTION_DEFINE([LTDL_INIT], [recursive], + [m4_define([_LTDL_MODE], [recursive])]) +LT_OPTION_DEFINE([LTDL_INIT], [subproject], + [m4_define([_LTDL_MODE], [subproject])]) + +m4_define([_LTDL_TYPE], []) +LT_OPTION_DEFINE([LTDL_INIT], [installable], + [m4_define([_LTDL_TYPE], [installable])]) +LT_OPTION_DEFINE([LTDL_INIT], [convenience], + [m4_define([_LTDL_TYPE], [convenience])]) diff --git a/m4/ltsugar.m4 b/m4/ltsugar.m4 new file mode 100644 index 0000000..48bc934 --- /dev/null +++ b/m4/ltsugar.m4 @@ -0,0 +1,124 @@ +# ltsugar.m4 -- libtool m4 base layer. -*-Autoconf-*- +# +# Copyright (C) 2004-2005, 2007-2008, 2011-2015 Free Software +# Foundation, Inc. +# Written by Gary V. Vaughan, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 6 ltsugar.m4 + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTSUGAR_VERSION], [m4_if([0.1])]) + + +# lt_join(SEP, ARG1, [ARG2...]) +# ----------------------------- +# Produce ARG1SEPARG2...SEPARGn, omitting [] arguments and their +# associated separator. +# Needed until we can rely on m4_join from Autoconf 2.62, since all earlier +# versions in m4sugar had bugs. +m4_define([lt_join], +[m4_if([$#], [1], [], + [$#], [2], [[$2]], + [m4_if([$2], [], [], [[$2]_])$0([$1], m4_shift(m4_shift($@)))])]) +m4_define([_lt_join], +[m4_if([$#$2], [2], [], + [m4_if([$2], [], [], [[$1$2]])$0([$1], m4_shift(m4_shift($@)))])]) + + +# lt_car(LIST) +# lt_cdr(LIST) +# ------------ +# Manipulate m4 lists. +# These macros are necessary as long as will still need to support +# Autoconf-2.59, which quotes differently. +m4_define([lt_car], [[$1]]) +m4_define([lt_cdr], +[m4_if([$#], 0, [m4_fatal([$0: cannot be called without arguments])], + [$#], 1, [], + [m4_dquote(m4_shift($@))])]) +m4_define([lt_unquote], $1) + + +# lt_append(MACRO-NAME, STRING, [SEPARATOR]) +# ------------------------------------------ +# Redefine MACRO-NAME to hold its former content plus 'SEPARATOR''STRING'. +# Note that neither SEPARATOR nor STRING are expanded; they are appended +# to MACRO-NAME as is (leaving the expansion for when MACRO-NAME is invoked). +# No SEPARATOR is output if MACRO-NAME was previously undefined (different +# than defined and empty). +# +# This macro is needed until we can rely on Autoconf 2.62, since earlier +# versions of m4sugar mistakenly expanded SEPARATOR but not STRING. +m4_define([lt_append], +[m4_define([$1], + m4_ifdef([$1], [m4_defn([$1])[$3]])[$2])]) + + + +# lt_combine(SEP, PREFIX-LIST, INFIX, SUFFIX1, [SUFFIX2...]) +# ---------------------------------------------------------- +# Produce a SEP delimited list of all paired combinations of elements of +# PREFIX-LIST with SUFFIX1 through SUFFIXn. Each element of the list +# has the form PREFIXmINFIXSUFFIXn. +# Needed until we can rely on m4_combine added in Autoconf 2.62. +m4_define([lt_combine], +[m4_if(m4_eval([$# > 3]), [1], + [m4_pushdef([_Lt_sep], [m4_define([_Lt_sep], m4_defn([lt_car]))])]]dnl +[[m4_foreach([_Lt_prefix], [$2], + [m4_foreach([_Lt_suffix], + ]m4_dquote(m4_dquote(m4_shift(m4_shift(m4_shift($@)))))[, + [_Lt_sep([$1])[]m4_defn([_Lt_prefix])[$3]m4_defn([_Lt_suffix])])])])]) + + +# lt_if_append_uniq(MACRO-NAME, VARNAME, [SEPARATOR], [UNIQ], [NOT-UNIQ]) +# ----------------------------------------------------------------------- +# Iff MACRO-NAME does not yet contain VARNAME, then append it (delimited +# by SEPARATOR if supplied) and expand UNIQ, else NOT-UNIQ. +m4_define([lt_if_append_uniq], +[m4_ifdef([$1], + [m4_if(m4_index([$3]m4_defn([$1])[$3], [$3$2$3]), [-1], + [lt_append([$1], [$2], [$3])$4], + [$5])], + [lt_append([$1], [$2], [$3])$4])]) + + +# lt_dict_add(DICT, KEY, VALUE) +# ----------------------------- +m4_define([lt_dict_add], +[m4_define([$1($2)], [$3])]) + + +# lt_dict_add_subkey(DICT, KEY, SUBKEY, VALUE) +# -------------------------------------------- +m4_define([lt_dict_add_subkey], +[m4_define([$1($2:$3)], [$4])]) + + +# lt_dict_fetch(DICT, KEY, [SUBKEY]) +# ---------------------------------- +m4_define([lt_dict_fetch], +[m4_ifval([$3], + m4_ifdef([$1($2:$3)], [m4_defn([$1($2:$3)])]), + m4_ifdef([$1($2)], [m4_defn([$1($2)])]))]) + + +# lt_if_dict_fetch(DICT, KEY, [SUBKEY], VALUE, IF-TRUE, [IF-FALSE]) +# ----------------------------------------------------------------- +m4_define([lt_if_dict_fetch], +[m4_if(lt_dict_fetch([$1], [$2], [$3]), [$4], + [$5], + [$6])]) + + +# lt_dict_filter(DICT, [SUBKEY], VALUE, [SEPARATOR], KEY, [...]) +# -------------------------------------------------------------- +m4_define([lt_dict_filter], +[m4_if([$5], [], [], + [lt_join(m4_quote(m4_default([$4], [[, ]])), + lt_unquote(m4_split(m4_normalize(m4_foreach(_Lt_key, lt_car([m4_shiftn(4, $@)]), + [lt_if_dict_fetch([$1], _Lt_key, [$2], [$3], [_Lt_key ])])))))])[]dnl +]) diff --git a/m4/ltversion.m4 b/m4/ltversion.m4 new file mode 100644 index 0000000..fa04b52 --- /dev/null +++ b/m4/ltversion.m4 @@ -0,0 +1,23 @@ +# ltversion.m4 -- version numbers -*- Autoconf -*- +# +# Copyright (C) 2004, 2011-2015 Free Software Foundation, Inc. +# Written by Scott James Remnant, 2004 +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# @configure_input@ + +# serial 4179 ltversion.m4 +# This file is part of GNU Libtool + +m4_define([LT_PACKAGE_VERSION], [2.4.6]) +m4_define([LT_PACKAGE_REVISION], [2.4.6]) + +AC_DEFUN([LTVERSION_VERSION], +[macro_version='2.4.6' +macro_revision='2.4.6' +_LT_DECL(, macro_version, 0, [Which release of libtool.m4 was used?]) +_LT_DECL(, macro_revision, 0) +]) diff --git a/m4/lt~obsolete.m4 b/m4/lt~obsolete.m4 new file mode 100644 index 0000000..c6b26f8 --- /dev/null +++ b/m4/lt~obsolete.m4 @@ -0,0 +1,99 @@ +# lt~obsolete.m4 -- aclocal satisfying obsolete definitions. -*-Autoconf-*- +# +# Copyright (C) 2004-2005, 2007, 2009, 2011-2015 Free Software +# Foundation, Inc. +# Written by Scott James Remnant, 2004. +# +# This file is free software; the Free Software Foundation gives +# unlimited permission to copy and/or distribute it, with or without +# modifications, as long as this notice is preserved. + +# serial 5 lt~obsolete.m4 + +# These exist entirely to fool aclocal when bootstrapping libtool. +# +# In the past libtool.m4 has provided macros via AC_DEFUN (or AU_DEFUN), +# which have later been changed to m4_define as they aren't part of the +# exported API, or moved to Autoconf or Automake where they belong. +# +# The trouble is, aclocal is a bit thick. It'll see the old AC_DEFUN +# in /usr/share/aclocal/libtool.m4 and remember it, then when it sees us +# using a macro with the same name in our local m4/libtool.m4 it'll +# pull the old libtool.m4 in (it doesn't see our shiny new m4_define +# and doesn't know about Autoconf macros at all.) +# +# So we provide this file, which has a silly filename so it's always +# included after everything else. This provides aclocal with the +# AC_DEFUNs it wants, but when m4 processes it, it doesn't do anything +# because those macros already exist, or will be overwritten later. +# We use AC_DEFUN over AU_DEFUN for compatibility with aclocal-1.6. +# +# Anytime we withdraw an AC_DEFUN or AU_DEFUN, remember to add it here. +# Yes, that means every name once taken will need to remain here until +# we give up compatibility with versions before 1.7, at which point +# we need to keep only those names which we still refer to. + +# This is to help aclocal find these macros, as it can't see m4_define. +AC_DEFUN([LTOBSOLETE_VERSION], [m4_if([1])]) + +m4_ifndef([AC_LIBTOOL_LINKER_OPTION], [AC_DEFUN([AC_LIBTOOL_LINKER_OPTION])]) +m4_ifndef([AC_PROG_EGREP], [AC_DEFUN([AC_PROG_EGREP])]) +m4_ifndef([_LT_AC_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_AC_PROG_ECHO_BACKSLASH])]) +m4_ifndef([_LT_AC_SHELL_INIT], [AC_DEFUN([_LT_AC_SHELL_INIT])]) +m4_ifndef([_LT_AC_SYS_LIBPATH_AIX], [AC_DEFUN([_LT_AC_SYS_LIBPATH_AIX])]) +m4_ifndef([_LT_PROG_LTMAIN], [AC_DEFUN([_LT_PROG_LTMAIN])]) +m4_ifndef([_LT_AC_TAGVAR], [AC_DEFUN([_LT_AC_TAGVAR])]) +m4_ifndef([AC_LTDL_ENABLE_INSTALL], [AC_DEFUN([AC_LTDL_ENABLE_INSTALL])]) +m4_ifndef([AC_LTDL_PREOPEN], [AC_DEFUN([AC_LTDL_PREOPEN])]) +m4_ifndef([_LT_AC_SYS_COMPILER], [AC_DEFUN([_LT_AC_SYS_COMPILER])]) +m4_ifndef([_LT_AC_LOCK], [AC_DEFUN([_LT_AC_LOCK])]) +m4_ifndef([AC_LIBTOOL_SYS_OLD_ARCHIVE], [AC_DEFUN([AC_LIBTOOL_SYS_OLD_ARCHIVE])]) +m4_ifndef([_LT_AC_TRY_DLOPEN_SELF], [AC_DEFUN([_LT_AC_TRY_DLOPEN_SELF])]) +m4_ifndef([AC_LIBTOOL_PROG_CC_C_O], [AC_DEFUN([AC_LIBTOOL_PROG_CC_C_O])]) +m4_ifndef([AC_LIBTOOL_SYS_HARD_LINK_LOCKS], [AC_DEFUN([AC_LIBTOOL_SYS_HARD_LINK_LOCKS])]) +m4_ifndef([AC_LIBTOOL_OBJDIR], [AC_DEFUN([AC_LIBTOOL_OBJDIR])]) +m4_ifndef([AC_LTDL_OBJDIR], [AC_DEFUN([AC_LTDL_OBJDIR])]) +m4_ifndef([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH], [AC_DEFUN([AC_LIBTOOL_PROG_LD_HARDCODE_LIBPATH])]) +m4_ifndef([AC_LIBTOOL_SYS_LIB_STRIP], [AC_DEFUN([AC_LIBTOOL_SYS_LIB_STRIP])]) +m4_ifndef([AC_PATH_MAGIC], [AC_DEFUN([AC_PATH_MAGIC])]) +m4_ifndef([AC_PROG_LD_GNU], [AC_DEFUN([AC_PROG_LD_GNU])]) +m4_ifndef([AC_PROG_LD_RELOAD_FLAG], [AC_DEFUN([AC_PROG_LD_RELOAD_FLAG])]) +m4_ifndef([AC_DEPLIBS_CHECK_METHOD], [AC_DEFUN([AC_DEPLIBS_CHECK_METHOD])]) +m4_ifndef([AC_LIBTOOL_PROG_COMPILER_NO_RTTI], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_NO_RTTI])]) +m4_ifndef([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE], [AC_DEFUN([AC_LIBTOOL_SYS_GLOBAL_SYMBOL_PIPE])]) +m4_ifndef([AC_LIBTOOL_PROG_COMPILER_PIC], [AC_DEFUN([AC_LIBTOOL_PROG_COMPILER_PIC])]) +m4_ifndef([AC_LIBTOOL_PROG_LD_SHLIBS], [AC_DEFUN([AC_LIBTOOL_PROG_LD_SHLIBS])]) +m4_ifndef([AC_LIBTOOL_POSTDEP_PREDEP], [AC_DEFUN([AC_LIBTOOL_POSTDEP_PREDEP])]) +m4_ifndef([LT_AC_PROG_EGREP], [AC_DEFUN([LT_AC_PROG_EGREP])]) +m4_ifndef([LT_AC_PROG_SED], [AC_DEFUN([LT_AC_PROG_SED])]) +m4_ifndef([_LT_CC_BASENAME], [AC_DEFUN([_LT_CC_BASENAME])]) +m4_ifndef([_LT_COMPILER_BOILERPLATE], [AC_DEFUN([_LT_COMPILER_BOILERPLATE])]) +m4_ifndef([_LT_LINKER_BOILERPLATE], [AC_DEFUN([_LT_LINKER_BOILERPLATE])]) +m4_ifndef([_AC_PROG_LIBTOOL], [AC_DEFUN([_AC_PROG_LIBTOOL])]) +m4_ifndef([AC_LIBTOOL_SETUP], [AC_DEFUN([AC_LIBTOOL_SETUP])]) +m4_ifndef([_LT_AC_CHECK_DLFCN], [AC_DEFUN([_LT_AC_CHECK_DLFCN])]) +m4_ifndef([AC_LIBTOOL_SYS_DYNAMIC_LINKER], [AC_DEFUN([AC_LIBTOOL_SYS_DYNAMIC_LINKER])]) +m4_ifndef([_LT_AC_TAGCONFIG], [AC_DEFUN([_LT_AC_TAGCONFIG])]) +m4_ifndef([AC_DISABLE_FAST_INSTALL], [AC_DEFUN([AC_DISABLE_FAST_INSTALL])]) +m4_ifndef([_LT_AC_LANG_CXX], [AC_DEFUN([_LT_AC_LANG_CXX])]) +m4_ifndef([_LT_AC_LANG_F77], [AC_DEFUN([_LT_AC_LANG_F77])]) +m4_ifndef([_LT_AC_LANG_GCJ], [AC_DEFUN([_LT_AC_LANG_GCJ])]) +m4_ifndef([AC_LIBTOOL_LANG_C_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_C_CONFIG])]) +m4_ifndef([_LT_AC_LANG_C_CONFIG], [AC_DEFUN([_LT_AC_LANG_C_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_CXX_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_CXX_CONFIG])]) +m4_ifndef([_LT_AC_LANG_CXX_CONFIG], [AC_DEFUN([_LT_AC_LANG_CXX_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_F77_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_F77_CONFIG])]) +m4_ifndef([_LT_AC_LANG_F77_CONFIG], [AC_DEFUN([_LT_AC_LANG_F77_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_GCJ_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_GCJ_CONFIG])]) +m4_ifndef([_LT_AC_LANG_GCJ_CONFIG], [AC_DEFUN([_LT_AC_LANG_GCJ_CONFIG])]) +m4_ifndef([AC_LIBTOOL_LANG_RC_CONFIG], [AC_DEFUN([AC_LIBTOOL_LANG_RC_CONFIG])]) +m4_ifndef([_LT_AC_LANG_RC_CONFIG], [AC_DEFUN([_LT_AC_LANG_RC_CONFIG])]) +m4_ifndef([AC_LIBTOOL_CONFIG], [AC_DEFUN([AC_LIBTOOL_CONFIG])]) +m4_ifndef([_LT_AC_FILE_LTDLL_C], [AC_DEFUN([_LT_AC_FILE_LTDLL_C])]) +m4_ifndef([_LT_REQUIRED_DARWIN_CHECKS], [AC_DEFUN([_LT_REQUIRED_DARWIN_CHECKS])]) +m4_ifndef([_LT_AC_PROG_CXXCPP], [AC_DEFUN([_LT_AC_PROG_CXXCPP])]) +m4_ifndef([_LT_PREPARE_SED_QUOTE_VARS], [AC_DEFUN([_LT_PREPARE_SED_QUOTE_VARS])]) +m4_ifndef([_LT_PROG_ECHO_BACKSLASH], [AC_DEFUN([_LT_PROG_ECHO_BACKSLASH])]) +m4_ifndef([_LT_PROG_F77], [AC_DEFUN([_LT_PROG_F77])]) +m4_ifndef([_LT_PROG_FC], [AC_DEFUN([_LT_PROG_FC])]) +m4_ifndef([_LT_PROG_CXX], [AC_DEFUN([_LT_PROG_CXX])]) diff --git a/m4/nls.m4 b/m4/nls.m4 new file mode 100644 index 0000000..003704c --- /dev/null +++ b/m4/nls.m4 @@ -0,0 +1,32 @@ +# nls.m4 serial 5 (gettext-0.18) +dnl Copyright (C) 1995-2003, 2005-2006, 2008-2010 Free Software Foundation, +dnl Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. +dnl +dnl This file can can be used in projects which are not available under +dnl the GNU General Public License or the GNU Library General Public +dnl License but which still want to provide support for the GNU gettext +dnl functionality. +dnl Please note that the actual code of the GNU gettext library is covered +dnl by the GNU Library General Public License, and the rest of the GNU +dnl gettext package package is covered by the GNU General Public License. +dnl They are *not* in the public domain. + +dnl Authors: +dnl Ulrich Drepper , 1995-2000. +dnl Bruno Haible , 2000-2003. + +AC_PREREQ([2.50]) + +AC_DEFUN([AM_NLS], +[ + AC_MSG_CHECKING([whether NLS is requested]) + dnl Default is enabled NLS + AC_ARG_ENABLE([nls], + [ --disable-nls do not use Native Language Support], + USE_NLS=$enableval, USE_NLS=yes) + AC_MSG_RESULT([$USE_NLS]) + AC_SUBST([USE_NLS]) +]) diff --git a/m4/pkg.m4 b/m4/pkg.m4 new file mode 100644 index 0000000..f2bfc2d --- /dev/null +++ b/m4/pkg.m4 @@ -0,0 +1,57 @@ + +dnl PKG_CHECK_MODULES(GSTUFF, gtk+-2.0 >= 1.3 glib = 1.3.4, action-if, action-not) +dnl defines GSTUFF_LIBS, GSTUFF_CFLAGS, see pkg-config man page +dnl also defines GSTUFF_PKG_ERRORS on error +AC_DEFUN([PKG_CHECK_MODULES], [ + succeeded=no + + if test -z "$PKG_CONFIG"; then + AC_PATH_PROG(PKG_CONFIG, pkg-config, no) + fi + + if test "$PKG_CONFIG" = "no" ; then + echo "*** The pkg-config script could not be found. Make sure it is" + echo "*** in your path, or set the PKG_CONFIG environment variable" + echo "*** to the full path to pkg-config." + echo "*** Or see http://www.freedesktop.org/software/pkgconfig to get pkg-config." + else + PKG_CONFIG_MIN_VERSION=0.9.0 + if $PKG_CONFIG --atleast-pkgconfig-version $PKG_CONFIG_MIN_VERSION; then + AC_MSG_CHECKING(for $2) + + if $PKG_CONFIG --exists "$2" ; then + AC_MSG_RESULT(yes) + succeeded=yes + + AC_MSG_CHECKING($1_CFLAGS) + $1_CFLAGS=`$PKG_CONFIG --cflags "$2"` + AC_MSG_RESULT($$1_CFLAGS) + + AC_MSG_CHECKING($1_LIBS) + $1_LIBS=`$PKG_CONFIG --libs "$2"` + AC_MSG_RESULT($$1_LIBS) + else + $1_CFLAGS="" + $1_LIBS="" + ## If we have a custom action on failure, don't print errors, but + ## do set a variable so people can do so. + $1_PKG_ERRORS=`$PKG_CONFIG --errors-to-stdout --print-errors "$2"` + ifelse([$4], ,echo $$1_PKG_ERRORS,) + fi + + AC_SUBST($1_CFLAGS) + AC_SUBST($1_LIBS) + else + echo "*** Your version of pkg-config is too old. You need version $PKG_CONFIG_MIN_VERSION or newer." + echo "*** See http://www.freedesktop.org/software/pkgconfig" + fi + fi + + if test $succeeded = yes; then + ifelse([$3], , :, [$3]) + else + ifelse([$4], , AC_MSG_ERROR([Library requirements ($2) not met; consider adjusting the PKG_CONFIG_PATH environment variable if your libraries are in a nonstandard prefix so pkg-config can find them.]), [$4]) + fi +]) + + diff --git a/m4/po.m4 b/m4/po.m4 new file mode 100644 index 0000000..47f36a4 --- /dev/null +++ b/m4/po.m4 @@ -0,0 +1,449 @@ +# po.m4 serial 17 (gettext-0.18) +dnl Copyright (C) 1995-2010 Free Software Foundation, Inc. +dnl This file is free software; the Free Software Foundation +dnl gives unlimited permission to copy and/or distribute it, +dnl with or without modifications, as long as this notice is preserved. +dnl +dnl This file can can be used in projects which are not available under +dnl the GNU General Public License or the GNU Library General Public +dnl License but which still want to provide support for the GNU gettext +dnl functionality. +dnl Please note that the actual code of the GNU gettext library is covered +dnl by the GNU Library General Public License, and the rest of the GNU +dnl gettext package package is covered by the GNU General Public License. +dnl They are *not* in the public domain. + +dnl Authors: +dnl Ulrich Drepper , 1995-2000. +dnl Bruno Haible , 2000-2003. + +AC_PREREQ([2.50]) + +dnl Checks for all prerequisites of the po subdirectory. +AC_DEFUN([AM_PO_SUBDIRS], +[ + AC_REQUIRE([AC_PROG_MAKE_SET])dnl + AC_REQUIRE([AC_PROG_INSTALL])dnl + AC_REQUIRE([AM_PROG_MKDIR_P])dnl defined by automake + AC_REQUIRE([AM_NLS])dnl + + dnl Release version of the gettext macros. This is used to ensure that + dnl the gettext macros and po/Makefile.in.in are in sync. + AC_SUBST([GETTEXT_MACRO_VERSION], [0.18]) + + dnl Perform the following tests also if --disable-nls has been given, + dnl because they are needed for "make dist" to work. + + dnl Search for GNU msgfmt in the PATH. + dnl The first test excludes Solaris msgfmt and early GNU msgfmt versions. + dnl The second test excludes FreeBSD msgfmt. + AM_PATH_PROG_WITH_TEST(MSGFMT, msgfmt, + [$ac_dir/$ac_word --statistics /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1 && + (if $ac_dir/$ac_word --statistics /dev/null 2>&1 >/dev/null | grep usage >/dev/null; then exit 1; else exit 0; fi)], + :) + AC_PATH_PROG([GMSGFMT], [gmsgfmt], [$MSGFMT]) + + dnl Test whether it is GNU msgfmt >= 0.15. +changequote(,)dnl + case `$MSGFMT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in + '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) MSGFMT_015=: ;; + *) MSGFMT_015=$MSGFMT ;; + esac +changequote([,])dnl + AC_SUBST([MSGFMT_015]) +changequote(,)dnl + case `$GMSGFMT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in + '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) GMSGFMT_015=: ;; + *) GMSGFMT_015=$GMSGFMT ;; + esac +changequote([,])dnl + AC_SUBST([GMSGFMT_015]) + + dnl Search for GNU xgettext 0.12 or newer in the PATH. + dnl The first test excludes Solaris xgettext and early GNU xgettext versions. + dnl The second test excludes FreeBSD xgettext. + AM_PATH_PROG_WITH_TEST(XGETTEXT, xgettext, + [$ac_dir/$ac_word --omit-header --copyright-holder= --msgid-bugs-address= /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1 && + (if $ac_dir/$ac_word --omit-header --copyright-holder= --msgid-bugs-address= /dev/null 2>&1 >/dev/null | grep usage >/dev/null; then exit 1; else exit 0; fi)], + :) + dnl Remove leftover from FreeBSD xgettext call. + rm -f messages.po + + dnl Test whether it is GNU xgettext >= 0.15. +changequote(,)dnl + case `$XGETTEXT --version | sed 1q | sed -e 's,^[^0-9]*,,'` in + '' | 0.[0-9] | 0.[0-9].* | 0.1[0-4] | 0.1[0-4].*) XGETTEXT_015=: ;; + *) XGETTEXT_015=$XGETTEXT ;; + esac +changequote([,])dnl + AC_SUBST([XGETTEXT_015]) + + dnl Search for GNU msgmerge 0.11 or newer in the PATH. + AM_PATH_PROG_WITH_TEST(MSGMERGE, msgmerge, + [$ac_dir/$ac_word --update -q /dev/null /dev/null >&]AS_MESSAGE_LOG_FD[ 2>&1], :) + + dnl Installation directories. + dnl Autoconf >= 2.60 defines localedir. For older versions of autoconf, we + dnl have to define it here, so that it can be used in po/Makefile. + test -n "$localedir" || localedir='${datadir}/locale' + AC_SUBST([localedir]) + + dnl Support for AM_XGETTEXT_OPTION. + test -n "${XGETTEXT_EXTRA_OPTIONS+set}" || XGETTEXT_EXTRA_OPTIONS= + AC_SUBST([XGETTEXT_EXTRA_OPTIONS]) + + AC_CONFIG_COMMANDS([po-directories], [[ + for ac_file in $CONFIG_FILES; do + # Support "outfile[:infile[:infile...]]" + case "$ac_file" in + *:*) ac_file=`echo "$ac_file"|sed 's%:.*%%'` ;; + esac + # PO directories have a Makefile.in generated from Makefile.in.in. + case "$ac_file" in */Makefile.in) + # Adjust a relative srcdir. + ac_dir=`echo "$ac_file"|sed 's%/[^/][^/]*$%%'` + ac_dir_suffix="/`echo "$ac_dir"|sed 's%^\./%%'`" + ac_dots=`echo "$ac_dir_suffix"|sed 's%/[^/]*%../%g'` + # In autoconf-2.13 it is called $ac_given_srcdir. + # In autoconf-2.50 it is called $srcdir. + test -n "$ac_given_srcdir" || ac_given_srcdir="$srcdir" + case "$ac_given_srcdir" in + .) top_srcdir=`echo $ac_dots|sed 's%/$%%'` ;; + /*) top_srcdir="$ac_given_srcdir" ;; + *) top_srcdir="$ac_dots$ac_given_srcdir" ;; + esac + # Treat a directory as a PO directory if and only if it has a + # POTFILES.in file. This allows packages to have multiple PO + # directories under different names or in different locations. + if test -f "$ac_given_srcdir/$ac_dir/POTFILES.in"; then + rm -f "$ac_dir/POTFILES" + test -n "$as_me" && echo "$as_me: creating $ac_dir/POTFILES" || echo "creating $ac_dir/POTFILES" + cat "$ac_given_srcdir/$ac_dir/POTFILES.in" | sed -e "/^#/d" -e "/^[ ]*\$/d" -e "s,.*, $top_srcdir/& \\\\," | sed -e "\$s/\(.*\) \\\\/\1/" > "$ac_dir/POTFILES" + POMAKEFILEDEPS="POTFILES.in" + # ALL_LINGUAS, POFILES, UPDATEPOFILES, DUMMYPOFILES, GMOFILES depend + # on $ac_dir but don't depend on user-specified configuration + # parameters. + if test -f "$ac_given_srcdir/$ac_dir/LINGUAS"; then + # The LINGUAS file contains the set of available languages. + if test -n "$OBSOLETE_ALL_LINGUAS"; then + test -n "$as_me" && echo "$as_me: setting ALL_LINGUAS in configure.in is obsolete" || echo "setting ALL_LINGUAS in configure.in is obsolete" + fi + ALL_LINGUAS_=`sed -e "/^#/d" -e "s/#.*//" "$ac_given_srcdir/$ac_dir/LINGUAS"` + # Hide the ALL_LINGUAS assigment from automake < 1.5. + eval 'ALL_LINGUAS''=$ALL_LINGUAS_' + POMAKEFILEDEPS="$POMAKEFILEDEPS LINGUAS" + else + # The set of available languages was given in configure.in. + # Hide the ALL_LINGUAS assigment from automake < 1.5. + eval 'ALL_LINGUAS''=$OBSOLETE_ALL_LINGUAS' + fi + # Compute POFILES + # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).po) + # Compute UPDATEPOFILES + # as $(foreach lang, $(ALL_LINGUAS), $(lang).po-update) + # Compute DUMMYPOFILES + # as $(foreach lang, $(ALL_LINGUAS), $(lang).nop) + # Compute GMOFILES + # as $(foreach lang, $(ALL_LINGUAS), $(srcdir)/$(lang).gmo) + case "$ac_given_srcdir" in + .) srcdirpre= ;; + *) srcdirpre='$(srcdir)/' ;; + esac + POFILES= + UPDATEPOFILES= + DUMMYPOFILES= + GMOFILES= + for lang in $ALL_LINGUAS; do + POFILES="$POFILES $srcdirpre$lang.po" + UPDATEPOFILES="$UPDATEPOFILES $lang.po-update" + DUMMYPOFILES="$DUMMYPOFILES $lang.nop" + GMOFILES="$GMOFILES $srcdirpre$lang.gmo" + done + # CATALOGS depends on both $ac_dir and the user's LINGUAS + # environment variable. + INST_LINGUAS= + if test -n "$ALL_LINGUAS"; then + for presentlang in $ALL_LINGUAS; do + useit=no + if test "%UNSET%" != "$LINGUAS"; then + desiredlanguages="$LINGUAS" + else + desiredlanguages="$ALL_LINGUAS" + fi + for desiredlang in $desiredlanguages; do + # Use the presentlang catalog if desiredlang is + # a. equal to presentlang, or + # b. a variant of presentlang (because in this case, + # presentlang can be used as a fallback for messages + # which are not translated in the desiredlang catalog). + case "$desiredlang" in + "$presentlang"*) useit=yes;; + esac + done + if test $useit = yes; then + INST_LINGUAS="$INST_LINGUAS $presentlang" + fi + done + fi + CATALOGS= + if test -n "$INST_LINGUAS"; then + for lang in $INST_LINGUAS; do + CATALOGS="$CATALOGS $lang.gmo" + done + fi + test -n "$as_me" && echo "$as_me: creating $ac_dir/Makefile" || echo "creating $ac_dir/Makefile" + sed -e "/^POTFILES =/r $ac_dir/POTFILES" -e "/^# Makevars/r $ac_given_srcdir/$ac_dir/Makevars" -e "s|@POFILES@|$POFILES|g" -e "s|@UPDATEPOFILES@|$UPDATEPOFILES|g" -e "s|@DUMMYPOFILES@|$DUMMYPOFILES|g" -e "s|@GMOFILES@|$GMOFILES|g" -e "s|@CATALOGS@|$CATALOGS|g" -e "s|@POMAKEFILEDEPS@|$POMAKEFILEDEPS|g" "$ac_dir/Makefile.in" > "$ac_dir/Makefile" + for f in "$ac_given_srcdir/$ac_dir"/Rules-*; do + if test -f "$f"; then + case "$f" in + *.orig | *.bak | *~) ;; + *) cat "$f" >> "$ac_dir/Makefile" ;; + esac + fi + done + fi + ;; + esac + done]], + [# Capture the value of obsolete ALL_LINGUAS because we need it to compute + # POFILES, UPDATEPOFILES, DUMMYPOFILES, GMOFILES, CATALOGS. But hide it + # from automake < 1.5. + eval 'OBSOLETE_ALL_LINGUAS''="$ALL_LINGUAS"' + # Capture the value of LINGUAS because we need it to compute CATALOGS. + LINGUAS="${LINGUAS-%UNSET%}" + ]) +]) + +dnl Postprocesses a Makefile in a directory containing PO files. +AC_DEFUN([AM_POSTPROCESS_PO_MAKEFILE], +[ + # When this code is run, in config.status, two variables have already been + # set: + # - OBSOLETE_ALL_LINGUAS is the value of LINGUAS set in configure.in, + # - LINGUAS is the value of the environment variable LINGUAS at configure + # time. + +changequote(,)dnl + # Adjust a relative srcdir. + ac_dir=`echo "$ac_file"|sed 's%/[^/][^/]*$%%'` + ac_dir_suffix="/`echo "$ac_dir"|sed 's%^\./%%'`" + ac_dots=`echo "$ac_dir_suffix"|sed 's%/[^/]*%../%g'` + # In autoconf-2.13 it is called $ac_given_srcdir. + # In autoconf-2.50 it is called $srcdir. + test -n "$ac_given_srcdir" || ac_given_srcdir="$srcdir" + case "$ac_given_srcdir" in + .) top_srcdir=`echo $ac_dots|sed 's%/$%%'` ;; + /*) top_srcdir="$ac_given_srcdir" ;; + *) top_srcdir="$ac_dots$ac_given_srcdir" ;; + esac + + # Find a way to echo strings without interpreting backslash. + if test "X`(echo '\t') 2>/dev/null`" = 'X\t'; then + gt_echo='echo' + else + if test "X`(printf '%s\n' '\t') 2>/dev/null`" = 'X\t'; then + gt_echo='printf %s\n' + else + echo_func () { + cat < "$ac_file.tmp" + if grep -l '@TCLCATALOGS@' "$ac_file" > /dev/null; then + # Add dependencies that cannot be formulated as a simple suffix rule. + for lang in $ALL_LINGUAS; do + frobbedlang=`echo $lang | sed -e 's/\..*$//' -e 'y/ABCDEFGHIJKLMNOPQRSTUVWXYZ/abcdefghijklmnopqrstuvwxyz/'` + cat >> "$ac_file.tmp" < /dev/null; then + # Add dependencies that cannot be formulated as a simple suffix rule. + for lang in $ALL_LINGUAS; do + frobbedlang=`echo $lang | sed -e 's/_/-/g' -e 's/^sr-CS/sr-SP/' -e 's/@latin$/-Latn/' -e 's/@cyrillic$/-Cyrl/' -e 's/^sr-SP$/sr-SP-Latn/' -e 's/^uz-UZ$/uz-UZ-Latn/'` + cat >> "$ac_file.tmp" <> "$ac_file.tmp" <, 1996. + +AC_PREREQ([2.50]) + +# Search path for a program which passes the given test. + +dnl AM_PATH_PROG_WITH_TEST(VARIABLE, PROG-TO-CHECK-FOR, +dnl TEST-PERFORMED-ON-FOUND_PROGRAM [, VALUE-IF-NOT-FOUND [, PATH]]) +AC_DEFUN([AM_PATH_PROG_WITH_TEST], +[ +# Prepare PATH_SEPARATOR. +# The user is always right. +if test "${PATH_SEPARATOR+set}" != set; then + echo "#! /bin/sh" >conf$$.sh + echo "exit 0" >>conf$$.sh + chmod +x conf$$.sh + if (PATH="/nonexistent;."; conf$$.sh) >/dev/null 2>&1; then + PATH_SEPARATOR=';' + else + PATH_SEPARATOR=: + fi + rm -f conf$$.sh +fi + +# Find out how to test for executable files. Don't use a zero-byte file, +# as systems may use methods other than mode bits to determine executability. +cat >conf$$.file <<_ASEOF +#! /bin/sh +exit 0 +_ASEOF +chmod +x conf$$.file +if test -x conf$$.file >/dev/null 2>&1; then + ac_executable_p="test -x" +else + ac_executable_p="test -f" +fi +rm -f conf$$.file + +# Extract the first word of "$2", so it can be a program name with args. +set dummy $2; ac_word=[$]2 +AC_MSG_CHECKING([for $ac_word]) +AC_CACHE_VAL([ac_cv_path_$1], +[case "[$]$1" in + [[\\/]]* | ?:[[\\/]]*) + ac_cv_path_$1="[$]$1" # Let the user override the test with a path. + ;; + *) + ac_save_IFS="$IFS"; IFS=$PATH_SEPARATOR + for ac_dir in ifelse([$5], , $PATH, [$5]); do + IFS="$ac_save_IFS" + test -z "$ac_dir" && ac_dir=. + for ac_exec_ext in '' $ac_executable_extensions; do + if $ac_executable_p "$ac_dir/$ac_word$ac_exec_ext"; then + echo "$as_me: trying $ac_dir/$ac_word..." >&AS_MESSAGE_LOG_FD + if [$3]; then + ac_cv_path_$1="$ac_dir/$ac_word$ac_exec_ext" + break 2 + fi + fi + done + done + IFS="$ac_save_IFS" +dnl If no 4th arg is given, leave the cache variable unset, +dnl so AC_PATH_PROGS will keep looking. +ifelse([$4], , , [ test -z "[$]ac_cv_path_$1" && ac_cv_path_$1="$4" +])dnl + ;; +esac])dnl +$1="$ac_cv_path_$1" +if test ifelse([$4], , [-n "[$]$1"], ["[$]$1" != "$4"]); then + AC_MSG_RESULT([$][$1]) +else + AC_MSG_RESULT([no]) +fi +AC_SUBST([$1])dnl +]) diff --git a/pkgconfig/Makefile.am b/pkgconfig/Makefile.am new file mode 100644 index 0000000..bc1eca5 --- /dev/null +++ b/pkgconfig/Makefile.am @@ -0,0 +1,25 @@ +# This Makefile.am is in the public domain +pcfiles = \ + gnunetmulticast.pc \ + gnunetpsyc.pc \ + gnunetpsycstore.pc + +all-local: $(pcfiles) + +cp_verbose = $(cp_verbose_$(V)) +cp_verbose_ = $(cp_verbose_$(AM_DEFAULT_VERBOSITY)) +cp_verbose_0 = @echo " CP $@"; + +%.pc: %.pc + $(cp_verbose_0)cp $< $@ + +pkgconfigdir = $(libdir)/pkgconfig +pkgconfig_DATA = $(pcfiles) + +EXTRA_DIST = \ + gnunetmulticast.pc.in \ + gnunetpsyc.pc.in \ + gnunetpsycstore.pc.in + +CLEANFILES = $(pcfiles) +AM_CPPFLAGS = -I$(top_srcdir)/src/include diff --git a/pkgconfig/gnunetmulticast.pc.in b/pkgconfig/gnunetmulticast.pc.in new file mode 100644 index 0000000..2045555 --- /dev/null +++ b/pkgconfig/gnunetmulticast.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: GNUnet MULTICAST +Description: library to multicast messages to a group of peers +URL: https://gnunet.org +Version: @VERSION@ +Requires: +Libs: -L${libdir} -lgnunetmulticast +Cflags: -I${includedir} diff --git a/pkgconfig/gnunetpsyc.pc.in b/pkgconfig/gnunetpsyc.pc.in new file mode 100644 index 0000000..9cfabdf --- /dev/null +++ b/pkgconfig/gnunetpsyc.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: GNUnet PSYC +Description: library for PSYC multicast channel management +URL: https://gnunet.org +Version: @VERSION@ +Requires: +Libs: -L${libdir} -lgnunetpsyc +Cflags: -I${includedir} diff --git a/pkgconfig/gnunetpsycstore.pc.in b/pkgconfig/gnunetpsycstore.pc.in new file mode 100644 index 0000000..765abdc --- /dev/null +++ b/pkgconfig/gnunetpsycstore.pc.in @@ -0,0 +1,12 @@ +prefix=@prefix@ +exec_prefix=@exec_prefix@ +libdir=@libdir@ +includedir=@includedir@ + +Name: GNUnet PSYCSTORE +Description: library to for persistent storage of PSYC messages +URL: https://gnunet.org +Version: @VERSION@ +Requires: +Libs: -L${libdir} -lgnunetpsycstore +Cflags: -I${includedir} diff --git a/po/.gitignore b/po/.gitignore new file mode 100644 index 0000000..3fbf582 --- /dev/null +++ b/po/.gitignore @@ -0,0 +1,6 @@ +Makefile.in +Makefile +POTFILES +gnunet-ext.pot +remove-potcdate.sed +stamp-po diff --git a/po/ChangeLog b/po/ChangeLog new file mode 100644 index 0000000..16bda42 --- /dev/null +++ b/po/ChangeLog @@ -0,0 +1,12 @@ +2012-03-07 gettextize + + * Makefile.in.in: New file, from gettext-0.18.1. + * Rules-quot: New file, from gettext-0.18.1. + * boldquot.sed: New file, from gettext-0.18.1. + * en@boldquot.header: New file, from gettext-0.18.1. + * en@quot.header: New file, from gettext-0.18.1. + * insert-header.sin: New file, from gettext-0.18.1. + * quot.sed: New file, from gettext-0.18.1. + * remove-potcdate.sin: New file, from gettext-0.18.1. + * POTFILES.in: New file. + diff --git a/po/Makefile.in.in b/po/Makefile.in.in new file mode 100644 index 0000000..83d8838 --- /dev/null +++ b/po/Makefile.in.in @@ -0,0 +1,444 @@ +# Makefile for PO directory in any package using GNU gettext. +# Copyright (C) 1995-1997, 2000-2007, 2009-2010 by Ulrich Drepper +# +# This file can be copied and used freely without restrictions. It can +# be used in projects which are not available under the GNU General Public +# License but which still want to provide support for the GNU gettext +# functionality. +# Please note that the actual code of GNU gettext is covered by the GNU +# General Public License and is *not* in the public domain. +# +# Origin: gettext-0.18 +GETTEXT_MACRO_VERSION = 0.18 + +PACKAGE = @PACKAGE@ +VERSION = @VERSION@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ + +SHELL = /bin/sh +@SET_MAKE@ + +srcdir = @srcdir@ +top_srcdir = @top_srcdir@ +VPATH = @srcdir@ + +prefix = @prefix@ +exec_prefix = @exec_prefix@ +datarootdir = @datarootdir@ +datadir = @datadir@ +localedir = @localedir@ +gettextsrcdir = $(datadir)/gettext/po + +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ + +# We use $(mkdir_p). +# In automake <= 1.9.x, $(mkdir_p) is defined either as "mkdir -p --" or as +# "$(mkinstalldirs)" or as "$(install_sh) -d". For these automake versions, +# @install_sh@ does not start with $(SHELL), so we add it. +# In automake >= 1.10, @mkdir_p@ is derived from ${MKDIR_P}, which is defined +# either as "/path/to/mkdir -p" or ".../install-sh -c -d". For these automake +# versions, $(mkinstalldirs) and $(install_sh) are unused. +mkinstalldirs = $(SHELL) @install_sh@ -d +install_sh = $(SHELL) @install_sh@ +MKDIR_P = @MKDIR_P@ +mkdir_p = @mkdir_p@ + +GMSGFMT_ = @GMSGFMT@ +GMSGFMT_no = @GMSGFMT@ +GMSGFMT_yes = @GMSGFMT_015@ +GMSGFMT = $(GMSGFMT_$(USE_MSGCTXT)) +MSGFMT_ = @MSGFMT@ +MSGFMT_no = @MSGFMT@ +MSGFMT_yes = @MSGFMT_015@ +MSGFMT = $(MSGFMT_$(USE_MSGCTXT)) +XGETTEXT_ = @XGETTEXT@ +XGETTEXT_no = @XGETTEXT@ +XGETTEXT_yes = @XGETTEXT_015@ +XGETTEXT = $(XGETTEXT_$(USE_MSGCTXT)) +MSGMERGE = msgmerge +MSGMERGE_UPDATE = @MSGMERGE@ --update +MSGINIT = msginit +MSGCONV = msgconv +MSGFILTER = msgfilter + +POFILES = @POFILES@ +GMOFILES = @GMOFILES@ +UPDATEPOFILES = @UPDATEPOFILES@ +DUMMYPOFILES = @DUMMYPOFILES@ +DISTFILES.common = Makefile.in.in remove-potcdate.sin \ +$(DISTFILES.common.extra1) $(DISTFILES.common.extra2) $(DISTFILES.common.extra3) +DISTFILES = $(DISTFILES.common) Makevars POTFILES.in \ +$(POFILES) $(GMOFILES) \ +$(DISTFILES.extra1) $(DISTFILES.extra2) $(DISTFILES.extra3) + +POTFILES = \ + +CATALOGS = @CATALOGS@ + +# Makevars gets inserted here. (Don't remove this line!) + +.SUFFIXES: +.SUFFIXES: .po .gmo .mo .sed .sin .nop .po-create .po-update + +.po.mo: + @echo "$(MSGFMT) -c -o $@ $<"; \ + $(MSGFMT) -c -o t-$@ $< && mv t-$@ $@ + +.po.gmo: + @lang=`echo $* | sed -e 's,.*/,,'`; \ + test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \ + echo "$${cdcmd}rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics --verbose -o $${lang}.gmo $${lang}.po"; \ + cd $(srcdir) && rm -f $${lang}.gmo && $(GMSGFMT) -c --statistics --verbose -o t-$${lang}.gmo $${lang}.po && mv t-$${lang}.gmo $${lang}.gmo + +.sin.sed: + sed -e '/^#/d' $< > t-$@ + mv t-$@ $@ + + +all: check-macro-version all-@USE_NLS@ + +all-yes: stamp-po +all-no: + +# Ensure that the gettext macros and this Makefile.in.in are in sync. +check-macro-version: + @test "$(GETTEXT_MACRO_VERSION)" = "@GETTEXT_MACRO_VERSION@" \ + || { echo "*** error: gettext infrastructure mismatch: using a Makefile.in.in from gettext version $(GETTEXT_MACRO_VERSION) but the autoconf macros are from gettext version @GETTEXT_MACRO_VERSION@" 1>&2; \ + exit 1; \ + } + +# $(srcdir)/$(DOMAIN).pot is only created when needed. When xgettext finds no +# internationalized messages, no $(srcdir)/$(DOMAIN).pot is created (because +# we don't want to bother translators with empty POT files). We assume that +# LINGUAS is empty in this case, i.e. $(POFILES) and $(GMOFILES) are empty. +# In this case, stamp-po is a nop (i.e. a phony target). + +# stamp-po is a timestamp denoting the last time at which the CATALOGS have +# been loosely updated. Its purpose is that when a developer or translator +# checks out the package via CVS, and the $(DOMAIN).pot file is not in CVS, +# "make" will update the $(DOMAIN).pot and the $(CATALOGS), but subsequent +# invocations of "make" will do nothing. This timestamp would not be necessary +# if updating the $(CATALOGS) would always touch them; however, the rule for +# $(POFILES) has been designed to not touch files that don't need to be +# changed. +stamp-po: $(srcdir)/$(DOMAIN).pot + test ! -f $(srcdir)/$(DOMAIN).pot || \ + test -z "$(GMOFILES)" || $(MAKE) $(GMOFILES) + @test ! -f $(srcdir)/$(DOMAIN).pot || { \ + echo "touch stamp-po" && \ + echo timestamp > stamp-poT && \ + mv stamp-poT stamp-po; \ + } + +# Note: Target 'all' must not depend on target '$(DOMAIN).pot-update', +# otherwise packages like GCC can not be built if only parts of the source +# have been downloaded. + +# This target rebuilds $(DOMAIN).pot; it is an expensive operation. +# Note that $(DOMAIN).pot is not touched if it doesn't need to be changed. +$(DOMAIN).pot-update: $(POTFILES) $(srcdir)/POTFILES.in remove-potcdate.sed + if LC_ALL=C grep 'GNU @PACKAGE@' $(top_srcdir)/* 2>/dev/null | grep -v 'libtool:' >/dev/null; then \ + package_gnu='GNU '; \ + else \ + package_gnu=''; \ + fi; \ + if test -n '$(MSGID_BUGS_ADDRESS)' || test '$(PACKAGE_BUGREPORT)' = '@'PACKAGE_BUGREPORT'@'; then \ + msgid_bugs_address='$(MSGID_BUGS_ADDRESS)'; \ + else \ + msgid_bugs_address='$(PACKAGE_BUGREPORT)'; \ + fi; \ + case `$(XGETTEXT) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \ + '' | 0.[0-9] | 0.[0-9].* | 0.1[0-5] | 0.1[0-5].* | 0.16 | 0.16.[0-1]*) \ + $(XGETTEXT) --default-domain=$(DOMAIN) --directory=$(top_srcdir) \ + --add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS) @XGETTEXT_EXTRA_OPTIONS@ \ + --files-from=$(srcdir)/POTFILES.in \ + --copyright-holder='$(COPYRIGHT_HOLDER)' \ + --msgid-bugs-address="$$msgid_bugs_address" \ + ;; \ + *) \ + $(XGETTEXT) --default-domain=$(DOMAIN) --directory=$(top_srcdir) \ + --add-comments=TRANSLATORS: $(XGETTEXT_OPTIONS) @XGETTEXT_EXTRA_OPTIONS@ \ + --files-from=$(srcdir)/POTFILES.in \ + --copyright-holder='$(COPYRIGHT_HOLDER)' \ + --package-name="$${package_gnu}@PACKAGE@" \ + --package-version='@VERSION@' \ + --msgid-bugs-address="$$msgid_bugs_address" \ + ;; \ + esac + test ! -f $(DOMAIN).po || { \ + if test -f $(srcdir)/$(DOMAIN).pot; then \ + sed -f remove-potcdate.sed < $(srcdir)/$(DOMAIN).pot > $(DOMAIN).1po && \ + sed -f remove-potcdate.sed < $(DOMAIN).po > $(DOMAIN).2po && \ + if cmp $(DOMAIN).1po $(DOMAIN).2po >/dev/null 2>&1; then \ + rm -f $(DOMAIN).1po $(DOMAIN).2po $(DOMAIN).po; \ + else \ + rm -f $(DOMAIN).1po $(DOMAIN).2po $(srcdir)/$(DOMAIN).pot && \ + mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \ + fi; \ + else \ + mv $(DOMAIN).po $(srcdir)/$(DOMAIN).pot; \ + fi; \ + } + +# This rule has no dependencies: we don't need to update $(DOMAIN).pot at +# every "make" invocation, only create it when it is missing. +# Only "make $(DOMAIN).pot-update" or "make dist" will force an update. +$(srcdir)/$(DOMAIN).pot: + $(MAKE) $(DOMAIN).pot-update + +# This target rebuilds a PO file if $(DOMAIN).pot has changed. +# Note that a PO file is not touched if it doesn't need to be changed. +$(POFILES): $(srcdir)/$(DOMAIN).pot + @lang=`echo $@ | sed -e 's,.*/,,' -e 's/\.po$$//'`; \ + if test -f "$(srcdir)/$${lang}.po"; then \ + test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \ + echo "$${cdcmd}$(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} $${lang}.po $(DOMAIN).pot"; \ + cd $(srcdir) \ + && { case `$(MSGMERGE_UPDATE) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \ + '' | 0.[0-9] | 0.[0-9].* | 0.1[0-7] | 0.1[0-7].*) \ + $(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) $${lang}.po $(DOMAIN).pot;; \ + *) \ + $(MSGMERGE_UPDATE) $(MSGMERGE_OPTIONS) --lang=$${lang} $${lang}.po $(DOMAIN).pot;; \ + esac; \ + }; \ + else \ + $(MAKE) $${lang}.po-create; \ + fi + + +install: install-exec install-data +install-exec: +install-data: install-data-@USE_NLS@ + if test "$(PACKAGE)" = "gettext-tools"; then \ + $(mkdir_p) $(DESTDIR)$(gettextsrcdir); \ + for file in $(DISTFILES.common) Makevars.template; do \ + $(INSTALL_DATA) $(srcdir)/$$file \ + $(DESTDIR)$(gettextsrcdir)/$$file; \ + done; \ + for file in Makevars; do \ + rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \ + done; \ + else \ + : ; \ + fi +install-data-no: all +install-data-yes: all + @catalogs='$(CATALOGS)'; \ + for cat in $$catalogs; do \ + cat=`basename $$cat`; \ + lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \ + dir=$(localedir)/$$lang/LC_MESSAGES; \ + $(mkdir_p) $(DESTDIR)$$dir; \ + if test -r $$cat; then realcat=$$cat; else realcat=$(srcdir)/$$cat; fi; \ + $(INSTALL_DATA) $$realcat $(DESTDIR)$$dir/$(DOMAIN).mo; \ + echo "installing $$realcat as $(DESTDIR)$$dir/$(DOMAIN).mo"; \ + for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \ + if test -n "$$lc"; then \ + if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 2>/dev/null) | grep ' -> ' >/dev/null; then \ + link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc | sed -e 's/^.* -> //'`; \ + mv $(DESTDIR)$(localedir)/$$lang/$$lc $(DESTDIR)$(localedir)/$$lang/$$lc.old; \ + mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \ + (cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \ + for file in *; do \ + if test -f $$file; then \ + ln -s ../$$link/$$file $(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \ + fi; \ + done); \ + rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \ + else \ + if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \ + :; \ + else \ + rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \ + mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \ + fi; \ + fi; \ + rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \ + ln -s ../LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \ + ln $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo 2>/dev/null || \ + cp -p $(DESTDIR)$(localedir)/$$lang/LC_MESSAGES/$(DOMAIN).mo $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \ + echo "installing $$realcat link as $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo"; \ + fi; \ + done; \ + done + +install-strip: install + +installdirs: installdirs-exec installdirs-data +installdirs-exec: +installdirs-data: installdirs-data-@USE_NLS@ + if test "$(PACKAGE)" = "gettext-tools"; then \ + $(mkdir_p) $(DESTDIR)$(gettextsrcdir); \ + else \ + : ; \ + fi +installdirs-data-no: +installdirs-data-yes: + @catalogs='$(CATALOGS)'; \ + for cat in $$catalogs; do \ + cat=`basename $$cat`; \ + lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \ + dir=$(localedir)/$$lang/LC_MESSAGES; \ + $(mkdir_p) $(DESTDIR)$$dir; \ + for lc in '' $(EXTRA_LOCALE_CATEGORIES); do \ + if test -n "$$lc"; then \ + if (cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc 2>/dev/null) | grep ' -> ' >/dev/null; then \ + link=`cd $(DESTDIR)$(localedir)/$$lang && LC_ALL=C ls -l -d $$lc | sed -e 's/^.* -> //'`; \ + mv $(DESTDIR)$(localedir)/$$lang/$$lc $(DESTDIR)$(localedir)/$$lang/$$lc.old; \ + mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \ + (cd $(DESTDIR)$(localedir)/$$lang/$$lc.old && \ + for file in *; do \ + if test -f $$file; then \ + ln -s ../$$link/$$file $(DESTDIR)$(localedir)/$$lang/$$lc/$$file; \ + fi; \ + done); \ + rm -f $(DESTDIR)$(localedir)/$$lang/$$lc.old; \ + else \ + if test -d $(DESTDIR)$(localedir)/$$lang/$$lc; then \ + :; \ + else \ + rm -f $(DESTDIR)$(localedir)/$$lang/$$lc; \ + mkdir $(DESTDIR)$(localedir)/$$lang/$$lc; \ + fi; \ + fi; \ + fi; \ + done; \ + done + +# Define this as empty until I found a useful application. +installcheck: + +uninstall: uninstall-exec uninstall-data +uninstall-exec: +uninstall-data: uninstall-data-@USE_NLS@ + if test "$(PACKAGE)" = "gettext-tools"; then \ + for file in $(DISTFILES.common) Makevars.template; do \ + rm -f $(DESTDIR)$(gettextsrcdir)/$$file; \ + done; \ + else \ + : ; \ + fi +uninstall-data-no: +uninstall-data-yes: + catalogs='$(CATALOGS)'; \ + for cat in $$catalogs; do \ + cat=`basename $$cat`; \ + lang=`echo $$cat | sed -e 's/\.gmo$$//'`; \ + for lc in LC_MESSAGES $(EXTRA_LOCALE_CATEGORIES); do \ + rm -f $(DESTDIR)$(localedir)/$$lang/$$lc/$(DOMAIN).mo; \ + done; \ + done + +check: all + +info dvi ps pdf html tags TAGS ctags CTAGS ID: + +mostlyclean: + rm -f remove-potcdate.sed + rm -f stamp-poT + rm -f core core.* $(DOMAIN).po $(DOMAIN).1po $(DOMAIN).2po *.new.po + rm -fr *.o + +clean: mostlyclean + +distclean: clean + rm -f Makefile Makefile.in POTFILES *.mo + +maintainer-clean: distclean + @echo "This command is intended for maintainers to use;" + @echo "it deletes files that may require special tools to rebuild." + rm -f stamp-po $(GMOFILES) + +distdir = $(top_builddir)/$(PACKAGE)-$(VERSION)/$(subdir) +dist distdir: + $(MAKE) update-po + @$(MAKE) dist2 +# This is a separate target because 'update-po' must be executed before. +dist2: stamp-po $(DISTFILES) + dists="$(DISTFILES)"; \ + if test "$(PACKAGE)" = "gettext-tools"; then \ + dists="$$dists Makevars.template"; \ + fi; \ + if test -f $(srcdir)/$(DOMAIN).pot; then \ + dists="$$dists $(DOMAIN).pot stamp-po"; \ + fi; \ + if test -f $(srcdir)/ChangeLog; then \ + dists="$$dists ChangeLog"; \ + fi; \ + for i in 0 1 2 3 4 5 6 7 8 9; do \ + if test -f $(srcdir)/ChangeLog.$$i; then \ + dists="$$dists ChangeLog.$$i"; \ + fi; \ + done; \ + if test -f $(srcdir)/LINGUAS; then dists="$$dists LINGUAS"; fi; \ + for file in $$dists; do \ + if test -f $$file; then \ + cp -p $$file $(distdir) || exit 1; \ + else \ + cp -p $(srcdir)/$$file $(distdir) || exit 1; \ + fi; \ + done + +update-po: Makefile + $(MAKE) $(DOMAIN).pot-update + test -z "$(UPDATEPOFILES)" || $(MAKE) $(UPDATEPOFILES) + $(MAKE) update-gmo + +# General rule for creating PO files. + +.nop.po-create: + @lang=`echo $@ | sed -e 's/\.po-create$$//'`; \ + echo "File $$lang.po does not exist. If you are a translator, you can create it through 'msginit'." 1>&2; \ + exit 1 + +# General rule for updating PO files. + +.nop.po-update: + @lang=`echo $@ | sed -e 's/\.po-update$$//'`; \ + if test "$(PACKAGE)" = "gettext-tools"; then PATH=`pwd`/../src:$$PATH; fi; \ + tmpdir=`pwd`; \ + echo "$$lang:"; \ + test "$(srcdir)" = . && cdcmd="" || cdcmd="cd $(srcdir) && "; \ + echo "$${cdcmd}$(MSGMERGE) $(MSGMERGE_OPTIONS) --lang=$$lang $$lang.po $(DOMAIN).pot -o $$lang.new.po"; \ + cd $(srcdir); \ + if { case `$(MSGMERGE) --version | sed 1q | sed -e 's,^[^0-9]*,,'` in \ + '' | 0.[0-9] | 0.[0-9].* | 0.1[0-7] | 0.1[0-7].*) \ + $(MSGMERGE) $(MSGMERGE_OPTIONS) -o $$tmpdir/$$lang.new.po $$lang.po $(DOMAIN).pot;; \ + *) \ + $(MSGMERGE) $(MSGMERGE_OPTIONS) --lang=$$lang -o $$tmpdir/$$lang.new.po $$lang.po $(DOMAIN).pot;; \ + esac; \ + }; then \ + if cmp $$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \ + rm -f $$tmpdir/$$lang.new.po; \ + else \ + if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \ + :; \ + else \ + echo "msgmerge for $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \ + exit 1; \ + fi; \ + fi; \ + else \ + echo "msgmerge for $$lang.po failed!" 1>&2; \ + rm -f $$tmpdir/$$lang.new.po; \ + fi + +$(DUMMYPOFILES): + +update-gmo: Makefile $(GMOFILES) + @: + +# Recreate Makefile by invoking config.status. Explicitly invoke the shell, +# because execution permission bits may not work on the current file system. +# Use @SHELL@, which is the shell determined by autoconf for the use by its +# scripts, not $(SHELL) which is hardwired to /bin/sh and may be deficient. +Makefile: Makefile.in.in Makevars $(top_builddir)/config.status @POMAKEFILEDEPS@ + cd $(top_builddir) \ + && @SHELL@ ./config.status $(subdir)/$@.in po-directories + +force: + +# Tell versions [3.59,3.63) of GNU make not to export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/po/Makevars b/po/Makevars new file mode 100644 index 0000000..32692ab --- /dev/null +++ b/po/Makevars @@ -0,0 +1,41 @@ +# Makefile variables for PO directory in any package using GNU gettext. + +# Usually the message domain is the same as the package name. +DOMAIN = $(PACKAGE) + +# These two variables depend on the location of this directory. +subdir = po +top_builddir = .. + +# These options get passed to xgettext. +XGETTEXT_OPTIONS = --keyword=_ --keyword=N_ + +# This is the copyright holder that gets inserted into the header of the +# $(DOMAIN).pot file. Set this to the copyright holder of the surrounding +# package. (Note that the msgstr strings, extracted from the package's +# sources, belong to the copyright holder of the package.) Translators are +# expected to transfer the copyright for their translations to this person +# or entity, or to disclaim their copyright. The empty string stands for +# the public domain; in this case the translators are expected to disclaim +# their copyright. +COPYRIGHT_HOLDER = Free Software Foundation, Inc. + +# This is the email address or URL to which the translators shall report +# bugs in the untranslated strings: +# - Strings which are not entire sentences, see the maintainer guidelines +# in the GNU gettext documentation, section 'Preparing Strings'. +# - Strings which use unclear terms or require additional context to be +# understood. +# - Strings which make invalid assumptions about notation of date, time or +# money. +# - Pluralisation problems. +# - Incorrect English spelling. +# - Incorrect formatting. +# It can be your email address, or a mailing list address where translators +# can write to without being subscribed, or the URL of a web page through +# which the translators can contact you. +MSGID_BUGS_ADDRESS = + +# This is the list of locale categories, beyond LC_MESSAGES, for which the +# message catalogs shall be used. It is usually empty. +EXTRA_LOCALE_CATEGORIES = diff --git a/po/POTFILES.in b/po/POTFILES.in new file mode 100644 index 0000000..6fb7e2c --- /dev/null +++ b/po/POTFILES.in @@ -0,0 +1,2 @@ +# List of source files which contain translatable strings. +src/ext/gnunet-ext.c diff --git a/po/Rules-quot b/po/Rules-quot new file mode 100644 index 0000000..af52487 --- /dev/null +++ b/po/Rules-quot @@ -0,0 +1,47 @@ +# Special Makefile rules for English message catalogs with quotation marks. + +DISTFILES.common.extra1 = quot.sed boldquot.sed en@quot.header en@boldquot.header insert-header.sin Rules-quot + +.SUFFIXES: .insert-header .po-update-en + +en@quot.po-create: + $(MAKE) en@quot.po-update +en@boldquot.po-create: + $(MAKE) en@boldquot.po-update + +en@quot.po-update: en@quot.po-update-en +en@boldquot.po-update: en@boldquot.po-update-en + +.insert-header.po-update-en: + @lang=`echo $@ | sed -e 's/\.po-update-en$$//'`; \ + if test "$(PACKAGE)" = "gettext"; then PATH=`pwd`/../src:$$PATH; GETTEXTLIBDIR=`cd $(top_srcdir)/src && pwd`; export GETTEXTLIBDIR; fi; \ + tmpdir=`pwd`; \ + echo "$$lang:"; \ + ll=`echo $$lang | sed -e 's/@.*//'`; \ + LC_ALL=C; export LC_ALL; \ + cd $(srcdir); \ + if $(MSGINIT) -i $(DOMAIN).pot --no-translator -l $$lang -o - 2>/dev/null | sed -f $$tmpdir/$$lang.insert-header | $(MSGCONV) -t UTF-8 | $(MSGFILTER) sed -f `echo $$lang | sed -e 's/.*@//'`.sed 2>/dev/null > $$tmpdir/$$lang.new.po; then \ + if cmp $$lang.po $$tmpdir/$$lang.new.po >/dev/null 2>&1; then \ + rm -f $$tmpdir/$$lang.new.po; \ + else \ + if mv -f $$tmpdir/$$lang.new.po $$lang.po; then \ + :; \ + else \ + echo "creation of $$lang.po failed: cannot move $$tmpdir/$$lang.new.po to $$lang.po" 1>&2; \ + exit 1; \ + fi; \ + fi; \ + else \ + echo "creation of $$lang.po failed!" 1>&2; \ + rm -f $$tmpdir/$$lang.new.po; \ + fi + +en@quot.insert-header: insert-header.sin + sed -e '/^#/d' -e 's/HEADER/en@quot.header/g' $(srcdir)/insert-header.sin > en@quot.insert-header + +en@boldquot.insert-header: insert-header.sin + sed -e '/^#/d' -e 's/HEADER/en@boldquot.header/g' $(srcdir)/insert-header.sin > en@boldquot.insert-header + +mostlyclean: mostlyclean-quot +mostlyclean-quot: + rm -f *.insert-header diff --git a/po/boldquot.sed b/po/boldquot.sed new file mode 100644 index 0000000..4b937aa --- /dev/null +++ b/po/boldquot.sed @@ -0,0 +1,10 @@ +s/"\([^"]*\)"/“\1”/g +s/`\([^`']*\)'/‘\1’/g +s/ '\([^`']*\)' / ‘\1’ /g +s/ '\([^`']*\)'$/ ‘\1’/g +s/^'\([^`']*\)' /‘\1’ /g +s/“”/""/g +s/“/“/g +s/”/”/g +s/‘/‘/g +s/’/’/g diff --git a/po/en@boldquot.header b/po/en@boldquot.header new file mode 100644 index 0000000..fedb6a0 --- /dev/null +++ b/po/en@boldquot.header @@ -0,0 +1,25 @@ +# All this catalog "translates" are quotation characters. +# The msgids must be ASCII and therefore cannot contain real quotation +# characters, only substitutes like grave accent (0x60), apostrophe (0x27) +# and double quote (0x22). These substitutes look strange; see +# http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html +# +# This catalog translates grave accent (0x60) and apostrophe (0x27) to +# left single quotation mark (U+2018) and right single quotation mark (U+2019). +# It also translates pairs of apostrophe (0x27) to +# left single quotation mark (U+2018) and right single quotation mark (U+2019) +# and pairs of quotation mark (0x22) to +# left double quotation mark (U+201C) and right double quotation mark (U+201D). +# +# When output to an UTF-8 terminal, the quotation characters appear perfectly. +# When output to an ISO-8859-1 terminal, the single quotation marks are +# transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to +# grave/acute accent (by libiconv), and the double quotation marks are +# transliterated to 0x22. +# When output to an ASCII terminal, the single quotation marks are +# transliterated to apostrophes, and the double quotation marks are +# transliterated to 0x22. +# +# This catalog furthermore displays the text between the quotation marks in +# bold face, assuming the VT100/XTerm escape sequences. +# diff --git a/po/en@quot.header b/po/en@quot.header new file mode 100644 index 0000000..a9647fc --- /dev/null +++ b/po/en@quot.header @@ -0,0 +1,22 @@ +# All this catalog "translates" are quotation characters. +# The msgids must be ASCII and therefore cannot contain real quotation +# characters, only substitutes like grave accent (0x60), apostrophe (0x27) +# and double quote (0x22). These substitutes look strange; see +# http://www.cl.cam.ac.uk/~mgk25/ucs/quotes.html +# +# This catalog translates grave accent (0x60) and apostrophe (0x27) to +# left single quotation mark (U+2018) and right single quotation mark (U+2019). +# It also translates pairs of apostrophe (0x27) to +# left single quotation mark (U+2018) and right single quotation mark (U+2019) +# and pairs of quotation mark (0x22) to +# left double quotation mark (U+201C) and right double quotation mark (U+201D). +# +# When output to an UTF-8 terminal, the quotation characters appear perfectly. +# When output to an ISO-8859-1 terminal, the single quotation marks are +# transliterated to apostrophes (by iconv in glibc 2.2 or newer) or to +# grave/acute accent (by libiconv), and the double quotation marks are +# transliterated to 0x22. +# When output to an ASCII terminal, the single quotation marks are +# transliterated to apostrophes, and the double quotation marks are +# transliterated to 0x22. +# diff --git a/po/insert-header.sin b/po/insert-header.sin new file mode 100644 index 0000000..b26de01 --- /dev/null +++ b/po/insert-header.sin @@ -0,0 +1,23 @@ +# Sed script that inserts the file called HEADER before the header entry. +# +# At each occurrence of a line starting with "msgid ", we execute the following +# commands. At the first occurrence, insert the file. At the following +# occurrences, do nothing. The distinction between the first and the following +# occurrences is achieved by looking at the hold space. +/^msgid /{ +x +# Test if the hold space is empty. +s/m/m/ +ta +# Yes it was empty. First occurrence. Read the file. +r HEADER +# Output the file's contents by reading the next line. But don't lose the +# current line while doing this. +g +N +bb +:a +# The hold space was nonempty. Following occurrences. Do nothing. +x +:b +} diff --git a/po/quot.sed b/po/quot.sed new file mode 100644 index 0000000..0122c46 --- /dev/null +++ b/po/quot.sed @@ -0,0 +1,6 @@ +s/"\([^"]*\)"/“\1”/g +s/`\([^`']*\)'/‘\1’/g +s/ '\([^`']*\)' / ‘\1’ /g +s/ '\([^`']*\)'$/ ‘\1’/g +s/^'\([^`']*\)' /‘\1’ /g +s/“”/""/g diff --git a/po/remove-potcdate.sin b/po/remove-potcdate.sin new file mode 100644 index 0000000..2436c49 --- /dev/null +++ b/po/remove-potcdate.sin @@ -0,0 +1,19 @@ +# Sed script that remove the POT-Creation-Date line in the header entry +# from a POT file. +# +# The distinction between the first and the following occurrences of the +# pattern is achieved by looking at the hold space. +/^"POT-Creation-Date: .*"$/{ +x +# Test if the hold space is empty. +s/P/P/ +ta +# Yes it was empty. First occurrence. Remove the line. +g +d +bb +:a +# The hold space was nonempty. Following occurrences. Do nothing. +x +:b +} diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..282522d --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,2 @@ +Makefile +Makefile.in diff --git a/src/Makefile.am b/src/Makefile.am new file mode 100644 index 0000000..42185e6 --- /dev/null +++ b/src/Makefile.am @@ -0,0 +1,7 @@ +# This Makefile.am is in the public domain +SUBDIRS = include \ + multicast \ + psycutil \ + psycstore \ + psyc \ + social diff --git a/src/include/.gitignore b/src/include/.gitignore new file mode 100644 index 0000000..282522d --- /dev/null +++ b/src/include/.gitignore @@ -0,0 +1,2 @@ +Makefile +Makefile.in diff --git a/src/include/Makefile.am b/src/include/Makefile.am new file mode 100644 index 0000000..8b8bcba --- /dev/null +++ b/src/include/Makefile.am @@ -0,0 +1,15 @@ +# This Makefile.am is in the public domain +SUBDIRS = . + +gnunetincludedir = $(includedir)/gnunet + +gnunetinclude_HEADERS = \ + gnunet_multicast_service.h \ + gnunet_psycstore_plugin.h \ + gnunet_psycstore_service.h \ + gnunet_psyc_service.h \ + gnunet_psyc_util_lib.h \ + gnunet_psyc_env.h \ + gnunet_psyc_message.h \ + gnunet_psyc_slicer.h \ + gnunet_social_service.h diff --git a/src/include/gnunet_multicast_service.h b/src/include/gnunet_multicast_service.h new file mode 100644 index 0000000..58fca0b --- /dev/null +++ b/src/include/gnunet_multicast_service.h @@ -0,0 +1,925 @@ +/* + This file is part of GNUnet. + Copyright (C) 2012, 2013 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later +*/ + +/** + * @author Gabor X Toth + * @author Christian Grothoff + * + * @file + * Multicast service; multicast messaging via CADET + * + * @defgroup multicast Multicast service + * Multicast messaging via CADET. + * @{ + */ + +#ifndef GNUNET_MULTICAST_SERVICE_H +#define GNUNET_MULTICAST_SERVICE_H + +#ifdef __cplusplus +extern "C" +{ +#if 0 /* keep Emacsens' auto-indent happy */ +} +#endif +#endif + +#include "gnunet_util_lib.h" +#include "gnunet_transport_service.h" + +/** + * Version number of GNUnet-multicast API. + */ +#define GNUNET_MULTICAST_VERSION 0x00000000 + +/** + * Opaque handle for a multicast group member. + */ +struct GNUNET_MULTICAST_Member; + +/** + * Handle for the origin of a multicast group. + */ +struct GNUNET_MULTICAST_Origin; + + +enum GNUNET_MULTICAST_MessageFlags +{ + /** + * First fragment of a message. + */ + GNUNET_MULTICAST_MESSAGE_FIRST_FRAGMENT = 1 << 0, + + /** + * Last fragment of a message. + */ + GNUNET_MULTICAST_MESSAGE_LAST_FRAGMENT = 1 << 1, + + /** + * OR'ed flags if message is not fragmented. + */ + GNUNET_MULTICAST_MESSAGE_NOT_FRAGMENTED + = GNUNET_MULTICAST_MESSAGE_FIRST_FRAGMENT + | GNUNET_MULTICAST_MESSAGE_LAST_FRAGMENT, + + /** + * Historic message, used only locally when replaying messages from local + * storage. + */ + GNUNET_MULTICAST_MESSAGE_HISTORIC = 1 << 30 + +}; + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Header of a multicast message fragment. + * + * This format is public as the replay mechanism must replay message fragments using the + * same format. This is needed as we want to integrity-check message fragments within + * the multicast layer to avoid multicasting mal-formed messages. + */ +struct GNUNET_MULTICAST_MessageHeader +{ + + /** + * Header for all multicast message fragments from the origin. + */ + struct GNUNET_MessageHeader header; + + /** + * Number of hops this message fragment has taken since the origin. + * + * Helpful to determine shortest paths to the origin among honest peers for + * unicast requests from members. Updated at each hop and thus not signed and + * not secure. + */ + uint32_t hop_counter GNUNET_PACKED; + + /** + * ECC signature of the message fragment. + * + * Signature must match the public key of the multicast group. + */ + struct GNUNET_CRYPTO_EddsaSignature signature; + + /** + * Purpose for the signature and size of the signed data. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Number of the message fragment, monotonically increasing starting from 1. + */ + uint64_t fragment_id GNUNET_PACKED; + + /** + * Byte offset of this @e fragment of the @e message. + */ + uint64_t fragment_offset GNUNET_PACKED; + + /** + * Number of the message this fragment belongs to. + * + * Set in GNUNET_MULTICAST_origin_to_all(). + */ + uint64_t message_id GNUNET_PACKED; + + /** + * Counter that monotonically increases whenever a member parts the group. + * + * Set in GNUNET_MULTICAST_origin_to_all(). + * + * It has significance in case of replay requests: when a member has missed + * messages and gets a replay request: in this case if the @a group_generation + * is still the same before and after the missed messages, it means that no + * @e join or @e part operations happened during the missed messages. + */ + uint64_t group_generation GNUNET_PACKED; + + /** + * Flags for this message fragment. + * + * @see enum GNUNET_MULTICAST_MessageFlags + */ + uint32_t flags GNUNET_PACKED; + + /* Followed by message body. */ +}; + + +/** + * Header of a request from a member to the origin. + */ +struct GNUNET_MULTICAST_RequestHeader +{ + /** + * Header for all requests from a member to the origin. + */ + struct GNUNET_MessageHeader header; + + /** + * Public key of the sending member. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey member_pub_key; + + /** + * ECC signature of the request fragment. + * + * Signature must match the public key of the multicast group. + */ + struct GNUNET_CRYPTO_EcdsaSignature signature; + + /** + * Purpose for the signature and size of the signed data. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Number of the request fragment. + * Monotonically increasing from 1. + */ + uint64_t fragment_id GNUNET_PACKED; + + /** + * Byte offset of this @e fragment of the @e request. + */ + uint64_t fragment_offset GNUNET_PACKED; + + /** + * Number of the request this fragment belongs to. + * + * Set in GNUNET_MULTICAST_origin_to_all(). + */ + uint64_t request_id GNUNET_PACKED; + + /** + * Flags for this request. + */ + enum GNUNET_MULTICAST_MessageFlags flags GNUNET_PACKED; + + /* Followed by request body. */ +}; + +GNUNET_NETWORK_STRUCT_END + + +/** + * Maximum size of a multicast message fragment. + */ +#define GNUNET_MULTICAST_FRAGMENT_MAX_SIZE (63 * 1024) + +#define GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD \ + (GNUNET_MULTICAST_FRAGMENT_MAX_SIZE \ + - sizeof (struct GNUNET_MULTICAST_MessageHeader)) + + +/** + * Handle that identifies a join request. + * + * Used to match calls to #GNUNET_MULTICAST_JoinRequestCallback to the + * corresponding calls to #GNUNET_MULTICAST_join_decision(). + */ +struct GNUNET_MULTICAST_JoinHandle; + + +/** + * Function to call with the decision made for a join request. + * + * Must be called once and only once in response to an invocation of the + * #GNUNET_MULTICAST_JoinRequestCallback. + * + * @param jh + * Join request handle. + * @param is_admitted + * #GNUNET_YES if the join is approved, + * #GNUNET_NO if it is disapproved, + * #GNUNET_SYSERR if we cannot answer the request. + * @param relay_count + * Number of relays given. + * @param relays + * Array of suggested peers that might be useful relays to use + * when joining the multicast group (essentially a list of peers that + * are already part of the multicast group and might thus be willing + * to help with routing). If empty, only this local peer (which must + * be the multicast origin) is a good candidate for building the + * multicast tree. Note that it is unnecessary to specify our own + * peer identity in this array. + * @param join_resp + * Message to send in response to the joining peer; + * can also be used to redirect the peer to a different group at the + * application layer; this response is to be transmitted to the + * peer that issued the request even if admission is denied. + */ +struct GNUNET_MULTICAST_ReplayHandle * +GNUNET_MULTICAST_join_decision (struct GNUNET_MULTICAST_JoinHandle *jh, + int is_admitted, + uint16_t relay_count, + const struct GNUNET_PeerIdentity *relays, + const struct GNUNET_MessageHeader *join_resp); + + +/** + * Method called whenever another peer wants to join the multicast group. + * + * Implementations of this function must call GNUNET_MULTICAST_join_decision() + * with the decision. + * + * @param cls + * Closure. + * @param member_pub_key + * Public key of the member requesting join. + * @param join_msg + * Application-dependent join message from the new member. + * @param jh + * Join handle to pass to GNUNET_MULTICAST_join_decison(). + */ +typedef void +(*GNUNET_MULTICAST_JoinRequestCallback) (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key, + const struct GNUNET_MessageHeader *join_msg, + struct GNUNET_MULTICAST_JoinHandle *jh); + + +/** + * Method called to inform about the decision in response to a join request. + * + * If @a is_admitted is not #GNUNET_YES, then the multicast service disconnects + * the client and the multicast member handle returned by + * GNUNET_MULTICAST_member_join() is invalidated. + * + * @param cls + * Closure. + * @param is_admitted + * #GNUNET_YES or #GNUNET_NO or #GNUNET_SYSERR + * @param peer + * The peer we are connected to and the join decision is from. + * @param relay_count + * Number of peers in the @a relays array. + * @param relays + * Peer identities of members of the group, which serve as relays + * and can be used to join the group at. If empty, only the origin can + * be used to connect to the group. + * @param join_msg + * Application-dependent join message from the origin. + */ +typedef void +(*GNUNET_MULTICAST_JoinDecisionCallback) (void *cls, + int is_admitted, + const struct GNUNET_PeerIdentity *peer, + uint16_t relay_count, + const struct GNUNET_PeerIdentity *relays, + const struct GNUNET_MessageHeader *join_msg); + + +/** + * Function called whenever a group member has transmitted a request + * to the origin (other than joining or leaving). + * + * FIXME: need to distinguish between origin cancelling a message (some fragments + * were sent, then the rest 'discarded') and the case where we got disconnected; + * right now, both would mean 'msg' is NULL, but they could be quite different... + * So the semantics from the receiver side of + * GNUNET_MULTICAST_member_to_origin_cancel() are not clear here. Maybe we + * should do something with the flags in this case? + * + * @param cls + * Closure (set from GNUNET_MULTICAST_origin_start). + * @param sender + * Identity of the sender. + * @param req + * Request to the origin. + * @param flags + * Flags for the request. + */ +typedef void +(*GNUNET_MULTICAST_RequestCallback) (void *cls, + const struct GNUNET_MULTICAST_RequestHeader *req); + + +/** + * Function called whenever a group member is receiving a message fragment from + * the origin. + * + * If admission to the group is denied, this function is called once with the + * response of the @e origin (as given to GNUNET_MULTICAST_join_decision()) and + * then a second time with NULL to indicate that the connection failed for good. + * + * FIXME: need to distinguish between origin cancelling a message (some fragments + * were sent, then the rest 'discarded') and the case where we got disconnected; + * right now, both would mean 'msg' is NULL, but they could be quite different... + * So the semantics from the receiver side of + * GNUNET_MULTICAST_origin_to_all_cancel() are not clear here. + * + * @param cls + * Closure (set from GNUNET_MULTICAST_member_join()) + * @param msg + * Message from the origin, NULL if the origin shut down + * (or we were kicked out, and we should thus call + * GNUNET_MULTICAST_member_part() next) + */ +typedef void +(*GNUNET_MULTICAST_MessageCallback) (void *cls, + const struct GNUNET_MULTICAST_MessageHeader *msg); + + +/** + * Opaque handle to a replay request from the multicast service. + */ +struct GNUNET_MULTICAST_ReplayHandle; + + +/** + * Functions with this signature are called whenever the multicast service needs + * a message fragment to be replayed by fragment_id. + * + * Implementations of this function MUST call GNUNET_MULTICAST_replay() ONCE + * (with a message or an error); however, if the origin is destroyed or the + * group is left, the replay handle must no longer be used. + * + * @param cls + * Closure (set from GNUNET_MULTICAST_origin_start() + * or GNUNET_MULTICAST_member_join()). + * @param member_pub_key + * The member requesting replay. + * @param fragment_id + * Which message fragment should be replayed. + * @param flags + * Flags for the replay. + * @param rh + * Handle to pass to message transmit function. + */ +typedef void +(*GNUNET_MULTICAST_ReplayFragmentCallback) (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key, + uint64_t fragment_id, + uint64_t flags, + struct GNUNET_MULTICAST_ReplayHandle *rh); + +/** + * Functions with this signature are called whenever the multicast service needs + * a message fragment to be replayed by message_id and fragment_offset. + * + * Implementations of this function MUST call GNUNET_MULTICAST_replay() ONCE + * (with a message or an error); however, if the origin is destroyed or the + * group is left, the replay handle must no longer be used. + * + * @param cls + * Closure (set from GNUNET_MULTICAST_origin_start() + * or GNUNET_MULTICAST_member_join()). + * @param member_pub_key + * The member requesting replay. + * @param message_id + * Which message should be replayed. + * @param fragment_offset + * Offset of the fragment within of @a message_id to be replayed. + * @param flags + * Flags for the replay. + * @param rh + * Handle to pass to message transmit function. + */ +typedef void +(*GNUNET_MULTICAST_ReplayMessageCallback) (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key, + uint64_t message_id, + uint64_t fragment_offset, + uint64_t flags, + struct GNUNET_MULTICAST_ReplayHandle *rh); + + +/** + * Possible error codes during replay. + */ +enum GNUNET_MULTICAST_ReplayErrorCode +{ + + /** + * Everything is fine. + */ + GNUNET_MULTICAST_REC_OK = 0, + + /** + * Message fragment not found in the message store. + * + * Either discarded if it is too old, or not arrived yet if this member has + * missed some messages. + */ + GNUNET_MULTICAST_REC_NOT_FOUND = 1, + + /** + * Fragment ID counter was larger than the highest counter this + * replay function has ever encountered; thus it is likely the + * origin never sent it and we're at the HEAD of the multicast + * stream as far as this node is concerned. + * + * FIXME: needed? + */ + GNUNET_MULTICAST_REC_PAST_HEAD = 2, + + /** + * Access is denied to the requested fragment, membership test did not pass. + */ + GNUNET_MULTICAST_REC_ACCESS_DENIED = 3, + + /** + * Internal error (i.e. database error). Try some other peer. + */ + GNUNET_MULTICAST_REC_INTERNAL_ERROR = 4 + +}; + + +/** + * Replay a message fragment for the multicast group. + * + * @param rh + * Replay handle identifying which replay operation was requested. + * @param msg + * Replayed message fragment, NULL if not found / an error occurred. + * @param ec + * Error code. See enum GNUNET_MULTICAST_ReplayErrorCode + * If not #GNUNET_MULTICAST_REC_OK, the replay handle is invalidated. + */ +void +GNUNET_MULTICAST_replay_response (struct GNUNET_MULTICAST_ReplayHandle *rh, + const struct GNUNET_MessageHeader *msg, + enum GNUNET_MULTICAST_ReplayErrorCode ec); + + +/** + * Indicate the end of the replay session. + * + * Invalidates the replay handle. + * + * @param rh Replay session to end. + */ +void +GNUNET_MULTICAST_replay_response_end (struct GNUNET_MULTICAST_ReplayHandle *rh); + + +/** + * Function called to provide data for a transmission for a replay. + * + * @see GNUNET_MULTICAST_replay2() + */ +typedef int +(*GNUNET_MULTICAST_ReplayTransmitNotify) (void *cls, + size_t *data_size, + void *data); + + +/** + * Replay a message for the multicast group. + * + * @param rh + * Replay handle identifying which replay operation was requested. + * @param notify + * Function to call to get the message. + * @param notify_cls + * Closure for @a notify. + */ +void +GNUNET_MULTICAST_replay_response2 (struct GNUNET_MULTICAST_ReplayHandle *rh, + GNUNET_MULTICAST_ReplayTransmitNotify notify, + void *notify_cls); + + +/** + * Start a multicast group. + * + * Peers that issue GNUNET_MULTICAST_member_join() can transmit a join request + * to either an existing group member or to the origin. If the joining is + * approved, the member is cleared for @e replay and will begin to receive + * messages transmitted to the group. If joining is disapproved, the failed + * candidate will be given a response. Members in the group can send messages + * to the origin. + * + * TODO: This function could optionally offer to advertise the origin in the + * P2P overlay network(where?) under the respective public key so that other + * peers can find an alternate PeerId to join it. Higher level protocols may + * however provide other means of solving the problem of the offline host + * (see secushare specs about that) and therefore merely need a way to provide + * a list of possible PeerIds. + * + * @param cfg + * Configuration to use. + * @param priv_key + * ECC key that will be used to sign messages for this + * multicast session; public key is used to identify the multicast group; + * @param max_fragment_id + * Maximum fragment ID already sent to the group. + * 0 for a new group. + * @param join_request_cb + * Function called to approve / disapprove joining of a peer. + * @param replay_frag_cb + * Function that can be called to replay a message fragment. + * @param replay_msg_cb + * Function that can be called to replay a message. + * @param request_cb + * Function called with message fragments from group members. + * @param message_cb + * Function called with the message fragments sent to the + * network by GNUNET_MULTICAST_origin_to_all(). These message fragments + * should be stored for answering replay requests later. + * @param cls + * Closure for the various callbacks that follow. + * + * @return Handle for the origin, NULL on error. + */ +struct GNUNET_MULTICAST_Origin * +GNUNET_MULTICAST_origin_start (const struct GNUNET_CONFIGURATION_Handle *cfg, + const struct GNUNET_CRYPTO_EddsaPrivateKey *priv_key, + uint64_t max_fragment_id, + GNUNET_MULTICAST_JoinRequestCallback join_request_cb, + GNUNET_MULTICAST_ReplayFragmentCallback replay_frag_cb, + GNUNET_MULTICAST_ReplayMessageCallback replay_msg_cb, + GNUNET_MULTICAST_RequestCallback request_cb, + GNUNET_MULTICAST_MessageCallback message_cb, + void *cls); + +/** + * Function called to provide data for a transmission from the origin to all + * members. + * + * Note that returning #GNUNET_OK or #GNUNET_SYSERR (but not #GNUNET_NO) + * invalidates the respective transmission handle. + * + * @param cls + * Closure. + * @param[in,out] data_size + * Initially set to the number of bytes available in + * @a data, should be set to the number of bytes written to data. + * @param[out] data + * Where to write the body of the message to give to the + * method. The function must copy at most @a data_size bytes to @a data. + * + * @return #GNUNET_SYSERR on error (fatal, aborts transmission) + * #GNUNET_NO on success, if more data is to be transmitted later. + * Should be used if @a data_size was not big enough to take all the + * data. If 0 is returned in @a data_size the transmission is paused, + * and can be resumed with GNUNET_MULTICAST_origin_to_all_resume(). + * #GNUNET_YES if this completes the transmission (all data supplied) + * @deprecated should move to MQ-style API! + */ +typedef int +(*GNUNET_MULTICAST_OriginTransmitNotify) (void *cls, + size_t *data_size, + void *data); + + +/** + * Handle for a request to send a message to all multicast group members + * (from the origin). + */ +struct GNUNET_MULTICAST_OriginTransmitHandle; + + +/** + * Send a message to the multicast group. + * + * @param origin + * Handle to the multicast group. + * @param message_id + * Application layer ID for the message. Opaque to multicast. + * @param group_generation + * Group generation of the message. Documented in + * struct GNUNET_MULTICAST_MessageHeader. + * @param notify + * Function to call to get the message. + * @param notify_cls + * Closure for @a notify. + * + * @return NULL on error (i.e. request already pending). + * @deprecated should move to MQ-style API! + */ +struct GNUNET_MULTICAST_OriginTransmitHandle * +GNUNET_MULTICAST_origin_to_all (struct GNUNET_MULTICAST_Origin *origin, + uint64_t message_id, + uint64_t group_generation, + GNUNET_MULTICAST_OriginTransmitNotify notify, + void *notify_cls); + + + +/** + * Resume message transmission to multicast group. + * + * @param th Transmission to cancel. + */ +void +GNUNET_MULTICAST_origin_to_all_resume (struct GNUNET_MULTICAST_OriginTransmitHandle *th); + + +/** + * Cancel request for message transmission to multicast group. + * + * @param th Transmission to cancel. + */ +void +GNUNET_MULTICAST_origin_to_all_cancel (struct GNUNET_MULTICAST_OriginTransmitHandle *th); + + +/** + * Stop a multicast group. + * + * @param origin Multicast group to stop. + */ +void +GNUNET_MULTICAST_origin_stop (struct GNUNET_MULTICAST_Origin *origin, + GNUNET_ContinuationCallback stop_cb, + void *stop_cls); + + +/** + * Join a multicast group. + * + * The entity joining is always the local peer. Further information about the + * candidate can be provided in @a join_msg. If the join fails, the + * @a message_cb is invoked with a (failure) response and then with NULL. If + * the join succeeds, outstanding (state) messages and ongoing multicast + * messages will be given to the @a message_cb until the member decides to part + * the group. The @a mem_test_cb and @a replay_cb functions may be called at + * anytime by the multicast service to support relaying messages to other + * members of the group. + * + * @param cfg + * Configuration to use. + * @param group_key + * ECC public key that identifies the group to join. + * @param member_pub_key + * ECC key that identifies the member + * and used to sign requests sent to the origin. + * @param origin + * Peer ID of the origin to send unicast requsets to. If NULL, + * unicast requests are sent back via multiple hops on the reverse path + * of multicast messages. + * @param relay_count + * Number of peers in the @a relays array. + * @param relays + * Peer identities of members of the group, which serve as relays + * and can be used to join the group at. and send the @a join_request to. + * If empty, the @a join_request is sent directly to the @a origin. + * @param join_msg + * Application-dependent join message to be passed to the peer @a origin. + * @param join_request_cb + * Function called to approve / disapprove joining of a peer. + * @param join_decision_cb + * Function called to inform about the join decision. + * @param replay_frag_cb + * Function that can be called to replay message fragments + * this peer already knows from this group. NULL if this + * client is unable to support replay. + * @param replay_msg_cb + * Function that can be called to replay message fragments + * this peer already knows from this group. NULL if this + * client is unable to support replay. + * @param message_cb + * Function to be called for all message fragments we + * receive from the group, excluding those our @a replay_cb + * already has. + * @param cls + * Closure for callbacks. + * + * @return Handle for the member, NULL on error. + */ +struct GNUNET_MULTICAST_Member * +GNUNET_MULTICAST_member_join (const struct GNUNET_CONFIGURATION_Handle *cfg, + const struct GNUNET_CRYPTO_EddsaPublicKey *group_key, + const struct GNUNET_CRYPTO_EcdsaPrivateKey *member_pub_key, + const struct GNUNET_PeerIdentity *origin, + uint16_t relay_count, + const struct GNUNET_PeerIdentity *relays, + const struct GNUNET_MessageHeader *join_request, + GNUNET_MULTICAST_JoinRequestCallback join_request_cb, + GNUNET_MULTICAST_JoinDecisionCallback join_decision_cb, + GNUNET_MULTICAST_ReplayFragmentCallback replay_frag_cb, + GNUNET_MULTICAST_ReplayMessageCallback replay_msg_cb, + GNUNET_MULTICAST_MessageCallback message_cb, + void *cls); + +/** + * Handle for a replay request. + */ +struct GNUNET_MULTICAST_MemberReplayHandle; + + +/** + * Request a fragment to be replayed by fragment ID. + * + * Useful if messages below the @e max_known_fragment_id given when joining are + * needed and not known to the client. + * + * @param member + * Membership handle. + * @param fragment_id + * ID of a message fragment that this client would like to see replayed. + * @param flags + * Additional flags for the replay request. + * It is used and defined by GNUNET_MULTICAST_ReplayFragmentCallback + * + * @return Replay request handle, NULL on error. + */ +struct GNUNET_MULTICAST_MemberReplayHandle * +GNUNET_MULTICAST_member_replay_fragment (struct GNUNET_MULTICAST_Member *member, + uint64_t fragment_id, + uint64_t flags); + + +/** + * Request a message fr to be replayed. + * + * Useful if messages below the @e max_known_fragment_id given when joining are + * needed and not known to the client. + * + * @param member + * Membership handle. + * @param message_id + * ID of the message this client would like to see replayed. + * @param fragment_offset + * Offset of the fragment within the message to replay. + * @param flags + * Additional flags for the replay request. + * It is used & defined by GNUNET_MULTICAST_ReplayMessageCallback + * + * @return Replay request handle, NULL on error. + */ +struct GNUNET_MULTICAST_MemberReplayHandle * +GNUNET_MULTICAST_member_replay_message (struct GNUNET_MULTICAST_Member *member, + uint64_t message_id, + uint64_t fragment_offset, + uint64_t flags); + + +/** + * Cancel a replay request. + * + * @param rh + * Request to cancel. + */ +void +GNUNET_MULTICAST_member_replay_cancel (struct GNUNET_MULTICAST_MemberReplayHandle *rh); + + +/** + * Part a multicast group. + * + * Disconnects from all group members and invalidates the @a member handle. + * + * An application-dependent part message can be transmitted beforehand using + * #GNUNET_MULTICAST_member_to_origin()) + * + * @param member + * Membership handle. + */ +void +GNUNET_MULTICAST_member_part (struct GNUNET_MULTICAST_Member *member, + GNUNET_ContinuationCallback part_cb, + void *part_cls); + + +/** + * Function called to provide data for a transmission from a member to the origin. + * + * Note that returning #GNUNET_OK or #GNUNET_SYSERR (but not #GNUNET_NO) + * invalidates the respective transmission handle. + * + * @param cls + * Closure. + * @param[in,out] data_size + * Initially set to the number of bytes available in + * @a data, should be set to the number of bytes written to data. + * @param[out] data + * Where to write the body of the message to give to the + * method. The function must copy at most @a data_size bytes to @a data. + * + * @return #GNUNET_SYSERR on error (fatal, aborts transmission) + * #GNUNET_NO on success, if more data is to be transmitted later. + * Should be used if @a data_size was not big enough to take all the + * data. If 0 is returned in @a data_size the transmission is paused, + * and can be resumed with GNUNET_MULTICAST_member_to_origin_resume(). + * #GNUNET_YES if this completes the transmission (all data supplied) + * @deprecated should move to MQ-style API! + */ +typedef int +(*GNUNET_MULTICAST_MemberTransmitNotify) (void *cls, + size_t *data_size, + void *data); + + +/** + * Handle for a message to be delivered from a member to the origin. + */ +struct GNUNET_MULTICAST_MemberTransmitHandle; + + +/** + * Send a message to the origin of the multicast group. + * + * @param member + * Membership handle. + * @param request_id + * Application layer ID for the request. Opaque to multicast. + * @param notify + * Callback to call to get the message. + * @param notify_cls + * Closure for @a notify. + * + * @return Handle to cancel request, NULL on error (i.e. request already pending). + * @deprecated should move to MQ-style API! + */ +struct GNUNET_MULTICAST_MemberTransmitHandle * +GNUNET_MULTICAST_member_to_origin (struct GNUNET_MULTICAST_Member *member, + uint64_t request_id, + GNUNET_MULTICAST_MemberTransmitNotify notify, + void *notify_cls); + + +/** + * Resume message transmission to origin. + * + * @param th + * Transmission to cancel. + */ +void +GNUNET_MULTICAST_member_to_origin_resume (struct GNUNET_MULTICAST_MemberTransmitHandle *th); + + +/** + * Cancel request for message transmission to origin. + * + * @param th + * Transmission to cancel. + */ +void +GNUNET_MULTICAST_member_to_origin_cancel (struct GNUNET_MULTICAST_MemberTransmitHandle *th); + + +#if 0 /* keep Emacsens' auto-indent happy */ +{ +#endif +#ifdef __cplusplus +} +#endif + +/* ifndef GNUNET_MULTICAST_SERVICE_H */ +#endif + +/** @} */ /* end of group */ diff --git a/src/include/gnunet_psyc_env.h b/src/include/gnunet_psyc_env.h new file mode 100644 index 0000000..0d878cb --- /dev/null +++ b/src/include/gnunet_psyc_env.h @@ -0,0 +1,340 @@ +/* + * This file is part of GNUnet. + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @author Gabor X Toth + * + * @file + * PSYC Environment library + * + * @defgroup psyc-util-env PSYC Utilities library: Environment + * Environment data structure operations for PSYC and Social messages. + * + * Library providing operations for the @e environment of + * PSYC and Social messages, and for (de)serializing variable values. + * + * @{ + */ + + +#ifndef GNUNET_PSYC_ENV_H +#define GNUNET_PSYC_ENV_H + +#ifdef __cplusplus +extern "C" +{ +#if 0 /* keep Emacsens' auto-indent happy */ +} +#endif +#endif + + +/** + * Possible operations on PSYC state (persistent) and transient variables (per message). + */ +enum GNUNET_PSYC_Operator +{ + /** + * Set value of a transient variable. + */ + GNUNET_PSYC_OP_SET = ':', + + /** + * Assign value for a persistent state variable. + * + * If an assigned value is NULL, the variable is deleted. + */ + GNUNET_PSYC_OP_ASSIGN = '=', + + /** + * Augment state variable. + * + * Used for appending strings, adding numbers, and adding new items to a list or dictionary. + */ + GNUNET_PSYC_OP_AUGMENT = '+', + + /** + * Diminish state variable. + * + * Used for subtracting numbers, and removing items from a list or dictionary. + */ + GNUNET_PSYC_OP_DIMINISH = '-', + + /** + * Update state variable. + * + * Used for modifying a single item of a list or dictionary. + */ + GNUNET_PSYC_OP_UPDATE = '@', +}; + + +/** + * PSYC variable types. + */ +enum GNUNET_PSYC_Type +{ + GNUNET_PSYC_TYPE_DATA = 0, + GNUNET_PSYC_TYPE_NUMBER, + GNUNET_PSYC_TYPE_LIST, + GNUNET_PSYC_TYPE_DICT +}; + + +/** + * PSYC state modifier. + */ +struct GNUNET_PSYC_Modifier +{ + /** + * State operation. + */ + enum GNUNET_PSYC_Operator oper; + + /** + * Variable name. + */ + const char *name; + + /** + * Size of @a value. + */ + size_t value_size; + + /** + * Value of variable. + */ + const void *value; + + /** + * Next modifier. + */ + struct GNUNET_PSYC_Modifier *next; + + /** + * Previous modifier. + */ + struct GNUNET_PSYC_Modifier *prev; +}; + + +/** + * Environment for a message. + * + * Contains modifiers. + */ +struct GNUNET_PSYC_Environment; + + +/** + * Create an environment. + * + * @return A newly allocated environment. + */ +struct GNUNET_PSYC_Environment * +GNUNET_PSYC_env_create (); + + +/** + * Add a modifier to the environment. + * + * @param env The environment. + * @param oper Operation to perform. + * @param name Name of the variable. + * @param value Value of the variable. + * @param value_size Size of @a value. + */ +void +GNUNET_PSYC_env_add (struct GNUNET_PSYC_Environment *env, + enum GNUNET_PSYC_Operator oper, const char *name, + const void *value, size_t value_size); + + +/** + * Get the first modifier of the environment. + */ +struct GNUNET_PSYC_Modifier * +GNUNET_PSYC_env_head (const struct GNUNET_PSYC_Environment *env); + + + +/** + * Get the last modifier of the environment. + */ +struct GNUNET_PSYC_Modifier * +GNUNET_PSYC_env_tail (const struct GNUNET_PSYC_Environment *env); + + +/** + * Remove a modifier from the environment. + */ +void +GNUNET_PSYC_env_remove (struct GNUNET_PSYC_Environment *env, + struct GNUNET_PSYC_Modifier *mod); + + +/** + * Remove a modifier at the beginning of the environment. + */ +int +GNUNET_PSYC_env_shift (struct GNUNET_PSYC_Environment *env, + enum GNUNET_PSYC_Operator *oper, const char **name, + const void **value, size_t *value_size); + + +/** + * Iterator for modifiers in the environment. + * + * @param cls Closure. + * @param mod Modifier. + * + * @return #GNUNET_YES to continue iterating, + * #GNUNET_NO to stop. + */ +typedef int +(*GNUNET_PSYC_Iterator) (void *cls, enum GNUNET_PSYC_Operator oper, + const char *name, const char *value, + uint32_t value_size); + + +/** + * Iterate through all modifiers in the environment. + * + * @param env The environment. + * @param it Iterator. + * @param it_cls Closure for iterator. + */ +void +GNUNET_PSYC_env_iterate (const struct GNUNET_PSYC_Environment *env, + GNUNET_PSYC_Iterator it, void *it_cls); + + +/** + * Get the number of modifiers in the environment. + * + * @param env The environment. + * + * @return Number of modifiers. + */ +size_t +GNUNET_PSYC_env_get_count (const struct GNUNET_PSYC_Environment *env); + + +/** + * Destroy an environment. + * + * @param env The environment to destroy. + */ +void +GNUNET_PSYC_env_destroy (struct GNUNET_PSYC_Environment *env); + + +/** + * Get the type of variable. + * + * @param name Name of the variable. + * + * @return Variable type. + */ +enum GNUNET_PSYC_Type +GNUNET_PSYC_var_get_type (char *name); + + +/** + * Perform an operation on a variable. + * + * @param name Name of variable. + * @param current_value Current value of variable. + * @param current_value_size Size of @a current_value. + * @param oper Operator. + * @param args Arguments for the operation. + * @param args_size Size of @a args. + * @param return_value Return value. + * @param return_value_size Size of @a return_value. + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +int +GNUNET_PSYC_operation (char *name, void *current_value, size_t current_value_size, + enum GNUNET_PSYC_Operator oper, void *args, size_t args_size, + void **return_value, size_t *return_value_size); + + +/** + * Get the variable's value as an integer. + * + * @param size Size of value. + * @param value Raw value of variable. + * @param[out] number Value converted to a 64-bit integer. + * + * @return #GNUNET_OK on success, #GNUNET_SYSERR if an error occurred (e.g. the value is invalid). + */ +int +GNUNET_PSYC_value_to_number (size_t size, const void *value, int64_t *number); + + +/** + * Get the variable's value as a dictionary. + * + * @param size Size of value. + * @param value Raw value of variable. + * @param[out] dict A newly created hashmap holding the elements of the dictionary. + * + * @return #GNUNET_OK on success, #GNUNET_SYSERR if an error occurred (e.g. the value is invalid). + */ +int +GNUNET_PSYC_value_to_dict (size_t size, const void *value, struct GNUNET_CONTAINER_MultiHashMap **dict); + + +/** + * Create a PSYC variable value from an integer. + * + * @param number The number to convert. + * @param[out] value_size Size of returned value. + * + * @return A newly allocated value or NULL on error. + */ +void * +GNUNET_PSYC_value_from_number (int64_t number, size_t *value_size); + + +/** + * Create a PSYC variable value from a dictionary. + * + * @param dict The dict to convert. + * @param[out] value_size Size of returned value. + * + * @return A newly allocated value or NULL on error. + */ +void * +GNUNET_PSYC_value_from_dict (struct GNUNET_CONTAINER_MultiHashMap *dict, size_t *value_size); + + +#if 0 /* keep Emacsens' auto-indent happy */ +{ +#endif +#ifdef __cplusplus +} +#endif + +/* ifndef GNUNET_PSYC_ENV_H */ +#endif + +/** @} */ /* end of group */ diff --git a/src/include/gnunet_psyc_message.h b/src/include/gnunet_psyc_message.h new file mode 100644 index 0000000..d0cf9cc --- /dev/null +++ b/src/include/gnunet_psyc_message.h @@ -0,0 +1,278 @@ +/* + This file is part of GNUnet. + Copyright (C) 2012, 2013 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later +*/ + +/** + * @author Gabor X Toth + * + * @file + * PSYC message utilities; receiving/transmitting/logging PSYC messages + * + * @defgroup psyc-util-message PSYC Utilities library: Messages + * Receiving, transmitting, logging PSYC messages. + * @{ + */ + +#ifndef GNUNET_PSYC_MESSAGE_H +#define GNUNET_PSYC_MESSAGE_H + +#ifdef __cplusplus +extern "C" +{ +#if 0 /* keep Emacsens' auto-indent happy */ +} +#endif +#endif + + +#include "gnunet_util_lib.h" +#include "gnunet_psyc_util_lib.h" +#include "gnunet_psyc_service.h" + + +/** + * Create a PSYC message. + * + * @param method_name + * PSYC method for the message. + * @param env + * Environment for the message. + * @param data + * Data payload for the message. + * @param data_size + * Size of @a data. + * + * @return Message header with size information, + * followed by the message parts. + * + * FIXME: arg order + */ +struct GNUNET_PSYC_Message * +GNUNET_PSYC_message_create (const char *method_name, + const struct GNUNET_PSYC_Environment *env, + const void *data, + size_t data_size); + +/** + * Parse PSYC message. + * + * @param msg + * The PSYC message to parse. + * @param env + * The environment for the message with a list of modifiers. + * @param[out] method_name + * Pointer to the method name inside @a pmsg. + * @param[out] data + * Pointer to data inside @a pmsg. + * @param[out] data_size + * Size of @data is written here. + * + * @return #GNUNET_OK on success, + * #GNUNET_SYSERR on parse error. + * + * FIXME: arg order + */ +int +GNUNET_PSYC_message_parse (const struct GNUNET_PSYC_MessageHeader *msg, + const char **method_name, + struct GNUNET_PSYC_Environment *env, + const void **data, + uint16_t *data_size); + + +void +GNUNET_PSYC_log_message (enum GNUNET_ErrorType kind, + const struct GNUNET_MessageHeader *msg); + + +struct GNUNET_PSYC_TransmitHandle; + +/** + * Create a transmission handle. + */ +struct GNUNET_PSYC_TransmitHandle * +GNUNET_PSYC_transmit_create (struct GNUNET_MQ_Handle *mq); + + +/** + * Destroy a transmission handle. + */ +void +GNUNET_PSYC_transmit_destroy (struct GNUNET_PSYC_TransmitHandle *tmit); + + +/** + * Transmit a message. + * + * @param tmit + * Transmission handle. + * @param method_name + * Which method should be invoked. + * @param env + * Environment for the message. + * Should stay available until the first call to notify_data. + * Can be NULL if there are no modifiers or @a notify_mod is + * provided instead. + * @param notify_mod + * Function to call to obtain modifiers. + * Can be NULL if there are no modifiers or @a env is provided instead. + * @param notify_data + * Function to call to obtain fragments of the data. + * @param notify_cls + * Closure for @a notify_mod and @a notify_data. + * @param flags + * Flags for the message being transmitted. + * + * @return #GNUNET_OK if the transmission was started. + * #GNUNET_SYSERR if another transmission is already going on. + */ +int +GNUNET_PSYC_transmit_message (struct GNUNET_PSYC_TransmitHandle *tmit, + const char *method_name, + const struct GNUNET_PSYC_Environment *env, + GNUNET_PSYC_TransmitNotifyModifier notify_mod, + GNUNET_PSYC_TransmitNotifyData notify_data, + void *notify_cls, + uint32_t flags); + + +/** + * Resume transmission. + * + * @param tmit Transmission handle. + */ +void +GNUNET_PSYC_transmit_resume (struct GNUNET_PSYC_TransmitHandle *tmit); + + +/** + * Abort transmission request. + * + * @param tmit Transmission handle. + */ +void +GNUNET_PSYC_transmit_cancel (struct GNUNET_PSYC_TransmitHandle *tmit); + + +/** + * Got acknowledgement of a transmitted message part, continue transmission. + * + * @param tmit Transmission handle. + */ +void +GNUNET_PSYC_transmit_got_ack (struct GNUNET_PSYC_TransmitHandle *tmit); + + +struct GNUNET_PSYC_ReceiveHandle; + + +/** + * Create handle for receiving messages. + */ +struct GNUNET_PSYC_ReceiveHandle * +GNUNET_PSYC_receive_create (GNUNET_PSYC_MessageCallback message_cb, + GNUNET_PSYC_MessagePartCallback message_part_cb, + void *cb_cls); + + +/** + * Destroy handle for receiving messages. + */ +void +GNUNET_PSYC_receive_destroy (struct GNUNET_PSYC_ReceiveHandle *recv); + + +/** + * Reset stored data related to the last received message. + */ +void +GNUNET_PSYC_receive_reset (struct GNUNET_PSYC_ReceiveHandle *recv); + + +/** + * Handle incoming PSYC message. + * + * @param recv + * Receive handle. + * @param msg + * The message. + * + * @return #GNUNET_OK on success, + * #GNUNET_SYSERR on receive error. + */ +int +GNUNET_PSYC_receive_message (struct GNUNET_PSYC_ReceiveHandle *recv, + const struct GNUNET_PSYC_MessageHeader *msg); + + +/** + * Check if @a data contains a series of valid message parts. + * + * @param data_size + * Size of @a data. + * @param data + * Data. + * @param[out] first_ptype + * Type of first message part. + * @param[out] last_ptype + * Type of last message part. + * + * @return Number of message parts found in @a data. + * or GNUNET_SYSERR if the message contains invalid parts. + */ +int +GNUNET_PSYC_receive_check_parts (uint16_t data_size, const char *data, + uint16_t *first_ptype, uint16_t *last_ptype); + + +/** + * Initialize PSYC message header. + */ +void +GNUNET_PSYC_message_header_init (struct GNUNET_PSYC_MessageHeader *pmsg, + const struct GNUNET_MULTICAST_MessageHeader *mmsg, + uint32_t flags); + + +/** + * Create a new PSYC message header from a multicast message for sending it to clients. + */ +struct GNUNET_PSYC_MessageHeader * +GNUNET_PSYC_message_header_create (const struct GNUNET_MULTICAST_MessageHeader *mmsg, + uint32_t flags); + + +/** + * Create a new PSYC message header from a PSYC message. + */ +struct GNUNET_PSYC_MessageHeader * +GNUNET_PSYC_message_header_create_from_psyc (const struct GNUNET_PSYC_Message *msg); + + +#if 0 /* keep Emacsens' auto-indent happy */ +{ +#endif +#ifdef __cplusplus +} +#endif + +/* ifndef GNUNET_PSYC_MESSAGE_H */ +#endif + +/** @} */ /* end of group */ diff --git a/src/include/gnunet_psyc_service.h b/src/include/gnunet_psyc_service.h new file mode 100644 index 0000000..3a3131e --- /dev/null +++ b/src/include/gnunet_psyc_service.h @@ -0,0 +1,1364 @@ +/* + This file is part of GNUnet. + Copyright (C) 2012, 2013 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later +*/ + +/** + * @author Gabor X Toth + * @author Christian Grothoff + * + * @file + * PSYC service + * + * @defgroup psyc PSYC service + * Send/receive messages in PSYC channels and access the PSYC Store. + * + * Note that clients of this API are NOT expected to understand the PSYC message + * format, only the semantics! Parsing (and serializing) the PSYC stream format + * is done within the implementation of the libgnunetpsyc library, and this API + * deliberately exposes as little as possible of the actual data stream format + * to the application! + * + * NOTE: + * - this API does not know about PSYC's "root" and "places"; + * there is no 'root' in GNUnet-PSYC as we're decentralized; + * 'places' and 'persons' are combined within the same + * abstraction, that of a "channel". Channels are identified + * and accessed in this API using a public/private key. + * Higher-level applications should use NAMES within GNS + * to obtain public keys, and the distinction between + * 'places' and 'persons' can then be made with the help + * of the naming system (and/or conventions). + * Channels are (as in PSYC) organized into a hierarchy; each + * channel master (the one with the private key) is then + * the operator of the multicast group (its Origin in + * the terminology of the multicast API). + * - The API supports passing large amounts of data using + * 'streaming' for the argument passed to a method. State + * and variables must fit into memory and cannot be streamed + * (thus, no passing of 4 GB of data in a variable; + * once we implement this, we might want to create a + * @c \#define for the maximum size of a variable). + * - PSYC defines standard variables, methods, etc. This + * library deliberately abstracts over all of these; a + * higher-level API should combine the naming system (GNS) + * and standard methods (_converse, _notice, _request, + * _warning, _error etc) and variables (_action, _color, + * _time, etc). However, this API does take over the + * routing variables, specifically '_context' (channel), + * and '_source'. We only kind-of support '_target', as + * the target is either everyone in the group or the + * origin, and never just a single member of the group; + * for such individual messages, an application needs to + * construct an 'inbox' channel where the master (only) + * receives messages (but never forwards; private responses + * would be transmitted by joining the senders 'inbox' + * channel -- or a inbox#bob subchannel). The + * goal for all of this is to keep the abstractions in this + * API minimal: interaction with multicast, try \& slice, + * state/variable/channel management. Higher-level + * operations belong elsewhere (so maybe this API should + * be called 'PSYC-low', whereas a higher-level API + * implementing defaults for standard methods and + * variables might be called 'PSYC-std' or 'PSYC-high'. + * + * In PSYC terminology this is simply called the "PSYC + * routing layer" and the abstractions, for instance in + * psyced, are quite similar. The higher one is called + * "PSYC entity layer." In the text rendering of the + * protocol the two are separated by an empty line. See + * http://about.psyc.eu/Spec:Packet and related. --lynX + * + * @{ + */ + +#ifndef GNUNET_PSYC_SERVICE_H +#define GNUNET_PSYC_SERVICE_H + +#ifdef __cplusplus +extern "C" +{ +#if 0 /* keep Emacsens' auto-indent happy */ +} +#endif +#endif + +#include "gnunet_util_lib.h" +#include "gnunet_multicast_service.h" +//Mingw work around +#ifdef MINGW + # ifndef UINT64_MAX + # define UINT64_MAX 0xffffffffffffffffULL + # endif +#endif + +/** + * Version number of GNUnet-PSYC API. + */ +#define GNUNET_PSYC_VERSION 0x00000000 + + +/** + * Policy flags for a channel. + */ +enum GNUNET_PSYC_ChannelFlags +{ + /** + * Admission must be confirmed by the master. + */ + GNUNET_PSYC_CHANNEL_ADMISSION_CONTROL = 1 << 0, + + /** + * Past messages are only available to slaves who were admitted at the time + * they were sent to the channel. + */ + GNUNET_PSYC_CHANNEL_RESTRICTED_HISTORY = 1 << 1 +}; + + +/** + * PSYC channel policies. + */ +enum GNUNET_PSYC_Policy +{ + /** + * Anyone can join the channel, without announcing their presence; + * all messages are always public and can be distributed freely. + * Joins may be announced, but this is not required. + */ + GNUNET_PSYC_CHANNEL_ANONYMOUS = 0, + + /** + * The master must approve membership to the channel, messages must only be + * distributed to current channel slaves. This includes the channel + * state as well as transient messages. + */ + GNUNET_PSYC_CHANNEL_PRIVATE + = GNUNET_PSYC_CHANNEL_ADMISSION_CONTROL + | GNUNET_PSYC_CHANNEL_RESTRICTED_HISTORY + +#if IDEAS_FOR_FUTURE + /** + * Anyone can freely join the channel (no approval required); + * however, messages must only be distributed to current channel + * slaves, so the master must still acknowledge that the slave + * joined before transient messages are delivered. As approval is + * guaranteed, the presistent channel state can be synchronized freely + * immediately, prior to master confirmation. + */ + GNUNET_PSYC_CHANNEL_OPEN + = GNUNET_PSYC_CHANNEL_RESTRICTED_HISTORY, + + /** + * The master must approve joins to the channel, but past messages can be + * freely distributed to slaves. + */ + GNUNET_PSYC_CHANNEL_CLOSED + = GNUNET_PSYC_CHANNEL_ADMISSION_CONTROL, +#endif +}; + + +enum GNUNET_PSYC_MessageFlags +{ + /** + * Default / no flags. + */ + GNUNET_PSYC_MESSAGE_DEFAULT = 0, + + /** + * Historic message, retrieved from PSYCstore. + */ + GNUNET_PSYC_MESSAGE_HISTORIC = 1 << 0, + + /** + * Request from slave to master. + */ + GNUNET_PSYC_MESSAGE_REQUEST = 1 << 1, + + /** + * Message can be delivered out of order. + */ + GNUNET_PSYC_MESSAGE_ORDER_ANY = 1 << 2 +}; + + +/** + * Values for the @a state_delta field of GNUNET_PSYC_MessageHeader. + */ +enum GNUNET_PSYC_StateDeltaValues +{ + GNUNET_PSYC_STATE_RESET = 0, + + GNUNET_PSYC_STATE_NOT_MODIFIED = UINT64_MAX +}; + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * A PSYC message. + * + * Used for single-fragment messages e.g. in a join request or response. + */ +struct GNUNET_PSYC_Message +{ + /** + * Message header with size and type information. + */ + struct GNUNET_MessageHeader header; + + /* Followed by concatenated PSYC message parts: + * messages with GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_* types + */ +}; + + +/** + * Header of a PSYC message. + * + * The PSYC service adds this when delivering the message to local clients, + * not present on the multicast layer. + */ +struct GNUNET_PSYC_MessageHeader +{ + /** + * Generic message header with size and type information. + */ + struct GNUNET_MessageHeader header; + + /** + * Flags for this message fragment. + * + * @see enum GNUNET_PSYC_MessageFlags + */ + uint32_t flags GNUNET_PACKED; + + /** + * Number of the message this message part belongs to. + * Monotonically increasing from 1. + */ + uint64_t message_id GNUNET_PACKED; + + /** + * Byte offset of this @e fragment of the @e message. + */ + uint64_t fragment_offset GNUNET_PACKED; + + /** + * Sending slave's public key. + * Not set if the message is from the master. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key; + + /* Followed by concatenated PSYC message parts: + * messages with GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_* types + */ +}; + + +/** + * The method of a message. + */ +struct GNUNET_PSYC_MessageMethod +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD + */ + struct GNUNET_MessageHeader header; + + /** + * OR'ed GNUNET_PSYC_MasterTransmitFlags + */ + uint32_t flags GNUNET_PACKED; + + /** + * Number of message IDs since the last message that contained state + * operations. @see enum GNUNET_PSYC_StateDeltaValues + */ + uint64_t state_delta GNUNET_PACKED; + + /* Followed by NUL-terminated method name. */ +}; + + +/** + * A modifier of a message. + */ +struct GNUNET_PSYC_MessageModifier +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER + */ + struct GNUNET_MessageHeader header; + + /** + * Size of value. + */ + uint32_t value_size GNUNET_PACKED; + + /** + * Size of name, including NUL terminator. + */ + uint16_t name_size GNUNET_PACKED; + + /** + * enum GNUNET_PSYC_Operator + */ + uint8_t oper; + + /* Followed by NUL-terminated name, then the value. */ +}; + + +struct GNUNET_PSYC_CountersResultMessage +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYC_RESULT_COUNTERS + */ + struct GNUNET_MessageHeader header; + + /** + * Status code for the operation. + */ + uint32_t result_code GNUNET_PACKED; + + /** + * Last message ID sent to the channel. + */ + uint64_t max_message_id GNUNET_PACKED; +}; + + +/** + * Join request sent to a PSYC master. + */ +struct GNUNET_PSYC_JoinRequestMessage +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYC_MASTER_JOIN_REQUEST + */ + struct GNUNET_MessageHeader header; + /** + * Public key of the joining slave. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key; + + /* Followed by struct GNUNET_MessageHeader join_request */ +}; + + +/** + * Join decision sent in reply to a join request. + */ +struct GNUNET_PSYC_JoinDecisionMessage +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYC_JOIN_DECISION + */ + struct GNUNET_MessageHeader header; + + /** + * #GNUNET_YES if the slave was admitted. + */ + int32_t is_admitted; + + /** + * Public key of the joining slave. + * Only set when the master is sending the decision, + * not set when a slave is receiving it. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key; + + /* Followed by struct GNUNET_MessageHeader join_response */ +}; + + +enum GNUNET_PSYC_HistoryReplayFlags +{ + /** + * Replay locally available messages. + */ + GNUNET_PSYC_HISTORY_REPLAY_LOCAL = 0, + + /** + * Replay messages from remote peers if not found locally. + */ + GNUNET_PSYC_HISTORY_REPLAY_REMOTE = 1, +}; + + +struct GNUNET_PSYC_HistoryRequestMessage +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_HISTORY_REPLAY + */ + struct GNUNET_MessageHeader header; + + /** + * @see enum GNUNET_PSYC_HistoryReplayFlags + */ + uint32_t flags GNUNET_PACKED; + + /** + * ID for this operation. + */ + uint64_t op_id GNUNET_PACKED; + + uint64_t start_message_id GNUNET_PACKED; + + uint64_t end_message_id GNUNET_PACKED; + + uint64_t message_limit GNUNET_PACKED; + + /* Followed by NUL-terminated method name prefix. */ +}; + + +struct GNUNET_PSYC_StateRequestMessage +{ + /** + * Types: + * - GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_STATE_GET + * - GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_STATE_GET_PREFIX + */ + struct GNUNET_MessageHeader header; + + uint32_t reserved GNUNET_PACKED; + + /** + * ID for this operation. + */ + uint64_t op_id GNUNET_PACKED; + + /* Followed by NUL-terminated name. */ +}; + + +/**** service -> library ****/ + + +/** + * Answer from service to client about last operation. + */ +struct GNUNET_PSYC_OperationResultMessage +{ + /** + * Types: + * - GNUNET_MESSAGE_TYPE_PSYC_RESULT_CODE + * - GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_STATE_RESULT + */ + struct GNUNET_MessageHeader header; + + uint32_t reserved GNUNET_PACKED; + + /** + * Operation ID. + */ + uint64_t op_id GNUNET_PACKED; + + /** + * Status code for the operation. + */ + uint64_t result_code GNUNET_PACKED; + + /* Followed by: + * - on error: NUL-terminated error message + * - on success: one of the following message types + * + * For a STATE_RESULT, one of: + * - GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER + * - GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT + * - GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END + */ +}; + +GNUNET_NETWORK_STRUCT_END + + +#define GNUNET_PSYC_MODIFIER_MAX_PAYLOAD \ + GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD \ + - sizeof (struct GNUNET_PSYC_MessageModifier) + +#define GNUNET_PSYC_MOD_CONT_MAX_PAYLOAD \ + GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD \ + - sizeof (struct GNUNET_MessageHeader) + +#define GNUNET_PSYC_DATA_MAX_PAYLOAD \ + GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD \ + - sizeof (struct GNUNET_MessageHeader) + + +/** + * PSYC message part processing states. + */ +enum GNUNET_PSYC_MessageState +{ + GNUNET_PSYC_MESSAGE_STATE_START = 0, + GNUNET_PSYC_MESSAGE_STATE_HEADER = 1, + GNUNET_PSYC_MESSAGE_STATE_METHOD = 2, + GNUNET_PSYC_MESSAGE_STATE_MODIFIER = 3, + GNUNET_PSYC_MESSAGE_STATE_MOD_CONT = 4, + GNUNET_PSYC_MESSAGE_STATE_DATA = 5, + GNUNET_PSYC_MESSAGE_STATE_END = 6, + GNUNET_PSYC_MESSAGE_STATE_CANCEL = 7, + GNUNET_PSYC_MESSAGE_STATE_ERROR = 8, +}; + + +/** + * Handle that identifies a join request. + * + * Used to match calls to #GNUNET_PSYC_JoinCallback to the + * corresponding calls to GNUNET_PSYC_join_decision(). + */ +struct GNUNET_PSYC_JoinHandle; + + +/** + * Method called from PSYC upon receiving a message. + * + * @param cls Closure. + * @param message_id Sequence number of the message. + * @param flags OR'ed GNUNET_PSYC_MessageFlags + * @param msg Message part, one of the following types: + */ +typedef void +(*GNUNET_PSYC_MessageCallback) (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg); + + +/** + * Method called from PSYC upon receiving part of a message. + * + * @param cls + * Closure. + * @param slave_pub_key + * Public key of the slave sending the message. + * Only set for channel master. + * @param message_id + * Sequence number of the message. + * @param flags + * OR'ed GNUNET_PSYC_MessageFlags + * @param fragment_offset + * Multicast message fragment offset. + * @param msg Message part, one of the following types: + * - #GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_HEADER + * - #GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD + * - #GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER + * - #GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT + * - #GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_DATA + * or NULL if an error occurred while receiving a message. + */ +typedef void +(*GNUNET_PSYC_MessagePartCallback) (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg); + + +/** + * Method called from PSYC upon receiving a join request. + * + * @param cls + * Closure. + * @param slave_pub_key + * Public key of the slave requesting join. + * @param join_msg + * Join message sent along with the request. + * @param jh + * Join handle to use with GNUNET_PSYC_join_decision() + */ +typedef void +(*GNUNET_PSYC_JoinRequestCallback) (void *cls, + const struct GNUNET_PSYC_JoinRequestMessage *req, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_pub_key, + const struct GNUNET_PSYC_Message *join_msg, + struct GNUNET_PSYC_JoinHandle *jh); + + +/** + * Function to call with the decision made for a join request. + * + * Must be called once and only once in response to an invocation of the + * #GNUNET_PSYC_JoinCallback. + * + * @param jh Join request handle. + * @param is_admitted + * #GNUNET_YES if the join is approved, + * #GNUNET_NO if it is disapproved, + * #GNUNET_SYSERR if we cannot answer the request. + * @param relay_count Number of relays given. + * @param relays Array of suggested peers that might be useful relays to use + * when joining the multicast group (essentially a list of peers that + * are already part of the multicast group and might thus be willing + * to help with routing). If empty, only this local peer (which must + * be the multicast origin) is a good candidate for building the + * multicast tree. Note that it is unnecessary to specify our own + * peer identity in this array. + * @param join_resp Application-dependent join response message to send along + * with the decision. + * + * @return #GNUNET_OK on success, + * #GNUNET_SYSERR if @a join_resp is too large. + */ +int +GNUNET_PSYC_join_decision (struct GNUNET_PSYC_JoinHandle *jh, + int is_admitted, + uint32_t relay_count, + const struct GNUNET_PeerIdentity *relays, + const struct GNUNET_PSYC_Message *join_resp); + + +/** + * Handle for the master of a PSYC channel. + */ +struct GNUNET_PSYC_Master; + + +/** + * Function called once we are connected to the PSYC service + * and the channel master is started. + * + * Also called when we reconnected to the service + * after the connection closed unexpectedly. + * + * @param cls + * Closure. + * @param result + * #GNUNET_YES if there were already messages sent to the channel, + * #GNUNET_NO if the message history is empty, + * #GNUNET_SYSERR on error. + * @param max_message_id + * Last message ID sent to the channel. + */ +typedef void +(*GNUNET_PSYC_MasterStartCallback) (void *cls, int result, + uint64_t max_message_id); + + +/** + * Start a PSYC master channel. + * + * Will start a multicast group identified by the given ECC key. Messages + * received from group members will be given to the respective handler methods. + * If a new member wants to join a group, the "join" method handler will be + * invoked; the join handler must then generate a "join" message to approve the + * joining of the new member. The channel can also change group membership + * without explicit requests. Note that PSYC doesn't itself "understand" join + * or part messages, the respective methods must call other PSYC functions to + * inform PSYC about the meaning of the respective events. + * + * @param cfg Configuration to use (to connect to PSYC service). + * @param channel_key ECC key that will be used to sign messages for this + * PSYC session. The public key is used to identify the PSYC channel. + * Note that end-users will usually not use the private key directly, but + * rather look it up in GNS for places managed by other users, or select + * a file with the private key(s) when setting up their own channels + * FIXME: we'll likely want to use NOT the p521 curve here, but a cheaper + * one in the future. + * @param policy Channel policy specifying join and history restrictions. + * Used to automate join decisions. + * @param master_start_cb Function to invoke after the channel master started. + * @param join_request_cb Function to invoke when a slave wants to join. + * @param message_cb Function to invoke on message parts sent to the channel + * and received from slaves + * @param cls Closure for @a method and @a join_cb. + * + * @return Handle for the channel master, NULL on error. + */ +struct GNUNET_PSYC_Master * +GNUNET_PSYC_master_start (const struct GNUNET_CONFIGURATION_Handle *cfg, + const struct GNUNET_CRYPTO_EddsaPrivateKey *channel_key, + enum GNUNET_PSYC_Policy policy, + GNUNET_PSYC_MasterStartCallback master_start_cb, + GNUNET_PSYC_JoinRequestCallback join_request_cb, + GNUNET_PSYC_MessageCallback message_cb, + GNUNET_PSYC_MessagePartCallback message_part_cb, + void *cls); + + +/** + * Function called to provide data for a transmission via PSYC. + * + * Note that returning #GNUNET_YES or #GNUNET_SYSERR (but not #GNUNET_NO) + * invalidates the respective transmission handle. + * + * @param cls Closure. + * @param[in,out] data_size Initially set to the number of bytes available in + * @a data, should be set to the number of bytes written to data. + * @param[out] data Where to write the body of the message to give to the + * method. The function must copy at most @a data_size bytes to @a data. + * @return #GNUNET_SYSERR on error (fatal, aborts transmission) + * #GNUNET_NO on success, if more data is to be transmitted later. + * Should be used if @a data_size was not big enough to take all the + * data. If 0 is returned in @a data_size the transmission is paused, + * and can be resumed with GNUNET_PSYC_master_transmit_resume(). + * #GNUNET_YES if this completes the transmission (all data supplied) + */ +typedef int +(*GNUNET_PSYC_TransmitNotifyData) (void *cls, + uint16_t *data_size, + void *data); + +/** + * Function called to provide a modifier for a transmission via PSYC. + * + * Note that returning #GNUNET_YES or #GNUNET_SYSERR (but not #GNUNET_NO) + * invalidates the respective transmission handle. + * + * @param cls Closure. + * @param[in,out] data_size Initially set to the number of bytes available in + * @a data, should be set to the number of bytes written to data. + * @param[out] data Where to write the modifier's name and value. + * The function must copy at most @a data_size bytes to @a data. + * When this callback is first called for a modifier, @a data should + * contain: "name\0value". If the whole value does not fit, subsequent + * calls to this function should write continuations of the value to + * @a data. + * @param[out] oper Where to write the operator of the modifier. + * Only needed during the first call to this callback at the beginning + * of the modifier. In case of subsequent calls asking for value + * continuations @a oper is set to #NULL. + * @param[out] full_value_size Where to write the full size of the value. + * Only needed during the first call to this callback at the beginning + * of the modifier. In case of subsequent calls asking for value + * continuations @a value_size is set to #NULL. + * @return #GNUNET_SYSERR on error (fatal, aborts transmission) + * #GNUNET_NO on success, if more data is to be transmitted later. + * Should be used if @a data_size was not big enough to take all the + * data for the modifier's value (the name must be always returned + * during the first call to this callback). + * If 0 is returned in @a data_size the transmission is paused, + * and can be resumed with GNUNET_PSYC_master_transmit_resume(). + * #GNUNET_YES if this completes the modifier (the whole value is supplied). + */ +typedef int +(*GNUNET_PSYC_TransmitNotifyModifier) (void *cls, + uint16_t *data_size, + void *data, + uint8_t *oper, + uint32_t *full_value_size); + +/** + * Flags for transmitting messages to a channel by the master. + */ +enum GNUNET_PSYC_MasterTransmitFlags +{ + GNUNET_PSYC_MASTER_TRANSMIT_NONE = 0, + + /** + * Whether this message should reset the channel state, + * i.e. remove all previously stored state variables. + */ + + GNUNET_PSYC_MASTER_TRANSMIT_STATE_RESET = 1 << 0, + + /** + * Whether this message contains any state modifiers. + */ + GNUNET_PSYC_MASTER_TRANSMIT_STATE_MODIFY = 1 << 1, + + /** + * Add PSYC header variable with the hash of the current channel state. + */ + GNUNET_PSYC_MASTER_TRANSMIT_STATE_HASH = 1 << 2, + + /** + * Whether we need to increment the group generation counter after + * transmitting this message. + */ + GNUNET_PSYC_MASTER_TRANSMIT_INC_GROUP_GEN = 1 << 3 +}; + + +/** + * Handle for a pending PSYC transmission operation. + */ +struct GNUNET_PSYC_MasterTransmitHandle; + + +/** + * Send a message to call a method to all members in the PSYC channel. + * + * @param master Handle to the PSYC channel. + * @param method_name Which method should be invoked. + * @param notify_mod Function to call to obtain modifiers. + * @param notify_data Function to call to obtain fragments of the data. + * @param notify_cls Closure for @a notify_mod and @a notify_data. + * @param flags Flags for the message being transmitted. + * @return Transmission handle, NULL on error (i.e. more than one request queued). + */ +struct GNUNET_PSYC_MasterTransmitHandle * +GNUNET_PSYC_master_transmit (struct GNUNET_PSYC_Master *master, + const char *method_name, + GNUNET_PSYC_TransmitNotifyModifier notify_mod, + GNUNET_PSYC_TransmitNotifyData notify_data, + void *notify_cls, + enum GNUNET_PSYC_MasterTransmitFlags flags); + + +/** + * Resume transmission to the channel. + * + * @param th Handle of the request that is being resumed. + */ +void +GNUNET_PSYC_master_transmit_resume (struct GNUNET_PSYC_MasterTransmitHandle *th); + + +/** + * Abort transmission request to channel. + * + * @param th Handle of the request that is being aborted. + */ +void +GNUNET_PSYC_master_transmit_cancel (struct GNUNET_PSYC_MasterTransmitHandle *th); + + +/** + * Relay a message + * + * @param master Handle to the PSYC channel. + * @param method_name Which method should be invoked. + * @param notify_mod Function to call to obtain modifiers. + * @param notify_data Function to call to obtain fragments of the data. + * @param notify_cls Closure for @a notify_mod and @a notify_data. + * @param flags Flags for the message being transmitted. + * @return Transmission handle, NULL on error (i.e. more than one request queued). + */ +struct GNUNET_PSYC_MasterTransmitHandle * +GNUNET_PSYC_master_relay (struct GNUNET_PSYC_Master *master, + uint64_t message_id); + + +/** + * Stop a PSYC master channel. + * + * @param master + * PSYC channel master to stop. + * @param keep_active + * Keep place active after last application disconnected. + * @param stop_cb + * Function called after the master stopped + * and disconnected from the psyc service. + * @param stop_cls + * Closure for @a part_cb. + */ +void +GNUNET_PSYC_master_stop (struct GNUNET_PSYC_Master *master, + int keep_active, + GNUNET_ContinuationCallback stop_cb, + void *stop_cls); + + +/** + * Handle for a PSYC channel slave. + */ +struct GNUNET_PSYC_Slave; + + +/** + * Function called after the slave connected to the PSYC service. + * + * Also called when reconnected to the service + * after the connection closed unexpectedly. + * + * @param cls + * Closure. + * @param result + * #GNUNET_YES if there were already messages sent to the channel, + * #GNUNET_NO if the message history is empty, + * #GNUNET_SYSERR on error. + * @param max_message_id + * Last message ID sent to the channel. + */ +typedef void +(*GNUNET_PSYC_SlaveConnectCallback) (void *cls, int result, + uint64_t max_message_id); + + +/** + * Method called to inform about the decision in response to a join request. + * + * If @a is_admitted is not #GNUNET_YES, then sending messages to the channel is + * not possible, but earlier history can be still queried. + * + * @param cls Closure. + * @param is_admitted #GNUNET_YES or #GNUNET_NO or #GNUNET_SYSERR + * @param join_msg Application-dependent join message from the origin. + */ +typedef void +(*GNUNET_PSYC_JoinDecisionCallback) (void *cls, + const struct GNUNET_PSYC_JoinDecisionMessage *dcsn, + int is_admitted, + const struct GNUNET_PSYC_Message *join_msg); + +/** + * Flags for GNUNET_PSYC_slave_join() + */ +enum GNUNET_PSYC_SlaveJoinFlags +{ + GNUNET_PSYC_SLAVE_JOIN_NONE = 0, + + /** + * Local join for history access, no network connection is established. + */ + GNUNET_PSYC_SLAVE_JOIN_LOCAL = 1, +}; + + +/** + * Join a PSYC channel. + * + * The entity joining is always the local peer. The user must immediately use + * the GNUNET_PSYC_slave_transmit() functions to transmit a @e join_msg to the + * channel; if the join request succeeds, the channel state (and @e recent + * method calls) will be replayed to the joining member. There is no explicit + * notification on failure (as the channel may simply take days to approve, + * and disapproval is simply being ignored). + * + * @param cfg + * Configuration to use. + * @param channel_pub_key + * ECC public key that identifies the channel we wish to join. + * @param slave_pub_key + * ECC private-public key pair that identifies the slave, and + * used by multicast to sign the join request and subsequent unicast + * requests sent to the master. + * @param flags + * Join flags. + * @param origin + * Peer identity of the origin. + * @param relay_count + * Number of peers in the @a relays array. + * @param relays + * Peer identities of members of the multicast group, which serve + * as relays and used to join the group at. + * @param message_cb + * Function to invoke on message fragments received from the channel. + * @param message_part_cb + * Function to invoke on message parts received from the channel. + * @param slave_connect_cb + * Function invoked once we have connected to the PSYC service. + * @param join_decision_cb + * Function invoked once we have received a join decision. + * @param cls + * Closure for @a message_cb and @a slave_joined_cb. + * @param join_msg + * Join message. + * + * @return Handle for the slave, NULL on error. + */ +struct GNUNET_PSYC_Slave * +GNUNET_PSYC_slave_join (const struct GNUNET_CONFIGURATION_Handle *cfg, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_pub_key, + const struct GNUNET_CRYPTO_EcdsaPrivateKey *slave_pub_key, + enum GNUNET_PSYC_SlaveJoinFlags flags, + const struct GNUNET_PeerIdentity *origin, + uint32_t relay_count, + const struct GNUNET_PeerIdentity *relays, + GNUNET_PSYC_MessageCallback message_cb, + GNUNET_PSYC_MessagePartCallback message_part_cb, + GNUNET_PSYC_SlaveConnectCallback slave_connect_cb, + GNUNET_PSYC_JoinDecisionCallback join_decision_cb, + void *cls, + const struct GNUNET_PSYC_Message *join_msg); + + +/** + * Part a PSYC channel. + * + * Will terminate the connection to the PSYC service. Polite clients should + * first explicitly send a part request (via GNUNET_PSYC_slave_transmit()). + * + * @param slave + * Slave handle. + * @param keep_active + * Keep place active after last application disconnected. + * @param part_cb + * Function called after the slave parted the channel + * and disconnected from the psyc service. + * @param part_cls + * Closure for @a part_cb. + */ +void +GNUNET_PSYC_slave_part (struct GNUNET_PSYC_Slave *slave, + int keep_active, + GNUNET_ContinuationCallback part_cb, + void *part_cls); + + +/** + * Flags for transmitting messages to the channel master by a slave. + */ +enum GNUNET_PSYC_SlaveTransmitFlags +{ + GNUNET_PSYC_SLAVE_TRANSMIT_NONE = 0 +}; + + +/** + * Handle for a pending PSYC transmission operation. + */ +struct GNUNET_PSYC_SlaveTransmitHandle; + + +/** + * Request a message to be sent to the channel master. + * + * @param slave Slave handle. + * @param method_name Which (PSYC) method should be invoked (on host). + * @param notify_mod Function to call to obtain modifiers. + * @param notify_data Function to call to obtain fragments of the data. + * @param notify_cls Closure for @a notify. + * @param flags Flags for the message being transmitted. + * @return Transmission handle, NULL on error (i.e. more than one request queued). + */ +struct GNUNET_PSYC_SlaveTransmitHandle * +GNUNET_PSYC_slave_transmit (struct GNUNET_PSYC_Slave *slave, + const char *method_name, + GNUNET_PSYC_TransmitNotifyModifier notify_mod, + GNUNET_PSYC_TransmitNotifyData notify_data, + void *notify_cls, + enum GNUNET_PSYC_SlaveTransmitFlags flags); + + +/** + * Resume transmission to the master. + * + * @param th Handle of the request that is being resumed. + */ +void +GNUNET_PSYC_slave_transmit_resume (struct GNUNET_PSYC_SlaveTransmitHandle *th); + + +/** + * Abort transmission request to master. + * + * @param th Handle of the request that is being aborted. + */ +void +GNUNET_PSYC_slave_transmit_cancel (struct GNUNET_PSYC_SlaveTransmitHandle *th); + + +/** + * Handle to access PSYC channel operations for both the master and slaves. + */ +struct GNUNET_PSYC_Channel; + + +/** + * Convert a channel @a master to a @e channel handle to access the @e channel + * APIs. + * + * @param master Channel master handle. + * @return Channel handle, valid for as long as @a master is valid. + */ +struct GNUNET_PSYC_Channel * +GNUNET_PSYC_master_get_channel (struct GNUNET_PSYC_Master *master); + + +/** + * Convert @a slave to a @e channel handle to access the @e channel APIs. + * + * @param slave Slave handle. + * @return Channel handle, valid for as long as @a slave is valid. + */ +struct GNUNET_PSYC_Channel * +GNUNET_PSYC_slave_get_channel (struct GNUNET_PSYC_Slave *slave); + + +/** + * Add a slave to the channel's membership list. + * + * Note that this will NOT generate any PSYC traffic, it will merely update the + * local database to modify how we react to membership test queries. + * The channel master still needs to explicitly transmit a @e join message to + * notify other channel members and they then also must still call this function + * in their respective methods handling the @e join message. This way, how @e + * join and @e part operations are exactly implemented is still up to the + * application; for example, there might be a @e part_all method to kick out + * everyone. + * + * Note that channel slaves are explicitly trusted to execute such methods + * correctly; not doing so correctly will result in either denying other slaves + * access or offering access to channel data to non-members. + * + * @param channel + * Channel handle. + * @param slave_pub_key + * Identity of channel slave to add. + * @param announced_at + * ID of the message that announced the membership change. + * @param effective_since + * Addition of slave is in effect since this message ID. + * @param result_cb + * Function to call with the result of the operation. + * The @e result_code argument is #GNUNET_OK on success, or + * #GNUNET_SYSERR on error. In case of an error, the @e data argument + * can contain an optional error message. + * @param cls + * Closure for @a result_cb. + */ +void +GNUNET_PSYC_channel_slave_add (struct GNUNET_PSYC_Channel *channel, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_pub_key, + uint64_t announced_at, + uint64_t effective_since, + GNUNET_ResultCallback result_cb, + void *cls); + + +/** + * Remove a slave from the channel's membership list. + * + * Note that this will NOT generate any PSYC traffic, it will merely update the + * local database to modify how we react to membership test queries. + * The channel master still needs to explicitly transmit a @e part message to + * notify other channel members and they then also must still call this function + * in their respective methods handling the @e part message. This way, how + * @e join and @e part operations are exactly implemented is still up to the + * application; for example, there might be a @e part_all message to kick out + * everyone. + * + * Note that channel members are explicitly trusted to perform these + * operations correctly; not doing so correctly will result in either + * denying members access or offering access to channel data to + * non-members. + * + * @param channel + * Channel handle. + * @param slave_pub_key + * Identity of channel slave to remove. + * @param announced_at + * ID of the message that announced the membership change. + * @param result_cb + * Function to call with the result of the operation. + * The @e result_code argument is #GNUNET_OK on success, or + * #GNUNET_SYSERR on error. In case of an error, the @e data argument + * can contain an optional error message. + * @param cls + * Closure for @a result_cb. + */ +void +GNUNET_PSYC_channel_slave_remove (struct GNUNET_PSYC_Channel *channel, + const struct GNUNET_CRYPTO_EcdsaPublicKey + *slave_pub_key, + uint64_t announced_at, + GNUNET_ResultCallback result_cb, + void *cls); + + +/** + * History request handle. + */ +struct GNUNET_PSYC_HistoryRequest; + + +/** + * Request to replay a part of the message history of the channel. + * + * Historic messages (but NOT the state at the time) will be replayed (given to + * the normal method handlers) if available and if access is permitted. + * + * @param channel + * Which channel should be replayed? + * @param start_message_id + * Earliest interesting point in history. + * @param end_message_id + * Last (inclusive) interesting point in history. + * @param method_prefix + * Retrieve only messages with a matching method prefix. + * @param flags + * OR'ed enum GNUNET_PSYC_HistoryReplayFlags + * @param result_cb + * Function to call when the requested history has been fully replayed. + * Once this function has been called, the client must not call + * GNUNET_PSYC_channel_history_replay_cancel() anymore. + * @param cls + * Closure for the callbacks. + * + * @return Handle to cancel history replay operation. + */ +struct GNUNET_PSYC_HistoryRequest * +GNUNET_PSYC_channel_history_replay (struct GNUNET_PSYC_Channel *channel, + uint64_t start_message_id, + uint64_t end_message_id, + const char *method_prefix, + uint32_t flags, + GNUNET_PSYC_MessageCallback message_cb, + GNUNET_PSYC_MessagePartCallback message_part_cb, + GNUNET_ResultCallback result_cb, + void *cls); + + +/** + * Request to replay the latest messages from the message history of the channel. + * + * Historic messages (but NOT the state at the time) will be replayed (given to + * the normal method handlers) if available and if access is permitted. + * + * @param channel + * Which channel should be replayed? + * @param message_limit + * Maximum number of messages to replay. + * @param flags + * OR'ed enum GNUNET_PSYC_HistoryReplayFlags + * @param finish_cb + * Function to call when the requested history has been fully replayed + * (counting message IDs might not suffice, as some messages might be + * secret and thus the listener would not know the story is finished + * without being told explicitly)o once this function has been called, the + * client must not call GNUNET_PSYC_channel_history_replay_cancel() anymore. + * @param cls + * Closure for the callbacks. + * + * @return Handle to cancel history replay operation. + */ +struct GNUNET_PSYC_HistoryRequest * +GNUNET_PSYC_channel_history_replay_latest (struct GNUNET_PSYC_Channel *channel, + uint64_t message_limit, + const char *method_prefix, + uint32_t flags, + GNUNET_PSYC_MessageCallback message_cb, + GNUNET_PSYC_MessagePartCallback message_part_cb, + GNUNET_ResultCallback result_cb, + void *cls); + + +void +GNUNET_PSYC_channel_history_replay_cancel (struct GNUNET_PSYC_Channel *channel, + struct GNUNET_PSYC_HistoryRequest *hr); + + +/** + * Function called to inform a member about stored state values for a channel. + * + * If @a full_value_size > value_size then this function is called multiple + * times until the whole value arrived. + * + * @param cls + * Closure. + * @param name + * Name of the state variable. + * NULL if there are no more state variables to be returned. + * @param value + * Value of the state variable. + * @param value_size + * Number of bytes in @a value. + * @param full_value_size + * Number of bytes in the full value, including continuations. + * Only set for the first part of a variable, + * in case of a continuation it is 0. + */ +typedef void +(*GNUNET_PSYC_StateVarCallback) (void *cls, + const struct GNUNET_MessageHeader *mod, + const char *name, + const void *value, + uint32_t value_size, + uint32_t full_value_size); + + +/** + * State request handle. + */ +struct GNUNET_PSYC_StateRequest; + + +/** + * Retrieve the best matching channel state variable. + * + * If the requested variable name is not present in the state, the nearest + * less-specific name is matched; for example, requesting "_a_b" will match "_a" + * if "_a_b" does not exist. + * + * @param channel + * Channel handle. + * @param full_name + * Full name of the requested variable. + * The actual variable returned might have a shorter name. + * @param var_cb + * Function called once when a matching state variable is found. + * Not called if there's no matching state variable. + * @param result_cb + * Function called after the operation finished. + * (i.e. all state variables have been returned via @a state_cb) + * @param cls + * Closure for the callbacks. + */ +struct GNUNET_PSYC_StateRequest * +GNUNET_PSYC_channel_state_get (struct GNUNET_PSYC_Channel *channel, + const char *full_name, + GNUNET_PSYC_StateVarCallback var_cb, + GNUNET_ResultCallback result_cb, + void *cls); + + +/** + * Return all channel state variables whose name matches a given prefix. + * + * A name matches if it starts with the given @a name_prefix, thus requesting + * the empty prefix ("") will match all values; requesting "_a_b" will also + * return values stored under "_a_b_c". + * + * The @a state_cb is invoked on all matching state variables asynchronously, as + * the state is stored in and retrieved from the PSYCstore, + * + * @param channel + * Channel handle. + * @param name_prefix + * Prefix of the state variable name to match. + * @param var_cb + * Function called once when a matching state variable is found. + * Not called if there's no matching state variable. + * @param result_cb + * Function called after the operation finished. + * (i.e. all state variables have been returned via @a state_cb) + * @param cls + * Closure for the callbacks. + */ +struct GNUNET_PSYC_StateRequest * +GNUNET_PSYC_channel_state_get_prefix (struct GNUNET_PSYC_Channel *channel, + const char *name_prefix, + GNUNET_PSYC_StateVarCallback var_cb, + GNUNET_ResultCallback result_cb, + void *cls); + +/** + * Cancel a state request operation. + * + * @param sr + * Handle for the operation to cancel. + */ +void +GNUNET_PSYC_channel_state_get_cancel (struct GNUNET_PSYC_StateRequest *sr); + + + +#if 0 /* keep Emacsens' auto-indent happy */ +{ +#endif +#ifdef __cplusplus +} +#endif + +/* ifndef GNUNET_PSYC_SERVICE_H */ +#endif + +/** @} */ /* end of group */ diff --git a/src/include/gnunet_psyc_slicer.h b/src/include/gnunet_psyc_slicer.h new file mode 100644 index 0000000..87f66d7 --- /dev/null +++ b/src/include/gnunet_psyc_slicer.h @@ -0,0 +1,378 @@ +/* + This file is part of GNUnet. + Copyright (C) 2013 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later +*/ + +/** + * @author Gabor X Toth + * @author Christian Grothoff + * + * @file + * PSYC Slicer library + * + * @defgroup psyc-util-slicer PSYC Utilities library: Slicer + * Try-and-slice processing of PSYC method names and environment. + * @{ + */ + +#ifndef GNUNET_PSYC_SLICER_H +#define GNUNET_PSYC_SLICER_H + + +#ifdef __cplusplus +extern "C" +{ +#if 0 /* keep Emacsens' auto-indent happy */ +} +#endif +#endif + +#include "gnunet_util_lib.h" + + +/** + * Handle to an implementation of try-and-slice. + */ +struct GNUNET_PSYC_Slicer; + + +/** + * Function called upon receiving a message indicating a call to a @e method. + * + * This function is called one or more times for each message until all data + * fragments arrive from the network. + * + * @param cls + * Closure. + * @param msg + * Message part, as it arrived from the network. + * @param message_id + * Message counter, monotonically increasing from 1. + * @param flags + * OR'ed GNUNET_PSYC_MessageFlags + * @param fragment_offset + * Multicast message fragment offset. + * @param tmit_flags + * OR'ed GNUNET_PSYC_MasterTransmitFlags + * @param nym + * The sender of the message. + * Can be NULL if the message is not connected to a pseudonym. + * @param method_name + * Original method name from PSYC. + * May be more specific than the registered method name due to + * try-and-slice matching. + */ +typedef void +(*GNUNET_PSYC_MethodCallback) (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_PSYC_MessageMethod *meth, + uint64_t message_id, + const char *method_name); + + +/** + * Function called upon receiving a modifier of a message. + * + * @param cls + * Closure. + * @param message_id + * Message ID this data fragment belongs to. + * @param flags + * OR'ed GNUNET_PSYC_MessageFlags + * @param fragment_offset + * Multicast message fragment offset. + * @param msg + * Message part, as it arrived from the network. + * @param oper + * Operation to perform. + * 0 in case of a modifier continuation. + * @param name + * Name of the modifier. + * NULL in case of a modifier continuation. + * @param value + * Value of the modifier. + * @param value_size + * Size of @value. + */ +typedef void +(*GNUNET_PSYC_ModifierCallback) (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + enum GNUNET_PSYC_Operator oper, + const char *name, + const void *value, + uint16_t value_size, + uint16_t full_value_size); + + +/** + * Function called upon receiving a data fragment of a message. + * + * @param cls + * Closure. + * @param msg + * Message part, as it arrived from the network. + * @param message_id + * Message ID this data fragment belongs to. + * @param flags + * OR'ed GNUNET_PSYC_MessageFlags + * @param fragment_offset + * Multicast message fragment offset. + * @param data + * Data stream given to the method. + * @param data_size + * Number of bytes in @a data. + * @param end + * End of message? + * #GNUNET_NO if there are further fragments, + * #GNUNET_YES if this is the last fragment, + * #GNUNET_SYSERR indicates the message was cancelled by the sender. + */ +typedef void +(*GNUNET_PSYC_DataCallback) (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + const void *data, + uint16_t data_size); + + +/** + * End of message. + * + * @param cls + * Closure. + * @param msg + * Message part, as it arrived from the network. + * @param message_id + * Message ID this data fragment belongs to. + * @param flags + * OR'ed GNUNET_PSYC_MessageFlags + * @param fragment_offset + * Multicast message fragment offset. + * @param cancelled + * #GNUNET_YES if the message was cancelled, + * #GNUNET_NO if the message is complete. + */ +typedef void +(*GNUNET_PSYC_EndOfMessageCallback) (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + uint8_t is_cancelled); + + +/** + * Create a try-and-slice instance. + * + * A slicer processes incoming messages and notifies callbacks about matching + * methods or modifiers encountered. + * + * @return A new try-and-slice construct. + */ +struct GNUNET_PSYC_Slicer * +GNUNET_PSYC_slicer_create (void); + + +/** + * Add a method to the try-and-slice instance. + * + * The callbacks are called for messages with a matching @a method_name prefix. + * + * @param slicer + * The try-and-slice instance to extend. + * @param method_name + * Name of the given method, use empty string to match all. + * @param method_cb + * Method handler invoked upon a matching message. + * @param modifier_cb + * Modifier handler, invoked after @a method_cb + * for each modifier in the message. + * @param data_cb + * Data handler, invoked after @a modifier_cb for each data fragment. + * @param eom_cb + * Invoked upon reaching the end of a matching message. + * @param cls + * Closure for the callbacks. + */ +void +GNUNET_PSYC_slicer_method_add (struct GNUNET_PSYC_Slicer *slicer, + const char *method_name, + GNUNET_PSYC_MessageCallback msg_cb, + GNUNET_PSYC_MethodCallback method_cb, + GNUNET_PSYC_ModifierCallback modifier_cb, + GNUNET_PSYC_DataCallback data_cb, + GNUNET_PSYC_EndOfMessageCallback eom_cb, + void *cls); + +/** + * Remove a registered method from the try-and-slice instance. + * + * Removes one matching handler registered with the given + * @a method_name and callbacks. + * + * @param slicer + * The try-and-slice instance. + * @param method_name + * Name of the method to remove. + * @param method_cb + * Only remove matching method handler, or NULL. + * @param modifier_cb + * Only remove matching modifier handler, or NULL. + * @param data_cb + * Only remove matching data handler, or NULL. + * @param eom_cb + * Only remove matching End of Message handler, or NULL. + * + * @return #GNUNET_OK if a method handler was removed, + * #GNUNET_NO if no handler matched the given method name and callbacks. + */ +int +GNUNET_PSYC_slicer_method_remove (struct GNUNET_PSYC_Slicer *slicer, + const char *method_name, + GNUNET_PSYC_MessageCallback msg_cb, + GNUNET_PSYC_MethodCallback method_cb, + GNUNET_PSYC_ModifierCallback modifier_cb, + GNUNET_PSYC_DataCallback data_cb, + GNUNET_PSYC_EndOfMessageCallback eom_cb); + + +/** + * Watch a place for changed objects. + * + * @param slicer + * The try-and-slice instance. + * @param object_filter + * Object prefix to match. + * @param modifier_cb + * Function to call when encountering a state modifier. + * @param cls + * Closure for callback. + */ +void +GNUNET_PSYC_slicer_modifier_add (struct GNUNET_PSYC_Slicer *slicer, + const char *object_filter, + GNUNET_PSYC_ModifierCallback modifier_cb, + void *cls); + + +/** + * Remove a registered modifier from the try-and-slice instance. + * + * Removes one matching handler registered with the given + * @a object_filter and callback. + * + * @param slicer + * The try-and-slice instance. + * @param object_filter + * Object prefix to match. + * @param modifier_cb + * Function to call when encountering a state modifier changes. + */ +int +GNUNET_PSYC_slicer_modifier_remove (struct GNUNET_PSYC_Slicer *slicer, + const char *object_filter, + GNUNET_PSYC_ModifierCallback modifier_cb); + + +/** + * Process an incoming message and call matching handlers. + * + * @param slicer + * The slicer to use. + * @param msg + * The message as it arrived from the network. + */ +void +GNUNET_PSYC_slicer_message (struct GNUNET_PSYC_Slicer *slicer, + const struct GNUNET_PSYC_MessageHeader *msg); + + +/** + * Process an incoming message part and call matching handlers. + * + * @param slicer + * The slicer to use. + * @param message_id + * ID of the message. + * @param flags + * Flags for the message. + * @see enum GNUNET_PSYC_MessageFlags + * @param fragment offset + * Fragment offset of the message. + * @param msg + * The message part as it arrived from the network. + */ +void +GNUNET_PSYC_slicer_message_part (struct GNUNET_PSYC_Slicer *slicer, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg); + + +/** + * Remove all registered method handlers. + * + * @param slicer + * Slicer to clear. + */ +void +GNUNET_PSYC_slicer_method_clear (struct GNUNET_PSYC_Slicer *slicer); + + +/** + * Remove all registered modifier handlers. + * + * @param slicer + * Slicer to clear. + */ +void +GNUNET_PSYC_slicer_modifier_clear (struct GNUNET_PSYC_Slicer *slicer); + + +/** + * Remove all registered method & modifier handlers. + * + * @param slicer + * Slicer to clear. + */ +void +GNUNET_PSYC_slicer_clear (struct GNUNET_PSYC_Slicer *slicer); + + +/** + * Destroy a given try-and-slice instance. + * + * @param slicer + * Slicer to destroy + */ +void +GNUNET_PSYC_slicer_destroy (struct GNUNET_PSYC_Slicer *slicer); + + +#if 0 /* keep Emacsens' auto-indent happy */ +{ +#endif +#ifdef __cplusplus +} +#endif + +/* ifndef GNUNET_PSYC_SLICER_H */ +#endif + +/** @} */ /* end of group */ diff --git a/src/include/gnunet_psyc_util_lib.h b/src/include/gnunet_psyc_util_lib.h new file mode 100644 index 0000000..57eec65 --- /dev/null +++ b/src/include/gnunet_psyc_util_lib.h @@ -0,0 +1,53 @@ +/* + This file is part of GNUnet. + Copyright (C) 2012, 2013 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later +*/ + +/** + * @author Gabor X Toth + * + * @file + * PSYC utilities: messages, environment, slicer + */ + +#ifndef GNUNET_PSYC_UTIL_LIB_H +#define GNUNET_PSYC_UTIL_LIB_H + +#ifdef __cplusplus +extern "C" +{ +#if 0 /* keep Emacsens' auto-indent happy */ +} +#endif +#endif + + +#include "gnunet_psyc_env.h" +#include "gnunet_psyc_message.h" +#include "gnunet_psyc_slicer.h" + + +#if 0 /* keep Emacsens' auto-indent happy */ +{ +#endif +#ifdef __cplusplus +} +#endif + +/* ifndef GNUNET_PSYC_UTIL_LIB_H */ +#endif diff --git a/src/include/gnunet_psycstore_plugin.h b/src/include/gnunet_psycstore_plugin.h new file mode 100644 index 0000000..fac549f --- /dev/null +++ b/src/include/gnunet_psycstore_plugin.h @@ -0,0 +1,383 @@ +/* + This file is part of GNUnet + Copyright (C) 2013 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later +*/ + +/** + * @author Gabor X Toth + * + * @file + * Plugin API for the PSYCstore database backend + * + * @defgroup psycstore-plugin PSYC Store plugin API + * Plugin API for the PSYC Store database backend + * @{ + */ +#ifndef GNUNET_PSYCSTORE_PLUGIN_H +#define GNUNET_PSYCSTORE_PLUGIN_H + +#include "gnunet_util_lib.h" +#include "gnunet_psycstore_service.h" + +#ifdef __cplusplus +extern "C" +{ +#if 0 /* keep Emacsens' auto-indent happy */ +} +#endif +#endif + + +/** + * Struct returned by the initialization function of the plugin. + */ +struct GNUNET_PSYCSTORE_PluginFunctions +{ + + /** + * Closure to pass to all plugin functions. + */ + void *cls; + + /** + * Store join/leave events for a PSYC channel in order to be able to answer + * membership test queries later. + * + * @see GNUNET_PSYCSTORE_membership_store() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*membership_store) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + int did_join, + uint64_t announced_at, + uint64_t effective_since, + uint64_t group_generation); + + /** + * Test if a member was admitted to the channel at the given message ID. + * + * @see GNUNET_PSYCSTORE_membership_test() + * + * @return #GNUNET_YES if the member was admitted, #GNUNET_NO if not, + * #GNUNET_SYSERR if there was en error. + */ + int + (*membership_test) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + uint64_t message_id); + + /** + * Store a message fragment sent to a channel. + * + * @see GNUNET_PSYCSTORE_fragment_store() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*fragment_store) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_MULTICAST_MessageHeader *message, + uint32_t psycstore_flags); + + /** + * Set additional flags for a given message. + * + * They are OR'd with any existing flags set. + * + * @param cls Closure. + * @param channel_key Public key of the channel. + * @param message_id ID of the message. + * @param psycstore_flags OR'd GNUNET_PSYCSTORE_MessageFlags. + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*message_add_flags) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id, + uint32_t psycstore_flags); + + /** + * Retrieve a message fragment range by fragment ID. + * + * @see GNUNET_PSYCSTORE_fragment_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*fragment_get) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t first_fragment_id, + uint64_t last_fragment_id, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls); + + /** + * Retrieve latest message fragments. + * + * @see GNUNET_PSYCSTORE_fragment_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*fragment_get_latest) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t fragment_limit, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls); + + /** + * Retrieve all fragments of a message ID range. + * + * @see GNUNET_PSYCSTORE_message_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*message_get) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t first_fragment_id, + uint64_t last_fragment_id, + uint64_t fragment_limit, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls); + + /** + * Retrieve all fragments of the latest messages. + * + * @see GNUNET_PSYCSTORE_message_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*message_get_latest) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t fragment_limit, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls); + + /** + * Retrieve a fragment of message specified by its message ID and fragment + * offset. + * + * @see GNUNET_PSYCSTORE_message_get_fragment() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*message_get_fragment) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id, + uint64_t fragment_offset, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls); + + /** + * Retrieve the max. values of message counters for a channel. + * + * @see GNUNET_PSYCSTORE_counters_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*counters_message_get) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t *max_fragment_id, + uint64_t *max_message_id, + uint64_t *max_group_generation); + + /** + * Retrieve the max. values of state counters for a channel. + * + * @see GNUNET_PSYCSTORE_counters_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*counters_state_get) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t *max_state_message_id); + + + /** + * Begin modifying current state. + * + * @see GNUNET_PSYCSTORE_state_modify() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*state_modify_begin) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id, uint64_t state_delta); + + /** + * Set the current value of a state variable. + * + * The state modification process is started with state_modify_begin(), + * which is followed by one or more calls to this function, + * and finished with state_modify_end(). + * + * @see GNUNET_PSYCSTORE_state_modify() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*state_modify_op) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + enum GNUNET_PSYC_Operator op, + const char *name, const void *value, size_t value_size); + + + /** + * End modifying current state. + * + * @see GNUNET_PSYCSTORE_state_modify() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*state_modify_end) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id); + + + /** + * Begin synchronizing state. + * + * @see GNUNET_PSYCSTORE_state_sync() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*state_sync_begin) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key); + + /** + * Assign value of a state variable while synchronizing state. + * + * The state synchronization process is started with state_sync_begin(), + * which is followed by one or more calls to this function, + * and finished using state_sync_end(). + * + * @see GNUNET_PSYCSTORE_state_sync() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*state_sync_assign) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, const void *value, size_t value_size); + + + /** + * End synchronizing state. + * + * @see GNUNET_PSYCSTORE_state_sync() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*state_sync_end) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t max_state_message_id, + uint64_t state_hash_message_id); + + + /** + * Reset the state of a channel. + * + * Delete all state variables stored for the given channel. + * + * @see GNUNET_PSYCSTORE_state_reset() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*state_reset) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key); + + /** + * Update signed state values from the current ones. + * + * Sets value_signed = value_current for each variable for the given channel. + */ + int + (*state_update_signed) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key); + + + /** + * Retrieve a state variable by name (exact match). + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*state_get) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, + GNUNET_PSYCSTORE_StateCallback cb, + void *cb_cls); + + /** + * Retrieve all state variables for a channel with the given prefix. + * + * @see GNUNET_PSYCSTORE_state_get_prefix() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*state_get_prefix) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, + GNUNET_PSYCSTORE_StateCallback cb, + void *cb_cls); + + + /** + * Retrieve all signed state variables for a channel. + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ + int + (*state_get_signed) (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + GNUNET_PSYCSTORE_StateCallback cb, + void *cb_cls); + +}; + + +#if 0 /* keep Emacsens' auto-indent happy */ +{ +#endif +#ifdef __cplusplus +} +#endif + +#endif + +/** @} */ /* end of group */ diff --git a/src/include/gnunet_psycstore_service.h b/src/include/gnunet_psycstore_service.h new file mode 100644 index 0000000..92516f4 --- /dev/null +++ b/src/include/gnunet_psycstore_service.h @@ -0,0 +1,701 @@ +/* + This file is part of GNUnet. + Copyright (C) 2013 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later +*/ + +/** + * @author Gabor X Toth + * @author Christian Grothoff + * + * @file + * PSYCstore service; implements persistent storage for the PSYC service + * + * @defgroup psycstore PSYC Store service + * Persistent storage for the PSYC service. + * @{ + */ +#ifndef GNUNET_PSYCSTORE_SERVICE_H +#define GNUNET_PSYCSTORE_SERVICE_H + +#ifdef __cplusplus +extern "C" +{ +#if 0 /* keep Emacsens' auto-indent happy */ +} +#endif +#endif + +#include "gnunet_util_lib.h" +#include "gnunet_psyc_util_lib.h" +#include "gnunet_multicast_service.h" +#include "gnunet_psyc_service.h" + +/** + * Version number of GNUnet PSYCstore API. + */ +#define GNUNET_PSYCSTORE_VERSION 0x00000000 + +/** + * Membership test failed. + */ +#define GNUNET_PSYCSTORE_MEMBERSHIP_TEST_FAILED -2 + +/** + * Flags for stored messages. + */ +enum GNUNET_PSYCSTORE_MessageFlags +{ + /** + * The message contains state modifiers. + */ + GNUNET_PSYCSTORE_MESSAGE_STATE = 1 << 0, + + /** + * The state modifiers have been applied to the state store. + */ + GNUNET_PSYCSTORE_MESSAGE_STATE_APPLIED = 1 << 1, + + /** + * The message contains a state hash. + */ + GNUNET_PSYCSTORE_MESSAGE_STATE_HASH = 1 << 2 +}; + + +/** + * Handle for a PSYCstore + */ +struct GNUNET_PSYCSTORE_Handle; + + +/** + * Connect to the PSYCstore service. + * + * @param cfg Configuration to use. + * + * @return Handle for the connecton. + */ +struct GNUNET_PSYCSTORE_Handle * +GNUNET_PSYCSTORE_connect (const struct GNUNET_CONFIGURATION_Handle *cfg); + + +/** + * Disconnect from the PSYCstore service. + * + * @param h Handle for the connection. + */ +void +GNUNET_PSYCSTORE_disconnect (struct GNUNET_PSYCSTORE_Handle *h); + + +/** + * Handle for an operation on the PSYCSTORE (useful to cancel the operation). + */ +struct GNUNET_PSYCSTORE_OperationHandle; + + +/** + * Function called with the result of an asynchronous operation. + * + * @param cls + * Closure. + * @param result + * Result of the operation. + * @param err_msg + * Error message, or NULL if there's no error. + * @param err_msg_size + * Size of @a err_msg + */ +typedef void +(*GNUNET_PSYCSTORE_ResultCallback) (void *cls, + int64_t result, + const char *err_msg, + uint16_t err_msg_size); + + +/** + * Store join/leave events for a PSYC channel in order to be able to answer + * membership test queries later. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel where the event happened. + * @param slave_key + * Public key of joining/leaving slave. + * @param did_join + * #GNUNET_YES on join, #GNUNET_NO on part. + * @param announced_at + * ID of the message that announced the membership change. + * @param effective_since + * Message ID this membership change is in effect since. + * For joins it is <= announced_at, for parts it is always 0. + * @param group_generation + * In case of a part, the last group generation the slave has access to. + * It has relevance when a larger message have fragments with different + * group generations. + * @param result_cb + * Callback to call with the result of the storage operation. + * @param cls + * Closure for the callback. + * + * @return Operation handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_membership_store (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + int did_join, + uint64_t announced_at, + uint64_t effective_since, + uint64_t group_generation, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls); + + +/** + * Test if a member was admitted to the channel at the given message ID. + * + * This is useful when relaying and replaying messages to check if a particular + * slave has access to the message fragment with a given group generation. It + * is also used when handling join requests to determine whether the slave is + * currently admitted to the channel. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param slave_key + * Public key of slave whose membership to check. + * @param message_id + * Message ID for which to do the membership test. + * @param group_generation + * Group generation of the fragment of the message to test. + * It has relevance if the message consists of multiple fragments with + * different group generations. + * @param result_cb + * Callback to call with the test result. + * @param cls + * Closure for the callback. + * + * @return Operation handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_membership_test (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + uint64_t message_id, + uint64_t group_generation, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls); + + +/** + * Store a message fragment sent to a channel. + * + * @param h Handle for the PSYCstore. + * @param channel_key The channel the message belongs to. + * @param msg Message to store. + * @param psycstore_flags Flags indicating whether the PSYC message contains + * state modifiers. + * @param result_cb Callback to call with the result of the operation. + * @param cls Closure for the callback. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_fragment_store (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_MULTICAST_MessageHeader *msg, + enum GNUNET_PSYCSTORE_MessageFlags psycstore_flags, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls); + + +/** + * Function called with one message fragment, as the result of a + * GNUNET_PSYCSTORE_fragment_get() or GNUNET_PSYCSTORE_message_get() call. + * + * @param cls Closure. + * @param message The retrieved message fragment. A NULL value indicates that + * there are no more results to be returned. + * @param psycstore_flags Flags stored with the message. + * + * @return #GNUNET_NO to stop calling this callback with further fragments, + * #GNUNET_YES to continue. + */ +typedef int +(*GNUNET_PSYCSTORE_FragmentCallback) (void *cls, + struct GNUNET_MULTICAST_MessageHeader *message, + enum GNUNET_PSYCSTORE_MessageFlags psycstore_flags); + + +/** + * Retrieve message fragments by fragment ID range. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param slave_key + * The slave requesting the fragment. If not NULL, a membership test is + * performed first and the fragment is only returned if the slave has + * access to it. + * @param first_fragment_id + * First fragment ID to retrieve. + * Use 0 to get the latest message fragment. + * @param last_fragment_id + * Last consecutive fragment ID to retrieve. + * Use 0 to get the latest message fragment. + * @param fragment_cb + * Callback to call with the retrieved fragments. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callbacks. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_fragment_get (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + uint64_t first_message_id, + uint64_t last_message_id, + GNUNET_PSYCSTORE_FragmentCallback fragment_cb, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls); + + +/** + * Retrieve latest message fragments. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param slave_key + * The slave requesting the fragment. If not NULL, a membership test is + * performed first and the fragment is only returned if the slave has + * access to it. + * @param first_fragment_id + * First fragment ID to retrieve. + * Use 0 to get the latest message fragment. + * @param last_fragment_id + * Last consecutive fragment ID to retrieve. + * Use 0 to get the latest message fragment. + * @param fragment_limit + * Maximum number of fragments to retrieve. + * @param fragment_cb + * Callback to call with the retrieved fragments. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callbacks. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_fragment_get_latest (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + uint64_t fragment_limit, + GNUNET_PSYCSTORE_FragmentCallback fragment_cb, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls); + + +/** + * Retrieve all fragments of messages in a message ID range. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param slave_key + * The slave requesting the message. + * If not NULL, a membership test is performed first + * and the message is only returned if the slave has access to it. + * @param first_message_id + * First message ID to retrieve. + * @param last_message_id + * Last consecutive message ID to retrieve. + * @param fragment_limit + * Maximum number of fragments to retrieve. + * @param method_prefix + * Retrieve only messages with a matching method prefix. + * @param fragment_cb + * Callback to call with the retrieved fragments. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callbacks. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_message_get (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + uint64_t first_message_id, + uint64_t last_message_id, + uint64_t fragment_limit, + const char *method_prefix, + GNUNET_PSYCSTORE_FragmentCallback fragment_cb, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls); + + +/** + * Retrieve all fragments of the latest messages. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param slave_key + * The slave requesting the message. + * If not NULL, a membership test is performed first + * and the message is only returned if the slave has access to it. + * @param message_limit + * Maximum number of messages to retrieve. + * @param method_prefix + * Retrieve only messages with a matching method prefix. + * @param fragment_cb + * Callback to call with the retrieved fragments. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callbacks. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_message_get_latest (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + uint64_t message_limit, + const char *method_prefix, + GNUNET_PSYCSTORE_FragmentCallback fragment_cb, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls); + + +/** + * Retrieve a fragment of message specified by its message ID and fragment + * offset. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param slave_key + * The slave requesting the message fragment. If not NULL, a membership + * test is performed first and the message fragment is only returned + * if the slave has access to it. + * @param message_id + * Message ID to retrieve. Use 0 to get the latest message. + * @param fragment_offset + * Offset of the fragment to retrieve. + * @param fragment_cb + * Callback to call with the retrieved fragments. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callbacks. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_message_get_fragment (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + uint64_t message_id, + uint64_t fragment_offset, + GNUNET_PSYCSTORE_FragmentCallback fragment_cb, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls); + + +/** + * Callback used to return the latest value of counters for the channel master. + * + * @see GNUNET_PSYCSTORE_counters_get() + * + * @param cls Closure. + * @param result_code + * Status code for the operation: + * #GNUNET_OK: success, counter values are returned. + * #GNUNET_NO: no message has been sent to the channel yet. + * #GNUNET_SYSERR: an error occurred. + * @param max_fragment_id + * Latest message fragment ID, used by multicast. + * @param max_message_id + * Latest message ID, used by PSYC. + * @param max_group_generation + * Latest group generation, used by PSYC. + * @param max_state_message_id + * Latest message ID containing state modifiers that + * was applied to the state store. Used for the state sync process. + */ +typedef void +(*GNUNET_PSYCSTORE_CountersCallback) (void *cls, + int result_code, + uint64_t max_fragment_id, + uint64_t max_message_id, + uint64_t max_group_generation, + uint64_t max_state_message_id); + + +/** + * Retrieve latest values of counters for a channel. + * + * The current value of counters are needed + * - when a channel master is restarted, so that it can continue incrementing + * the counters from their last value. + * - when a channel slave rejoins and starts the state synchronization process. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * Public key that identifies the channel. + * @param counters_cb + * Callback to call with the result. + * @param cls + * Closure for the @a ccb callback. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_counters_get (struct GNUNET_PSYCSTORE_Handle *h, + struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + GNUNET_PSYCSTORE_CountersCallback counters_cb, + void *cls); + + +/** + * Apply modifiers of a message to the current channel state. + * + * An error is returned if there are missing messages containing state + * operations before the current one. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param message_id + * ID of the message that contains the @a modifiers. + * @param state_delta + * Value of the @e state_delta PSYC header variable of the message. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the @a result_cb callback. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_state_modify (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id, + uint64_t state_delta, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls); + + +/** + * Store synchronized state. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param max_state_message_id + * ID of the last stateful message before @a state_hash_message_id. + * @param state_hash_message_id + * ID of the message that contains the state_hash PSYC header variable. + * @param modifier_count + * Number of elements in the @a modifiers array. + * @param modifiers + * Full state to store. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callback. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_state_sync (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t max_state_message_id, + uint64_t state_hash_message_id, + size_t modifier_count, + const struct GNUNET_PSYC_Modifier *modifiers, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls); + + + +/** + * Reset the state of a channel. + * + * Delete all state variables stored for the given channel. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callback. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_state_reset (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey + *channel_key, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls); + + +/** + * Update signed values of state variables in the state store. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param message_id + * Message ID that contained the state @a hash. + * @param hash + * Hash of the serialized full state. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callback. + * + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_state_hash_update (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id, + const struct GNUNET_HashCode *hash, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls); + + +/** + * Function called with the value of a state variable. + * + * @param cls + * Closure. + * @param name + * Name of the state variable. A NULL value indicates that there are no more + * state variables to be returned. + * @param value + * Value of the state variable. + * @param value_size + * Number of bytes in @a value. + * + * @return #GNUNET_NO to stop calling this callback with further variables, + * #GNUNET_YES to continue. + */; +typedef int +(*GNUNET_PSYCSTORE_StateCallback) (void *cls, const char *name, + const void *value, uint32_t value_size); + + +/** + * Retrieve the best matching state variable. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param name + * Name of variable to match, the returned variable might be less specific. + * @param state_cb + * Callback to return the matching state variable. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callbacks. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_state_get (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, + GNUNET_PSYCSTORE_StateCallback state_cb, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls); + + +/** + * Retrieve all state variables for a channel with the given prefix. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param name_prefix + * Prefix of state variable names to match. + * @param state_cb + * Callback to return matching state variables. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callbacks. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_state_get_prefix (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name_prefix, + GNUNET_PSYCSTORE_StateCallback state_cb, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls); + + +/** + * Cancel an operation. + * + * @param op Handle for the operation to cancel. + */ +int +GNUNET_PSYCSTORE_operation_cancel (struct GNUNET_PSYCSTORE_OperationHandle *op); + + + + +#if 0 /* keep Emacsens' auto-indent happy */ +{ +#endif +#ifdef __cplusplus +} +#endif + +/* ifndef GNUNET_PSYCSTORE_SERVICE_H */ +#endif + +/** @} */ /* end of group */ diff --git a/src/include/gnunet_social_service.h b/src/include/gnunet_social_service.h new file mode 100644 index 0000000..7faa336 --- /dev/null +++ b/src/include/gnunet_social_service.h @@ -0,0 +1,1344 @@ +/* + This file is part of GNUnet. + Copyright (C) 2013 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later +*/ + +/** + * @author Gabor X Toth + * @author Christian Grothoff + * + * @file + * Social service; implements social interactions through the PSYC service. + */ + +/** @defgroup social Social service +Social interactions through the PSYC service. + +# Overview + +The social service provides an API for social interactions based on a one-to-many messaging model. +It manages subscriptions of applications to places, provides messaging functionality in places, +allows access to the local message history and manages the GNS zone of _egos_ (user identities). + +The service stores private and public keys of subscribed places, as well as files received in subscribed places. + +# Concepts and terminology + +## Ego, Nym + +An _ego_ is an identity of a user, a private-public key pair. +A _nym_ is an identity of another user in the network, identified by its public key. +Each user can have multiple identities. + +struct GNUNET_SOCIAL_Ego and struct GNUNET_SOCIAL_Nym represents one of these identities. + +## Place, Host, Guest + +A _place_ is where social interactions happen. It is owned and created by an _ego_. +Creating a new place happens by an _ego_ entering a new place as a _host_, +where _guests_ can enter later to receive messages sent to the place. + +A place is identified by its public key. + +- struct GNUNET_SOCIAL_Host represents a place entered as host, +- struct GNUNET_SOCIAL_Guest is used for a place entered as guest. +- A struct GNUNET_SOCIAL_Place can be obtained for both a host and guest place + using GNUNET_SOCIAL_host_get_place() and GNUNET_SOCIAL_guest_get_place() + and can be used with API functions common to hosts and guests. + +## History + +Messages sent to places are stored locally by the PSYCstore service, and can be queried any time. +GNUNET_SOCIAL_history_replay_latest() retrieves the latest N messages sent to the place, +while GNUNET_SOCIAL_history_replay() is used to query a given message ID range. + +## GNU Name System + +The GNU Name System is used for assigning human-readable names to nyms and places. +There's a _GNS zone_ corresponding to each _nym_. +An _ego_ can publish PKEY and PLACE records in its own zone, pointing to nyms and places, respectively. + +## Announcement, talk request + +The host can _announce_ messages to the place, using GNUNET_SOCIAL_host_announce(). +Guests can send _talk_ requests to the host, using GNUNET_SOCIAL_guest_talk(). +The host receives talk requests of guests and can _relay_ them to the place, +or process it using a message handler function. + +# Using the API + +## Connecting to the service + +A client first establishes an _application connection_ to the service using +GNUNET_SOCIAL_app_connect() providing its _application ID_, then receives the +public keys of subscribed places and available egos in response. + +## Reconnecting to places + +Then the application can reconnect to its subscribed places by establishing +_place connections_ with GNUNET_SOCIAL_host_enter_reconnect() and +GNUNET_SOCIAL_guest_enter_reconnect(). + +## Subscribing to a place + +Entering and subscribing a new host or guest place is done using +GNUNET_SOCIAL_host_enter() and GNUNET_SOCIAL_guest_enter(). + +## Disconnecting from a place + +An application can disconnect from a place while the social service keeps its +network connection active, using GNUNET_SOCIAL_host_disconnect() and +GNUNET_SOCIAL_guest_disconnect(). + +## Leaving a place + +To permanently leave a place, see GNUNET_SOCIAL_host_leave() and GNUNET_SOCIAL_guest_leave(). +When leaving a place its network connections are closed and all applications are unsubscribed from the place. + +# Message methods + +## _converse + +Human conversation in a private or public place. + +### Environment + +#### _id_reply +Message ID this message is in reply to. + +#### _id_thread +Thread ID, the first message ID in the thread. + +#### _nym_author +Nym of the author. + +FIXME: Are nyms a different data type from egos and person entities? +Do they have a different format than any other entity address? +Questions and thoughts on how to fix this in "questions.org" + +#### _sig_author +Signature of the message body and its variables by the author. + +### Data + +Message body. + +## _notice_place + +Notification about a place. + +TODO: Applications can decide to auto-subscribe to certain places, +e.g. files under a given size. + +### Environment + +#### Using GNS + +##### _gns_place +GNS name of the place in a globally unique .zkey zone + +FIXME: A custom _gns PSYC data type should be avoidable by parsing +and interpreting PSYC uniforms appropriately. +Thoughts on this in "questions.org" + +#### Without GNS + +##### _key_pub_place +Public key of place + +FIXME: _key_pub can't be the data type for GNUnet-specific cryptographic +addressing. Questions and thoughts on how to fix this in "questions.org" + +##### _peer_origin +Peer ID of origin + +##### _list_peer_relays +List of peer IDs of relays + +## _notice_place_file + +Notification about a place hosting a file. + +### Environment + +The environment of _notice_place above, plus the following: + +#### _size_file +Size of file + +#### _type_file +MIME type of file + +#### _name_file +Name of file + +#### _description_file +Description of file + +## _file + +Messages with a _file method contain a file, +which is saved to disk upon reception at the following location: +$GNUNET_DATA_HOME/social/files// + +### Environment + +#### _size_file +Size of file + +#### _type_file +MIME type of file + +#### _name_file +Name of file + +#### _description_file +Description of file + +@{ +*/ + + +#ifndef GNUNET_SOCIAL_SERVICE_H +#define GNUNET_SOCIAL_SERVICE_H + +#ifdef __cplusplus +extern "C" +{ +#if 0 /* keep Emacsens' auto-indent happy */ +} +#endif +#endif + +#include +#include "gnunet_util_lib.h" +#include "gnunet_psyc_util_lib.h" +#include "gnunet_identity_service.h" +#include "gnunet_namestore_service.h" +#include "gnunet_psyc_service.h" + + +/** + * Version number of GNUnet Social API. + */ +#define GNUNET_SOCIAL_VERSION 0x00000000 + +/** + * Maximum size of client ID including '\0' terminator. + */ +#define GNUNET_SOCIAL_APP_MAX_ID_SIZE 256 + +enum GNUNET_SOCIAL_MsgProcFlags { + GNUNET_SOCIAL_MSG_PROC_NONE = 0, + GNUNET_SOCIAL_MSG_PROC_RELAY = 1, + GNUNET_SOCIAL_MSG_PROC_SAVE= 2, +}; + +/** + * Handle for an application. + */ +struct GNUNET_SOCIAL_App; + +/** + * Handle for an ego (own identity) + */ +struct GNUNET_SOCIAL_Ego; + +/** + * Handle for a pseudonym of another user in the network. + */ +struct GNUNET_SOCIAL_Nym; + +/** + * Handle for a place where social interactions happen. + */ +struct GNUNET_SOCIAL_Place; + +/** + * Host handle for a place that we entered. + */ +struct GNUNET_SOCIAL_Host; + +/** + * Guest handle for place that we entered. + */ +struct GNUNET_SOCIAL_Guest; + +/** + * Handle that can be used to reconnect to a place as host. + */ +struct GNUNET_SOCIAL_HostConnection; + +/** + * Handle that can be used to reconnect to a place as guest. + */ +struct GNUNET_SOCIAL_GuestConnection; + +/** + * Notification about an available identity. + * + * @param cls + * Closure. + * @param pub_key + * Public key of ego. + * @param name + * Name of ego. + */ +typedef void +(*GNUNET_SOCIAL_AppEgoCallback) (void *cls, + struct GNUNET_SOCIAL_Ego *ego, + const struct GNUNET_CRYPTO_EcdsaPublicKey *ego_pub_key, + const char *name); + + +/** + * Entry status of a place per application. + */ +enum GNUNET_SOCIAL_AppPlaceState +{ + /** + * The place was once entered by the ego, but left since. + * It's possible to establish a local connection to the place + * without re-entering to fetch history from the PSYCstore. + * @see enum GNUNET_PSYC_SlaveJoinFlags and GNUNET_SOCIAL_guest_enter() + */ + GNUNET_SOCIAL_PLACE_STATE_ARCHIVED = 0, + + /** + * The place is entered by the ego, + * but this application is not subscribed to it. + */ + GNUNET_SOCIAL_PLACE_STATE_ENTERED = 1, + + /** + * The place is entered by the ego and + * and this application is subscribed to it. + */ + GNUNET_SOCIAL_PLACE_STATE_SUBSCRIBED = 2, +}; + + +/** + * Called after receiving initial list of egos and places. + */ +typedef void +(*GNUNET_SOCIAL_AppConnectedCallback) (void *cls); + + +/** + * Notification about a home. + * + * @param cls + * Closure. + * @param hconn + * Host connection, to be used with GNUNET_SOCIAL_host_enter_reconnect() + * @param ego + * Ego used to enter the place. + * @param place_pub_key + * Public key of the place. + * @param place_state + * @see enum GNUNET_SOCIAL_AppPlaceState + */ +typedef void +(*GNUNET_SOCIAL_AppHostPlaceCallback) (void *cls, + struct GNUNET_SOCIAL_HostConnection *hconn, + struct GNUNET_SOCIAL_Ego *ego, + const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key, + enum GNUNET_SOCIAL_AppPlaceState place_state); + +/** + * Notification about a place. + * + * @param cls + * Closure. + * @param gconn + * Guest connection, to be used with GNUNET_SOCIAL_guest_enter_reconnect() + * @param ego + * Ego used to enter the place. + * @param place_pub_key + * Public key of the place. + * @param place_state + * @see enum GNUNET_SOCIAL_AppPlaceState + */ +typedef void +(*GNUNET_SOCIAL_AppGuestPlaceCallback) (void *cls, + struct GNUNET_SOCIAL_GuestConnection *gconn, + struct GNUNET_SOCIAL_Ego *ego, + const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key, + enum GNUNET_SOCIAL_AppPlaceState place_state); + + +/** + * Establish application connection to the social service. + * + * The @host_cb and @guest_cb functions are + * initially called for each entered places, + * then later each time a new place is entered with the current app ID. + * + * @param cfg + * Configuration. + * @param ego_cb + * Function to notify about an available ego. + * @param host_cb + * Function to notify about a place entered as host. + * @param guest_cb + * Function to notify about a place entered as guest. + * @param cls + * Closure for the callbacks. + * + * @return Handle that can be used to stop listening. + */ +struct GNUNET_SOCIAL_App * +GNUNET_SOCIAL_app_connect (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *id, + GNUNET_SOCIAL_AppEgoCallback ego_cb, + GNUNET_SOCIAL_AppHostPlaceCallback host_cb, + GNUNET_SOCIAL_AppGuestPlaceCallback guest_cb, + GNUNET_SOCIAL_AppConnectedCallback connected_cb, + void *cls); + + +/** + * Disconnect app. + * + * @param app + * Application handle. + * @param disconnect_cb + * Disconnect callback. + * @param disconnect_cls + * Disconnect closure. + */ +void +GNUNET_SOCIAL_app_disconnect (struct GNUNET_SOCIAL_App *app, + GNUNET_ContinuationCallback disconnect_cb, + void *disconnect_cls); + + +/** + * Get the public key of @a ego. + * + * @param ego + * Ego. + * + * @return Public key of ego. + */ +const struct GNUNET_CRYPTO_EcdsaPublicKey * +GNUNET_SOCIAL_ego_get_pub_key (const struct GNUNET_SOCIAL_Ego *ego); + + +/** + * Get the name of @a ego. + * + * @param ego + * Ego. + * + * @return Public key of @a ego. + */ +const char * +GNUNET_SOCIAL_ego_get_name (const struct GNUNET_SOCIAL_Ego *ego); + + +/** + * Get the public key of a @a nym. + * + * Suitable, for example, to be used with GNUNET_SOCIAL_zone_add_nym(). + * + * @param nym + * Pseudonym to map to a cryptographic identifier. + * + * @return Public key of nym. + */ +const struct GNUNET_CRYPTO_EcdsaPublicKey * +GNUNET_SOCIAL_nym_get_pub_key (const struct GNUNET_SOCIAL_Nym *nym); + + +/** + * Get the hash of the public key of a @a nym. + * + * @param nym + * Pseudonym to map to a cryptographic identifier. + * + * @return Hash of the public key of nym. + */ +const struct GNUNET_HashCode * +GNUNET_SOCIAL_nym_get_pub_key_hash (const struct GNUNET_SOCIAL_Nym *nym); + + +/** + * Function called asking for nym to be admitted to the place. + * + * Should call either GNUNET_SOCIAL_host_admit() or + * GNUNET_SOCIAL_host_reject_entry() (possibly asynchronously). If this host + * cannot decide, it is fine to call neither function, in which case hopefully + * some other host of the place exists that will make the decision. The @a nym + * reference remains valid until the #GNUNET_SOCIAL_FarewellCallback is invoked + * for it. + * + * @param cls + * Closure. + * @param nym + * Handle for the user who wants to enter. + * @param method_name + * Method name in the entry request. + * @param variable_count + * Number of elements in the @a variables array. + * @param variables + * Variables present in the message. + * @param data + * Payload given on enter (e.g. a password). + * @param data_size + * Number of bytes in @a data. + */ +typedef void +(*GNUNET_SOCIAL_AnswerDoorCallback) (void *cls, + struct GNUNET_SOCIAL_Nym *nym, + const char *method_name, + struct GNUNET_PSYC_Environment *env, + const void *data, + size_t data_size); + + +/** + * Function called when a @a nym leaves the place. + * + * This is also called if the @a nym was never given permission to enter + * (i.e. the @a nym stopped asking to get in). + * + * @param cls + * Closure. + * @param nym + * Handle for the user who left. + */ +typedef void +(*GNUNET_SOCIAL_FarewellCallback) (void *cls, + const struct GNUNET_SOCIAL_Nym *nym, + struct GNUNET_PSYC_Environment *env); + + +/** + * Function called after the host entered a home. + * + * @param cls + * Closure. + * @param result + * #GNUNET_OK on success, or + * #GNUNET_SYSERR on error. + * @param place_pub_key + * Public key of home. + * @param max_message_id + * Last message ID sent to the channel. + * Or 0 if no messages have been sent to the place yet. + */ +typedef void +(*GNUNET_SOCIAL_HostEnterCallback) (void *cls, int result, + const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key, + uint64_t max_message_id); + + +/** + * Enter a place as host. + * + * A place is created upon first entering, and it is active until permanently + * left using GNUNET_SOCIAL_host_leave(). + * + * @param cfg + * Configuration to contact the social service. + * @param ego + * Identity of the host. + * @param place_key + * Private-public key pair of the place. + * NULL for ephemeral places. + * @param policy + * Policy specifying entry and history restrictions for the place. + * @param slicer + * Slicer to handle incoming messages. + * @param enter_cb + * Function called when the place is entered and ready to use. + * @param answer_door_cb + * Function to handle new nyms that want to enter. + * @param farewell_cb + * Function to handle departing nyms. + * @param cls + * Closure for the callbacks. + * + * @return Handle for the host. + */ +struct GNUNET_SOCIAL_Host * +GNUNET_SOCIAL_host_enter (const struct GNUNET_SOCIAL_App *app, + const struct GNUNET_SOCIAL_Ego *ego, + enum GNUNET_PSYC_Policy policy, + struct GNUNET_PSYC_Slicer *slicer, + GNUNET_SOCIAL_HostEnterCallback enter_cb, + GNUNET_SOCIAL_AnswerDoorCallback answer_door_cb, + GNUNET_SOCIAL_FarewellCallback farewell_cb, + void *cls); + + +/** + * Reconnect to an already entered place as host. + * + * @param hconn + * Host connection handle. + * @see GNUNET_SOCIAL_app_connect() & GNUNET_SOCIAL_AppHostPlaceCallback() + * @param slicer + * Slicer to handle incoming messages. + * @param enter_cb + * Function called when the place is entered and ready to use. + * @param answer_door_cb + * Function to handle new nyms that want to enter. + * @param farewell_cb + * Function to handle departing nyms. + * @param cls + * Closure for the callbacks. + * + * @return Handle for the host. + */ +struct GNUNET_SOCIAL_Host * +GNUNET_SOCIAL_host_enter_reconnect (struct GNUNET_SOCIAL_HostConnection *hconn, + struct GNUNET_PSYC_Slicer *slicer, + GNUNET_SOCIAL_HostEnterCallback enter_cb, + GNUNET_SOCIAL_AnswerDoorCallback answer_door_cb, + GNUNET_SOCIAL_FarewellCallback farewell_cb, + void *cls); + + +/** + * Decision whether to admit @a nym into the place or refuse entry. + * + * @param hst + * Host of the place. + * @param nym + * Handle for the entity that wanted to enter. + * @param is_admitted + * #GNUNET_YES if @a nym is admitted, + * #GNUNET_NO if @a nym is refused entry, + * #GNUNET_SYSERR if we cannot answer the request. + * @param entry_resp + * Entry response message, or NULL. + * @return #GNUNET_OK on success, + * #GNUNET_SYSERR if the message is too large. + */ +int +GNUNET_SOCIAL_host_entry_decision (struct GNUNET_SOCIAL_Host *hst, + struct GNUNET_SOCIAL_Nym *nym, + int is_admitted, + const struct GNUNET_PSYC_Message *entry_resp); + + +/** + * Throw @a nym out of the place. + * + * Sends a _notice_place_leave announcement to the home. + * + * The @a nym reference will remain valid until the + * #GNUNET_SOCIAL_FarewellCallback is invoked, + * which should be very soon after this call. + * + * @param host + * Host of the place. + * @param nym + * Handle for the entity to be ejected. + * @param env + * Environment for the message or NULL. + * _nym is set to @e nym regardless whether an @e env is provided. + */ +void +GNUNET_SOCIAL_host_eject (struct GNUNET_SOCIAL_Host *host, + const struct GNUNET_SOCIAL_Nym *nym, + struct GNUNET_PSYC_Environment *env); + + +/** + * Flags for announcements by a host. + */ +enum GNUNET_SOCIAL_AnnounceFlags +{ + GNUNET_SOCIAL_ANNOUNCE_NONE = 0, + + /** + * Whether this announcement removes all objects from the place. + * + * New objects can be still added to the now empty place using the @e env + * parameter of the same announcement. + */ + GNUNET_SOCIAL_ANNOUNCE_CLEAR_OBJECTS = 1 << 0 +}; + + +/** + * Handle for an announcement request. + */ +struct GNUNET_SOCIAL_Announcement; + + +/** + * Send a message to all nyms that are present in the place. + * + * This function is restricted to the host. Nyms can only send requests + * to the host who can decide to relay it to everyone in the place. + * + * @param host + * Host of the place. + * @param method_name + * Method to use for the announcement. + * @param env + * Environment containing variables for the message and operations + * on objects of the place. + * Has to remain available until the first call to @a notify_data. + * Can be NULL. + * @param notify_data + * Function to call to get the payload of the announcement. + * @param notify_data_cls + * Closure for @a notify. + * @param flags + * Flags for this announcement. + * + * @return NULL on error (another announcement already in progress?). + */ +struct GNUNET_SOCIAL_Announcement * +GNUNET_SOCIAL_host_announce (struct GNUNET_SOCIAL_Host *host, + const char *method_name, + const struct GNUNET_PSYC_Environment *env, + GNUNET_PSYC_TransmitNotifyData notify_data, + void *notify_data_cls, + enum GNUNET_SOCIAL_AnnounceFlags flags); + + +/** + * Resume transmitting announcement. + * + * @param a + * The announcement to resume. + */ +void +GNUNET_SOCIAL_host_announce_resume (struct GNUNET_SOCIAL_Announcement *a); + + +/** + * Cancel announcement. + * + * @param a + * The announcement to cancel. + */ +void +GNUNET_SOCIAL_host_announce_cancel (struct GNUNET_SOCIAL_Announcement *a); + + +/** + * Allow relaying messages from guests matching a given @a method_prefix. + * + * @param host + * The host. + * @param method_prefix + * Method prefix to allow. + */ +void +GNUNET_SOCIAL_host_relay_allow_method (struct GNUNET_SOCIAL_Host *host, + const char *method_prefix); + + +/** + * Allow relaying changes to objects of the place. + * + * Only applies to messages with an allowed method name. + * @see GNUNET_SCOIAL_host_relay_allow_method() + * + * @param host + * The host. + * @param object_prefix + * Object prefix to allow modifying. + */ +void +GNUNET_SOCIAL_host_relay_allow_method (struct GNUNET_SOCIAL_Host *host, + const char *object_prefix); + + +/** + * Stop relaying messages from guests. + * + * Remove all allowed relay rules. + * + * + * + */ +void +GNUNET_SOCIAL_host_relay_stop (struct GNUNET_SOCIAL_Host *host); + + +/** + * Obtain handle for a hosted place. + * + * The returned handle can be used to access the place API. + * + * @param host + * Handle for the host. + * + * @return Handle for the hosted place, valid as long as @a host is valid. + */ +struct GNUNET_SOCIAL_Place * +GNUNET_SOCIAL_host_get_place (struct GNUNET_SOCIAL_Host *host); + + +/** + * Disconnect from a home. + * + * Invalidates host handle. + * + * @param hst + * The host to disconnect. + * @param disconnect_cb + * Function called after disconnected from the service. + * @param cls + * Closure for @a disconnect_cb. + */ +void +GNUNET_SOCIAL_host_disconnect (struct GNUNET_SOCIAL_Host *hst, + GNUNET_ContinuationCallback disconnect_cb, + void *cls); + + +/** + * Stop hosting a home. + * + * Sends a _notice_place_closing announcement to the home. + * Invalidates host handle. + * + * @param hst + * Host leaving. + * @param env + * Environment for the message or NULL. + * @param disconnect_cb + * Function called after the host left the place + * and disconnected from the service. + * @param cls + * Closure for @a disconnect_cb. + */ +void +GNUNET_SOCIAL_host_leave (struct GNUNET_SOCIAL_Host *hst, + const struct GNUNET_PSYC_Environment *env, + GNUNET_ContinuationCallback disconnect_cb, + void *cls); + + +/** + * Function called after the guest entered the local copy of the place. + * + * History and object query functions can be used after this call, + * but new messages can't be sent or received. + * + * @param cls + * Closure. + * @param result + * #GNUNET_OK on success, or + * #GNUNET_SYSERR on error, e.g. could not connect to the service, or + * could not resolve GNS name. + * @param place_pub_key + * Public key of place. + * @param max_message_id + * Last message ID sent to the place. + * Or 0 if no messages have been sent to the place yet. + */ +typedef void +(*GNUNET_SOCIAL_GuestEnterCallback) (void *cls, int result, + const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key, + uint64_t max_message_id); + + +/** + * Function called upon a guest receives a decision about entry to the place. + * + * @param is_admitted + * Is the guest admitted to the place? + * #GNUNET_YES if admitted, + * #GNUNET_NO if refused entry, + * #GNUNET_SYSERR if the request could not be answered. + * @param data + * Entry response message. + */ +typedef void +(*GNUNET_SOCIAL_EntryDecisionCallback) (void *cls, + int is_admitted, + const struct GNUNET_PSYC_Message *entry_resp); + + +/** + * Request entry to a place as a guest. + * + * @param app + * Application handle. + * @param ego + * Identity of the guest. + * @param place_pub_key + * Public key of the place to enter. + * @param flags + * Flags for the entry. + * @param origin + * Peer identity of the origin of the underlying multicast group. + * @param relay_count + * Number of elements in the @a relays array. + * @param relays + * Relays for the underlying multicast group. + * @param entry_msg + * Entry message. + * @param slicer + * Slicer to use for processing incoming requests from guests. + * + * @return NULL on errors, otherwise handle for the guest. + */ +struct GNUNET_SOCIAL_Guest * +GNUNET_SOCIAL_guest_enter (const struct GNUNET_SOCIAL_App *app, + const struct GNUNET_SOCIAL_Ego *ego, + const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key, + enum GNUNET_PSYC_SlaveJoinFlags flags, + const struct GNUNET_PeerIdentity *origin, + uint32_t relay_count, + const struct GNUNET_PeerIdentity *relays, + const struct GNUNET_PSYC_Message *entry_msg, + struct GNUNET_PSYC_Slicer *slicer, + GNUNET_SOCIAL_GuestEnterCallback local_enter_cb, + GNUNET_SOCIAL_EntryDecisionCallback entry_dcsn_cb, + void *cls); + + +/** + * Request entry to a place by name as a guest. + * + * @param app + * Application handle. + * @param ego + * Identity of the guest. + * @param gns_name + * GNS name of the place to enter. Either in the form of + * 'room.friend.gnu', or 'NYMPUBKEY.zkey'. This latter case refers to + * the 'PLACE' record of the empty label ("+") in the GNS zone with the + * nym's public key 'NYMPUBKEY', and can be used to request entry to a + * pseudonym's place directly. + * @param password + * Password to decrypt the record, or NULL for cleartext records. + * @param join_msg + * Entry request message. + * @param slicer + * Slicer to use for processing incoming requests from guests. + * @param local_enter_cb + * Called upon connection established to the social service. + * @param entry_decision_cb + * Called upon receiving entry decision. + * + * @return NULL on errors, otherwise handle for the guest. + */ +struct GNUNET_SOCIAL_Guest * +GNUNET_SOCIAL_guest_enter_by_name (const struct GNUNET_SOCIAL_App *app, + const struct GNUNET_SOCIAL_Ego *ego, + const char *gns_name, + const char *password, + const struct GNUNET_PSYC_Message *join_msg, + struct GNUNET_PSYC_Slicer *slicer, + GNUNET_SOCIAL_GuestEnterCallback local_enter_cb, + GNUNET_SOCIAL_EntryDecisionCallback entry_decision_cb, + void *cls); + + +/** + * Reconnect to an already entered place as guest. + * + * @param gconn + * Guest connection handle. + * @see GNUNET_SOCIAL_app_connect() & GNUNET_SOCIAL_AppGuestPlaceCallback() + * @param flags + * Flags for the entry. + * @param slicer + * Slicer to use for processing incoming requests from guests. + * @param local_enter_cb + * Called upon connection established to the social service. + * @param entry_decision_cb + * Called upon receiving entry decision. + * + * @return NULL on errors, otherwise handle for the guest. + */ +struct GNUNET_SOCIAL_Guest * +GNUNET_SOCIAL_guest_enter_reconnect (struct GNUNET_SOCIAL_GuestConnection *gconn, + enum GNUNET_PSYC_SlaveJoinFlags flags, + struct GNUNET_PSYC_Slicer *slicer, + GNUNET_SOCIAL_GuestEnterCallback local_enter_cb, + void *cls); + + +/** + * Flags for talking to the host of a place. + */ +enum GNUNET_SOCIAL_TalkFlags +{ + GNUNET_SOCIAL_TALK_NONE = 0 +}; + + +/** + * A talk request. + */ +struct GNUNET_SOCIAL_TalkRequest; + + +/** + * Talk to the host of the place. + * + * @param place + * Place where we want to talk to the host. + * @param method_name + * Method to invoke on the host. + * @param env + * Environment containing variables for the message, or NULL. + * @param notify_data + * Function to use to get the payload for the method. + * @param notify_data_cls + * Closure for @a notify_data. + * @param flags + * Flags for the message being sent. + * + * @return NULL if we are already trying to talk to the host, + * otherwise handle to cancel the request. + */ +struct GNUNET_SOCIAL_TalkRequest * +GNUNET_SOCIAL_guest_talk (struct GNUNET_SOCIAL_Guest *guest, + const char *method_name, + const struct GNUNET_PSYC_Environment *env, + GNUNET_PSYC_TransmitNotifyData notify_data, + void *notify_data_cls, + enum GNUNET_SOCIAL_TalkFlags flags); + + +/** + * Resume talking to the host of the place. + * + * @param tr + * Talk request to resume. + */ +void +GNUNET_SOCIAL_guest_talk_resume (struct GNUNET_SOCIAL_TalkRequest *tr); + + +/** + * Cancel talking to the host of the place. + * + * @param tr + * Talk request to cancel. + */ +void +GNUNET_SOCIAL_guest_talk_cancel (struct GNUNET_SOCIAL_TalkRequest *tr); + + +/** + * Disconnect from a place. + * + * Invalidates guest handle. + * + * @param gst + * The guest to disconnect. + * @param disconnect_cb + * Function called after disconnected from the service. + * @param cls + * Closure for @a disconnect_cb. + */ +void +GNUNET_SOCIAL_guest_disconnect (struct GNUNET_SOCIAL_Guest *gst, + GNUNET_ContinuationCallback disconnect_cb, + void *cls); + + +/** + * Leave a place temporarily or permanently. + * + * Notifies the owner of the place about leaving, and destroys the place handle. + * + * @param place + * Place to leave. + * @param env + * Optional environment for the leave message if @a keep_active + * is #GNUNET_NO. NULL if not needed. + * @param disconnect_cb + * Called upon disconnecting from the social service. + */ +void +GNUNET_SOCIAL_guest_leave (struct GNUNET_SOCIAL_Guest *gst, + struct GNUNET_PSYC_Environment *env, + GNUNET_ContinuationCallback disconnect_cb, + void *leave_cls); + + +/** + * Obtain handle for a place entered as guest. + * + * The returned handle can be used to access the place API. + * + * @param guest Handle for the guest. + * + * @return Handle for the place, valid as long as @a guest is valid. + */ +struct GNUNET_SOCIAL_Place * +GNUNET_SOCIAL_guest_get_place (struct GNUNET_SOCIAL_Guest *guest); + + +/** + * A history request. + */ +struct GNUNET_SOCIAL_HistoryRequest; + + +/** + * Get the public key of a place. + * + * @param plc + * Place. + * + * @return Public key of the place. + */ +const struct GNUNET_CRYPTO_EddsaPublicKey * +GNUNET_SOCIAL_place_get_pub_key (const struct GNUNET_SOCIAL_Place *plc); + + +/** + * Set message processing @a flags for a @a method_prefix. + * + * @param plc + * Place. + * @param method_prefix + * Method prefix @a flags apply to. + * @param flags + * The flags that apply to a matching @a method_prefix. + */ +void +GNUNET_SOCIAL_place_msg_proc_set (struct GNUNET_SOCIAL_Place *plc, + const char *method_prefix, + enum GNUNET_SOCIAL_MsgProcFlags flags); + +/** + * Clear all message processing flags previously set for this place. + */ +void +GNUNET_SOCIAL_place_msg_proc_clear (struct GNUNET_SOCIAL_Place *plc); + + +/** + * Learn about the history of a place. + * + * Messages are returned through the @a slicer function + * and have the #GNUNET_PSYC_MESSAGE_HISTORIC flag set. + * + * @param place + * Place we want to learn more about. + * @param start_message_id + * First historic message we are interested in. + * @param end_message_id + * Last historic message we are interested in (inclusive). + * @param method_prefix + * Only retrieve messages with this method prefix. + * @param flags + * OR'ed GNUNET_PSYC_HistoryReplayFlags + * @param slicer + * Slicer to use for retrieved messages. + * Can be the same as the slicer of the place. + * @param result_cb + * Function called after all messages retrieved. + * NULL if not needed. + * @param cls Closure for @a result_cb. + */ +struct GNUNET_SOCIAL_HistoryRequest * +GNUNET_SOCIAL_place_history_replay (struct GNUNET_SOCIAL_Place *plc, + uint64_t start_message_id, + uint64_t end_message_id, + const char *method_prefix, + uint32_t flags, + struct GNUNET_PSYC_Slicer *slicer, + GNUNET_ResultCallback result_cb, + void *cls); + + +/** + * Learn about the history of a place. + * + * Sends messages through the slicer function of the place where + * start_message_id <= message_id <= end_message_id. + * The messages will have the #GNUNET_PSYC_MESSAGE_HISTORIC flag set. + * + * To get the latest message, use 0 for both the start and end message ID. + * + * @param place + * Place we want to learn more about. + * @param message_limit + * Maximum number of historic messages we are interested in. + * @param result_cb + * Function called after all messages retrieved. + * NULL if not needed. + * @param cls Closure for @a result_cb. + */ +struct GNUNET_SOCIAL_HistoryRequest * +GNUNET_SOCIAL_place_history_replay_latest (struct GNUNET_SOCIAL_Place *plc, + uint64_t message_limit, + const char *method_prefix, + uint32_t flags, + struct GNUNET_PSYC_Slicer *slicer, + GNUNET_ResultCallback result_cb, + void *cls); + +/** + * Cancel learning about the history of a place. + * + * @param hist + * History lesson to cancel. + */ +void +GNUNET_SOCIAL_place_history_replay_cancel (struct GNUNET_SOCIAL_HistoryRequest *hist); + + +struct GNUNET_SOCIAL_LookHandle; + + +/** + * Look at a particular object in the place. + * + * The best matching object is returned (its name might be less specific than + * what was requested). + * + * @param place + * The place to look the object at. + * @param full_name + * Full name of the object. + * + * @return NULL if there is no such object at this place. + */ +struct GNUNET_SOCIAL_LookHandle * +GNUNET_SOCIAL_place_look_at (struct GNUNET_SOCIAL_Place *plc, + const char *full_name, + GNUNET_PSYC_StateVarCallback var_cb, + GNUNET_ResultCallback result_cb, + void *cls); + +/** + * Look for objects in the place with a matching name prefix. + * + * @param place + * The place to look its objects at. + * @param name_prefix + * Look at objects with names beginning with this value. + * @param var_cb + * Function to call for each object found. + * @param cls + * Closure for callback function. + * + * @return Handle that can be used to stop looking at objects. + */ +struct GNUNET_SOCIAL_LookHandle * +GNUNET_SOCIAL_place_look_for (struct GNUNET_SOCIAL_Place *plc, + const char *name_prefix, + GNUNET_PSYC_StateVarCallback var_cb, + GNUNET_ResultCallback result_cb, + void *cls); + + +/** + * Stop looking at objects. + * + * @param lh Look handle to stop. + */ +void +GNUNET_SOCIAL_place_look_cancel (struct GNUNET_SOCIAL_LookHandle *lh); + + +/** + * Advertise a @e place in the GNS zone of @a ego. + * + * @param app + * Application handle. + * @param ego + * Ego. + * @param place_pub_key + * Public key of place to add. + * @param name + * The name for the PLACE record to put in the zone. + * @param password + * Password used to encrypt the record or NULL to keep it cleartext. + * @param relay_count + * Number of elements in the @a relays array. + * @param relays + * List of relays to put in the PLACE record to advertise + * as entry points to the place in addition to the origin. + * @param expiration_time + * Expiration time of the record, use 0 to remove the record. + * @param result_cb + * Function called with the result of the operation. + * @param result_cls + * Closure for @a result_cb + * + * @return #GNUNET_OK if the request was sent, + * #GNUNET_SYSERR on error, e.g. the name/password is too long. + */ +int +GNUNET_SOCIAL_zone_add_place (const struct GNUNET_SOCIAL_App *app, + const struct GNUNET_SOCIAL_Ego *ego, + const char *name, + const char *password, + const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key, + const struct GNUNET_PeerIdentity *origin, + uint32_t relay_count, + const struct GNUNET_PeerIdentity *relays, + struct GNUNET_TIME_Absolute expiration_time, + GNUNET_ResultCallback result_cb, + void *result_cls); + + +/** + * Add public key to the GNS zone of the @e ego. + * + * @param cfg + * Configuration. + * @param ego + * Ego. + * @param name + * The name for the PKEY record to put in the zone. + * @param nym_pub_key + * Public key of nym to add. + * @param expiration_time + * Expiration time of the record, use 0 to remove the record. + * @param result_cb + * Function called with the result of the operation. + * @param result_cls + * Closure for @a result_cb + * + * @return #GNUNET_OK if the request was sent, + * #GNUNET_SYSERR on error, e.g. the name is too long. + */ +int +GNUNET_SOCIAL_zone_add_nym (const struct GNUNET_SOCIAL_App *app, + const struct GNUNET_SOCIAL_Ego *ego, + const char *name, + const struct GNUNET_CRYPTO_EcdsaPublicKey *nym_pub_key, + struct GNUNET_TIME_Absolute expiration_time, + GNUNET_ResultCallback result_cb, + void *result_cls); + + +#if 0 /* keep Emacsens' auto-indent happy */ +{ +#endif +#ifdef __cplusplus +} +#endif + +/* ifndef GNUNET_SOCIAL_SERVICE_H */ +#endif + +/** @} */ /* end of group */ diff --git a/src/multicast/.gitignore b/src/multicast/.gitignore new file mode 100644 index 0000000..a97844e --- /dev/null +++ b/src/multicast/.gitignore @@ -0,0 +1,7 @@ +gnunet-service-multicast +gnunet-multicast +test_multicast +test_multicast_multipeer +test_multicast_2peers +test_multicast_multipeer_line +test_multicast_multipeer_star diff --git a/src/multicast/Makefile.am b/src/multicast/Makefile.am new file mode 100644 index 0000000..61a9f8b --- /dev/null +++ b/src/multicast/Makefile.am @@ -0,0 +1,79 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +pkgcfgdir= $(pkgdatadir)/config.d/ + +libexecdir= $(pkglibdir)/libexec/ + +pkgcfg_DATA = \ + multicast.conf + +if MINGW + WINFLAGS = -Wl,--no-undefined -Wl,--export-all-symbols +endif + +if USE_COVERAGE + AM_CFLAGS = -fprofile-arcs -ftest-coverage +endif + +lib_LTLIBRARIES = libgnunetmulticast.la + +libgnunetmulticast_la_SOURCES = \ + multicast_api.c multicast.h +libgnunetmulticast_la_LIBADD = \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(GN_LIBINTL) $(XLIB) +libgnunetmulticast_la_LDFLAGS = \ + $(GN_LIB_LDFLAGS) $(WINFLAGS) \ + -version-info 0:0:0 + + +bin_PROGRAMS = \ + gnunet-multicast + +libexec_PROGRAMS = \ + gnunet-service-multicast \ + $(EXP_LIBEXEC) + +gnunet_multicast_SOURCES = \ + gnunet-multicast.c +gnunet_multicast_LDADD = \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(GN_LIBINTL) + +gnunet_service_multicast_SOURCES = \ + gnunet-service-multicast.c +gnunet_service_multicast_LDADD = \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(top_builddir)/src/cadet/libgnunetcadet.la \ + $(top_builddir)/src/statistics/libgnunetstatistics.la \ + $(GN_LIBINTL) + +check_PROGRAMS = \ + test_multicast \ + test_multicast_multipeer_star \ + test_multicast_multipeer_line + +if ENABLE_TEST_RUN +AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@}; export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH; unset XDG_DATA_HOME; unset XDG_CONFIG_HOME; +TESTS = $(check_PROGRAMS) +endif + +test_multicast_SOURCES = \ + test_multicast.c +test_multicast_LDADD = \ + libgnunetmulticast.la \ + $(top_builddir)/src/testing/libgnunettesting.la \ + $(top_builddir)/src/util/libgnunetutil.la +test_multicast_multipeer_star_SOURCES = \ + test_multicast_multipeer.c +test_multicast_multipeer_star_LDADD = \ + libgnunetmulticast.la \ + $(top_builddir)/src/testbed/libgnunettestbed.la \ + $(top_builddir)/src/util/libgnunetutil.la +test_multicast_multipeer_line_SOURCES = \ + test_multicast_multipeer.c +test_multicast_multipeer_line_LDADD = \ + libgnunetmulticast.la \ + $(top_builddir)/src/testbed/libgnunettestbed.la \ + $(top_builddir)/src/util/libgnunetutil.la diff --git a/src/multicast/gnunet-multicast.c b/src/multicast/gnunet-multicast.c new file mode 100644 index 0000000..63e1d52 --- /dev/null +++ b/src/multicast/gnunet-multicast.c @@ -0,0 +1,79 @@ +/* + This file is part of GNUnet. + Copyright (C) 2013 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later +*/ + +/** + * @file multicast/gnunet-multicast.c + * @brief multicast for writing a tool + * @author Christian Grothoff + */ +#include "platform.h" +#include "gnunet_util_lib.h" +/* #include "gnunet_multicast_service.h" */ + +/** + * Final status code. + */ +static int ret; + +/** + * Main function that will be run by the scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param cfg configuration + */ +static void +run (void *cls, char *const *args, const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + /* main code here */ + puts( gettext_noop ("This command doesn't do anything yet.") ); + ret = -1; +} + + +/** + * The main function. + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + static const struct GNUNET_GETOPT_CommandLineOption options[] = { + /* FIMXE: add options here */ + GNUNET_GETOPT_OPTION_END + }; + if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv)) + return 2; + + ret = (GNUNET_OK == + GNUNET_PROGRAM_run (argc, argv, "gnunet-multicast", + gettext_noop ("This command doesn't do anything yet."), + options, &run, + NULL)) ? ret : 1; + GNUNET_free ((void*) argv); + return ret; +} + +/* end of gnunet-multicast.c */ diff --git a/src/multicast/gnunet-service-multicast.c b/src/multicast/gnunet-service-multicast.c new file mode 100644 index 0000000..18c3661 --- /dev/null +++ b/src/multicast/gnunet-service-multicast.c @@ -0,0 +1,2234 @@ +/* + This file is part of GNUnet. + Copyright (C) 2009 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later +*/ + +/** + * @file multicast/gnunet-service-multicast.c + * @brief program that does multicast + * @author Christian Grothoff + */ +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_signatures.h" +#include "gnunet_applications.h" +#include "gnunet_statistics_service.h" +#include "gnunet_cadet_service.h" +#include "gnunet_multicast_service.h" +#include "multicast.h" + +/** + * Handle to our current configuration. + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Service handle. + */ +static struct GNUNET_SERVICE_Handle *service; + +/** + * CADET handle. + */ +static struct GNUNET_CADET_Handle *cadet; + +/** + * Identity of this peer. + */ +static struct GNUNET_PeerIdentity this_peer; + +/** + * Handle to the statistics service. + */ +static struct GNUNET_STATISTICS_Handle *stats; + +/** + * All connected origin clients. + * Group's pub_key_hash -> struct Origin * (uniq) + */ +static struct GNUNET_CONTAINER_MultiHashMap *origins; + +/** + * All connected member clients. + * Group's pub_key_hash -> struct Member * (multi) + */ +static struct GNUNET_CONTAINER_MultiHashMap *members; + +/** + * Connected member clients per group. + * Group's pub_key_hash -> Member's pub_key_hash (uniq) -> struct Member * (uniq) + */ +static struct GNUNET_CONTAINER_MultiHashMap *group_members; + +/** + * Incoming CADET channels with connected children in the tree. + * Group's pub_key_hash -> struct Channel * (multi) + */ +static struct GNUNET_CONTAINER_MultiHashMap *channels_in; + +/** + * Outgoing CADET channels connecting to parents in the tree. + * Group's pub_key_hash -> struct Channel * (multi) + */ +static struct GNUNET_CONTAINER_MultiHashMap *channels_out; + +/** + * Incoming replay requests from CADET. + * Group's pub_key_hash -> + * H(fragment_id, message_id, fragment_offset, flags) -> struct Channel * + */ +static struct GNUNET_CONTAINER_MultiHashMap *replay_req_cadet; + +/** + * Incoming replay requests from clients. + * Group's pub_key_hash -> + * H(fragment_id, message_id, fragment_offset, flags) -> struct GNUNET_SERVICE_Client * + */ +static struct GNUNET_CONTAINER_MultiHashMap *replay_req_client; + + +/** + * Join status of a remote peer. + */ +enum JoinStatus +{ + JOIN_REFUSED = -1, + JOIN_NOT_ASKED = 0, + JOIN_WAITING = 1, + JOIN_ADMITTED = 2, +}; + +enum ChannelDirection +{ + DIR_INCOMING = 0, + DIR_OUTGOING = 1, +}; + + +/** + * Context for a CADET channel. + */ +struct Channel +{ + /** + * Group the channel belongs to. + * + * Only set for outgoing channels. + */ + struct Group *group; + + /** + * CADET channel. + */ + struct GNUNET_CADET_Channel *channel; + + // FIXME: not used + /** + * CADET transmission handle. + */ + struct GNUNET_CADET_TransmitHandle *tmit_handle; + + /** + * Public key of the target group. + */ + struct GNUNET_CRYPTO_EddsaPublicKey group_pub_key; + + /** + * Hash of @a group_pub_key. + */ + struct GNUNET_HashCode group_pub_hash; + + /** + * Public key of the joining member. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey member_pub_key; + + /** + * Remote peer identity. + */ + struct GNUNET_PeerIdentity peer; + + /** + * Current window size, set by cadet_notify_window_change() + */ + int32_t window_size; + + /** + * Is the connection established? + */ + int8_t is_connected; + + /** + * Is the remote peer admitted to the group? + * @see enum JoinStatus + */ + int8_t join_status; + + /** + * Number of messages waiting to be sent to CADET. + */ + uint8_t msgs_pending; + + /** + * Channel direction. + * @see enum ChannelDirection + */ + uint8_t direction; +}; + + +/** + * List of connected clients. + */ +struct ClientList +{ + struct ClientList *prev; + struct ClientList *next; + struct GNUNET_SERVICE_Client *client; +}; + + +/** + * Client context for an origin or member. + */ +struct Group +{ + struct ClientList *clients_head; + struct ClientList *clients_tail; + + /** + * Public key of the group. + */ + struct GNUNET_CRYPTO_EddsaPublicKey pub_key; + + /** + * Hash of @a pub_key. + */ + struct GNUNET_HashCode pub_key_hash; + + /** + * CADET port hash. + */ + struct GNUNET_HashCode cadet_port_hash; + + /** + * Is the client disconnected? #GNUNET_YES or #GNUNET_NO + */ + uint8_t is_disconnected; + + /** + * Is this an origin (#GNUNET_YES), or member (#GNUNET_NO)? + */ + uint8_t is_origin; + + union { + struct Origin *origin; + struct Member *member; + }; +}; + + +/** +* Client context for a group's origin. + */ +struct Origin +{ + struct Group group; + + /** + * Private key of the group. + */ + struct GNUNET_CRYPTO_EddsaPrivateKey priv_key; + + /** + * CADET port. + */ + struct GNUNET_CADET_Port *cadet_port; + + /** + * Last message fragment ID sent to the group. + */ + uint64_t max_fragment_id; +}; + + +/** + * Client context for a group member. + */ +struct Member +{ + struct Group group; + + /** + * Private key of the member. + */ + struct GNUNET_CRYPTO_EcdsaPrivateKey priv_key; + + /** + * Public key of the member. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey pub_key; + + /** + * Hash of @a pub_key. + */ + struct GNUNET_HashCode pub_key_hash; + + /** + * Join request sent to the origin / members. + */ + struct MulticastJoinRequestMessage *join_req; + + /** + * Join decision sent in reply to our request. + * + * Only a positive decision is stored here, in case of a negative decision the + * client is disconnected. + */ + struct MulticastJoinDecisionMessageHeader *join_dcsn; + + /** + * CADET channel to the origin. + */ + struct Channel *origin_channel; + + /** + * Peer identity of origin. + */ + struct GNUNET_PeerIdentity origin; + + /** + * Peer identity of relays (other members to connect). + */ + struct GNUNET_PeerIdentity *relays; + + /** + * Last request fragment ID sent to the origin. + */ + uint64_t max_fragment_id; + + /** + * Number of @a relays. + */ + uint32_t relay_count; +}; + + +/** + * Client context. + */ +struct Client { + struct GNUNET_SERVICE_Client *client; + struct Group *group; +}; + + +struct ReplayRequestKey +{ + uint64_t fragment_id; + uint64_t message_id; + uint64_t fragment_offset; + uint64_t flags; +}; + + +static struct Channel * +cadet_channel_create (struct Group *grp, struct GNUNET_PeerIdentity *peer); + +static void +cadet_channel_destroy (struct Channel *chn); + +static void +client_send_join_decision (struct Member *mem, + const struct MulticastJoinDecisionMessageHeader *hdcsn); + + +/** + * Task run during shutdown. + * + * @param cls unused + */ +static void +shutdown_task (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "shutting down\n"); + if (NULL != cadet) + { + GNUNET_CADET_disconnect (cadet); + cadet = NULL; + } + if (NULL != stats) + { + GNUNET_STATISTICS_destroy (stats, GNUNET_YES); + stats = NULL; + } + /* FIXME: do more clean up here */ +} + + +/** + * Clean up origin data structures after a client disconnected. + */ +static void +cleanup_origin (struct Origin *orig) +{ + struct Group *grp = &orig->group; + GNUNET_CONTAINER_multihashmap_remove (origins, &grp->pub_key_hash, orig); + if (NULL != orig->cadet_port) + { + GNUNET_CADET_close_port (orig->cadet_port); + orig->cadet_port = NULL; + } + GNUNET_free (orig); +} + + +/** + * Clean up member data structures after a client disconnected. + */ +static void +cleanup_member (struct Member *mem) +{ + struct Group *grp = &mem->group; + struct GNUNET_CONTAINER_MultiHashMap * + grp_mem = GNUNET_CONTAINER_multihashmap_get (group_members, + &grp->pub_key_hash); + GNUNET_assert (NULL != grp_mem); + GNUNET_CONTAINER_multihashmap_remove (grp_mem, &mem->pub_key_hash, mem); + + if (0 == GNUNET_CONTAINER_multihashmap_size (grp_mem)) + { + GNUNET_CONTAINER_multihashmap_remove (group_members, &grp->pub_key_hash, + grp_mem); + GNUNET_CONTAINER_multihashmap_destroy (grp_mem); + } + if (NULL != mem->join_dcsn) + { + GNUNET_free (mem->join_dcsn); + mem->join_dcsn = NULL; + } + if (NULL != mem->origin_channel) + { + GNUNET_CADET_channel_destroy (mem->origin_channel->channel); + mem->origin_channel = NULL; + } + GNUNET_CONTAINER_multihashmap_remove (members, &grp->pub_key_hash, mem); + GNUNET_free (mem); +} + + +/** + * Clean up group data structures after a client disconnected. + */ +static void +cleanup_group (struct Group *grp) +{ + (GNUNET_YES == grp->is_origin) + ? cleanup_origin (grp->origin) + : cleanup_member (grp->member); +} + + +void +replay_key_hash (uint64_t fragment_id, uint64_t message_id, + uint64_t fragment_offset, uint64_t flags, + struct GNUNET_HashCode *key_hash) +{ + struct ReplayRequestKey key = { + .fragment_id = fragment_id, + .message_id = message_id, + .fragment_offset = fragment_offset, + .flags = flags, + }; + GNUNET_CRYPTO_hash (&key, sizeof (key), key_hash); +} + + +/** + * Remove channel from replay request hashmap. + * + * @param chn + * Channel to remove. + * + * @return #GNUNET_YES if there are more entries to process, + * #GNUNET_NO when reached end of hashmap. + */ +static int +replay_req_remove_cadet (struct Channel *chn) +{ + if (NULL == chn || NULL == chn->group) + return GNUNET_SYSERR; + + struct GNUNET_CONTAINER_MultiHashMap * + grp_replay_req = GNUNET_CONTAINER_multihashmap_get (replay_req_cadet, + &chn->group->pub_key_hash); + if (NULL == grp_replay_req) + return GNUNET_NO; + + struct GNUNET_CONTAINER_MultiHashMapIterator * + it = GNUNET_CONTAINER_multihashmap_iterator_create (grp_replay_req); + struct GNUNET_HashCode key; + const struct Channel *c; + while (GNUNET_YES + == GNUNET_CONTAINER_multihashmap_iterator_next (it, &key, + (const void **) &c)) + { + if (c == chn) + { + GNUNET_CONTAINER_multihashmap_remove (grp_replay_req, &key, chn); + GNUNET_CONTAINER_multihashmap_iterator_destroy (it); + return GNUNET_YES; + } + } + GNUNET_CONTAINER_multihashmap_iterator_destroy (it); + return GNUNET_NO; +} + + +/** + * Remove client from replay request hashmap. + * + * @param client + * Client to remove. + * + * @return #GNUNET_YES if there are more entries to process, + * #GNUNET_NO when reached end of hashmap. + */ +static int +replay_req_remove_client (struct Group *grp, struct GNUNET_SERVICE_Client *client) +{ + struct GNUNET_CONTAINER_MultiHashMap * + grp_replay_req = GNUNET_CONTAINER_multihashmap_get (replay_req_client, + &grp->pub_key_hash); + if (NULL == grp_replay_req) + return GNUNET_NO; + + struct GNUNET_CONTAINER_MultiHashMapIterator * + it = GNUNET_CONTAINER_multihashmap_iterator_create (grp_replay_req); + struct GNUNET_HashCode key; + const struct GNUNET_SERVICE_Client *c; + while (GNUNET_YES + == GNUNET_CONTAINER_multihashmap_iterator_next (it, &key, + (const void **) &c)) + { + if (c == client) + { + GNUNET_CONTAINER_multihashmap_remove (grp_replay_req, &key, client); + GNUNET_CONTAINER_multihashmap_iterator_destroy (it); + return GNUNET_YES; + } + } + GNUNET_CONTAINER_multihashmap_iterator_destroy (it); + return GNUNET_NO; +} + + +/** + * Send message to a client. + */ +static void +client_send (struct GNUNET_SERVICE_Client *client, + const struct GNUNET_MessageHeader *msg) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "%p Sending message to client.\n", client); + + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_copy (msg); + + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), + env); +} + + +/** + * Send message to all clients connected to the group. + */ +static void +client_send_group_keep_envelope (const struct Group *grp, + struct GNUNET_MQ_Envelope *env) +{ + struct ClientList *cli = grp->clients_head; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "%p Sending message to all clients of the group.\n", + grp); + while (NULL != cli) + { + GNUNET_MQ_send_copy (GNUNET_SERVICE_client_get_mq (cli->client), + env); + cli = cli->next; + } +} + + +/** + * Send message to all clients connected to the group and + * takes care of freeing @env. + */ +static void +client_send_group (const struct Group *grp, + struct GNUNET_MQ_Envelope *env) +{ + client_send_group_keep_envelope (grp, env); + GNUNET_MQ_discard (env); +} + + +/** + * Iterator callback for sending a message to origin clients. + */ +static int +client_send_origin_cb (void *cls, const struct GNUNET_HashCode *pub_key_hash, + void *origin) +{ + struct GNUNET_MQ_Envelope *env = cls; + struct Member *orig = origin; + + client_send_group_keep_envelope (&orig->group, env); + return GNUNET_YES; +} + + +/** + * Iterator callback for sending a message to member clients. + */ +static int +client_send_member_cb (void *cls, const struct GNUNET_HashCode *pub_key_hash, + void *member) +{ + struct GNUNET_MQ_Envelope *env = cls; + struct Member *mem = member; + + if (NULL != mem->join_dcsn) + { /* Only send message to admitted members */ + client_send_group_keep_envelope (&mem->group, env); + } + return GNUNET_YES; +} + + +/** + * Send message to all origin and member clients connected to the group. + * + * @param pub_key_hash + * H(key_pub) of the group. + * @param msg + * Message to send. + */ +static int +client_send_all (struct GNUNET_HashCode *pub_key_hash, + struct GNUNET_MQ_Envelope *env) +{ + int n = 0; + n += GNUNET_CONTAINER_multihashmap_get_multiple (origins, pub_key_hash, + client_send_origin_cb, + (void *) env); + n += GNUNET_CONTAINER_multihashmap_get_multiple (members, pub_key_hash, + client_send_member_cb, + (void *) env); + GNUNET_MQ_discard (env); + return n; +} + + +/** + * Send message to a random origin client or a random member client. + * + * @param grp The group to send @a msg to. + * @param msg Message to send. + */ +static int +client_send_random (struct GNUNET_HashCode *pub_key_hash, + struct GNUNET_MQ_Envelope *env) +{ + int n = 0; + n = GNUNET_CONTAINER_multihashmap_get_random (origins, client_send_origin_cb, + (void *) env); + if (n <= 0) + n = GNUNET_CONTAINER_multihashmap_get_random (members, client_send_member_cb, + (void *) env); + GNUNET_MQ_discard (env); + return n; +} + + +/** + * Send message to all origin clients connected to the group. + * + * @param pub_key_hash + * H(key_pub) of the group. + * @param msg + * Message to send. + */ +static int +client_send_origin (struct GNUNET_HashCode *pub_key_hash, + struct GNUNET_MQ_Envelope *env) +{ + int n = 0; + n += GNUNET_CONTAINER_multihashmap_get_multiple (origins, pub_key_hash, + client_send_origin_cb, + (void *) env); + return n; +} + + +/** + * Send fragment acknowledgement to all clients of the channel. + * + * @param pub_key_hash + * H(key_pub) of the group. + */ +static void +client_send_ack (struct GNUNET_HashCode *pub_key_hash) +{ + struct GNUNET_MQ_Envelope *env; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Sending message ACK to client.\n"); + env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_MULTICAST_FRAGMENT_ACK); + client_send_all (pub_key_hash, env); +} + + +struct CadetTransmitClosure +{ + struct Channel *chn; + const struct GNUNET_MessageHeader *msg; +}; + + +/** + * Send a message to a CADET channel. + * + * @param chn Channel. + * @param msg Message. + */ +static void +cadet_send_channel (struct Channel *chn, const struct GNUNET_MessageHeader *msg) +{ + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_copy (msg); + + GNUNET_MQ_send (GNUNET_CADET_get_mq (chn->channel), env); + + if (0 < chn->window_size) + { + client_send_ack (&chn->group_pub_hash); + } + else + { + chn->msgs_pending++; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "%p Queuing message. Pending messages: %u\n", + chn, chn->msgs_pending); + } +} + + +/** + * Create CADET channel and send a join request. + */ +static void +cadet_send_join_request (struct Member *mem) +{ + mem->origin_channel = cadet_channel_create (&mem->group, &mem->origin); + cadet_send_channel (mem->origin_channel, &mem->join_req->header); + + uint32_t i; + for (i = 0; i < mem->relay_count; i++) + { + struct Channel * + chn = cadet_channel_create (&mem->group, &mem->relays[i]); + cadet_send_channel (chn, &mem->join_req->header); + } +} + + +static int +cadet_send_join_decision_cb (void *cls, + const struct GNUNET_HashCode *group_pub_hash, + void *channel) +{ + const struct MulticastJoinDecisionMessageHeader *hdcsn = cls; + struct Channel *chn = channel; + + const struct MulticastJoinDecisionMessage *dcsn = + (struct MulticastJoinDecisionMessage *) &hdcsn[1]; + + if (0 == memcmp (&hdcsn->member_pub_key, &chn->member_pub_key, sizeof (chn->member_pub_key)) + && 0 == memcmp (&hdcsn->peer, &chn->peer, sizeof (chn->peer))) + { + if (GNUNET_YES == ntohl (dcsn->is_admitted)) + { + chn->join_status = JOIN_ADMITTED; + } + else + { + chn->join_status = JOIN_REFUSED; + } + cadet_send_channel (chn, &hdcsn->header); + return GNUNET_YES; + } + + // return GNUNET_YES to continue the multihashmap_get iteration + return GNUNET_YES; +} + + +/** + * Send join decision to a remote peer. + */ +static void +cadet_send_join_decision (struct Group *grp, + const struct MulticastJoinDecisionMessageHeader *hdcsn) +{ + GNUNET_CONTAINER_multihashmap_get_multiple (channels_in, &grp->pub_key_hash, + &cadet_send_join_decision_cb, + (void *) hdcsn); +} + + +/** + * Iterator callback for sending a message to origin clients. + */ +static int +cadet_send_cb (void *cls, const struct GNUNET_HashCode *pub_key_hash, + void *channel) +{ + const struct GNUNET_MessageHeader *msg = cls; + struct Channel *chn = channel; + if (JOIN_ADMITTED == chn->join_status) + cadet_send_channel (chn, msg); + return GNUNET_YES; +} + + +/** + * Send message to all connected children. + */ +static int +cadet_send_children (struct GNUNET_HashCode *pub_key_hash, + const struct GNUNET_MessageHeader *msg) +{ + int n = 0; + if (channels_in != NULL) + n += GNUNET_CONTAINER_multihashmap_get_multiple (channels_in, pub_key_hash, + cadet_send_cb, (void *) msg); + return n; +} + + +#if 0 // unused as yet +/** + * Send message to all connected parents. + */ +static int +cadet_send_parents (struct GNUNET_HashCode *pub_key_hash, + const struct GNUNET_MessageHeader *msg) +{ + int n = 0; + if (channels_in != NULL) + n += GNUNET_CONTAINER_multihashmap_get_multiple (channels_out, pub_key_hash, + cadet_send_cb, (void *) msg); + return n; +} +#endif + + +/** + * CADET channel connect handler. + * + * @see GNUNET_CADET_ConnectEventHandler() + */ +static void * +cadet_notify_connect (void *cls, + struct GNUNET_CADET_Channel *channel, + const struct GNUNET_PeerIdentity *source) +{ + struct Channel *chn = GNUNET_malloc (sizeof (struct Channel)); + chn->group = cls; + chn->channel = channel; + chn->direction = DIR_INCOMING; + chn->join_status = JOIN_NOT_ASKED; + + GNUNET_CONTAINER_multihashmap_put (channels_in, &chn->group->pub_key_hash, chn, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); + return chn; +} + + +/** + * CADET window size change handler. + * + * @see GNUNET_CADET_WindowSizeEventHandler() + */ +static void +cadet_notify_window_change (void *cls, + const struct GNUNET_CADET_Channel *channel, + int window_size) +{ + struct Channel *chn = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "%p Window size changed to %d. Pending messages: %u\n", + chn, window_size, chn->msgs_pending); + + chn->is_connected = GNUNET_YES; + chn->window_size = (int32_t) window_size; + + for (int i = 0; i < window_size; i++) + { + if (0 < chn->msgs_pending) + { + client_send_ack (&chn->group_pub_hash); + chn->msgs_pending--; + } + else + { + break; + } + } +} + + +/** + * CADET channel disconnect handler. + * + * @see GNUNET_CADET_DisconnectEventHandler() + */ +static void +cadet_notify_disconnect (void *cls, + const struct GNUNET_CADET_Channel *channel) +{ + if (NULL == cls) + return; + + struct Channel *chn = cls; + if (NULL != chn->group) + { + if (GNUNET_NO == chn->group->is_origin) + { + struct Member *mem = (struct Member *) chn->group; + if (chn == mem->origin_channel) + mem->origin_channel = NULL; + } + } + + int ret; + do + { + ret = replay_req_remove_cadet (chn); + } + while (GNUNET_YES == ret); + + GNUNET_free (chn); +} + + +static int +check_cadet_join_request (void *cls, + const struct MulticastJoinRequestMessage *req) +{ + struct Channel *chn = cls; + + if (NULL == chn + || JOIN_NOT_ASKED != chn->join_status) + { + return GNUNET_SYSERR; + } + + uint16_t size = ntohs (req->header.size); + if (size < sizeof (*req)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (ntohl (req->purpose.size) != (size + - sizeof (req->header) + - sizeof (req->reserved) + - sizeof (req->signature))) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_ecdsa_verify (GNUNET_SIGNATURE_PURPOSE_MULTICAST_REQUEST, + &req->purpose, &req->signature, + &req->member_pub_key)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/** + * Incoming join request message from CADET. + */ +static void +handle_cadet_join_request (void *cls, + const struct MulticastJoinRequestMessage *req) +{ + struct Channel *chn = cls; + GNUNET_CADET_receive_done (chn->channel); + + struct GNUNET_HashCode group_pub_hash; + GNUNET_CRYPTO_hash (&req->group_pub_key, sizeof (req->group_pub_key), &group_pub_hash); + chn->group_pub_key = req->group_pub_key; + chn->group_pub_hash = group_pub_hash; + chn->member_pub_key = req->member_pub_key; + chn->peer = req->peer; + chn->join_status = JOIN_WAITING; + + client_send_all (&group_pub_hash, + GNUNET_MQ_msg_copy (&req->header)); +} + + +static int +check_cadet_join_decision (void *cls, + const struct MulticastJoinDecisionMessageHeader *hdcsn) +{ + uint16_t size = ntohs (hdcsn->header.size); + if (size < sizeof (struct MulticastJoinDecisionMessageHeader) + + sizeof (struct MulticastJoinDecisionMessage)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + struct Channel *chn = cls; + if (NULL == chn) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (NULL == chn->group || GNUNET_NO != chn->group->is_origin) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + switch (chn->join_status) + { + case JOIN_REFUSED: + return GNUNET_SYSERR; + + case JOIN_ADMITTED: + return GNUNET_OK; + + case JOIN_NOT_ASKED: + case JOIN_WAITING: + break; + } + + return GNUNET_OK; +} + + +/** + * Incoming join decision message from CADET. + */ +static void +handle_cadet_join_decision (void *cls, + const struct MulticastJoinDecisionMessageHeader *hdcsn) +{ + const struct MulticastJoinDecisionMessage * + dcsn = (const struct MulticastJoinDecisionMessage *) &hdcsn[1]; + + struct Channel *chn = cls; + GNUNET_CADET_receive_done (chn->channel); + + // FIXME: do we need to copy chn->peer or compare it with hdcsn->peer? + struct Member *mem = (struct Member *) chn->group; + client_send_join_decision (mem, hdcsn); + if (GNUNET_YES == ntohl (dcsn->is_admitted)) + { + chn->join_status = JOIN_ADMITTED; + } + else + { + chn->join_status = JOIN_REFUSED; + cadet_channel_destroy (chn); + } +} + + +static int +check_cadet_message (void *cls, + const struct GNUNET_MULTICAST_MessageHeader *msg) +{ + uint16_t size = ntohs (msg->header.size); + if (size < sizeof (*msg)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + struct Channel *chn = cls; + if (NULL == chn) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (ntohl (msg->purpose.size) != (size + - sizeof (msg->header) + - sizeof (msg->hop_counter) + - sizeof (msg->signature))) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_eddsa_verify (GNUNET_SIGNATURE_PURPOSE_MULTICAST_MESSAGE, + &msg->purpose, &msg->signature, + &chn->group_pub_key)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/** + * Incoming multicast message from CADET. + */ +static void +handle_cadet_message (void *cls, + const struct GNUNET_MULTICAST_MessageHeader *msg) +{ + struct Channel *chn = cls; + GNUNET_CADET_receive_done (chn->channel); + client_send_all (&chn->group_pub_hash, + GNUNET_MQ_msg_copy (&msg->header)); +} + + +static int +check_cadet_request (void *cls, + const struct GNUNET_MULTICAST_RequestHeader *req) +{ + uint16_t size = ntohs (req->header.size); + if (size < sizeof (*req)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + struct Channel *chn = cls; + if (NULL == chn) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (ntohl (req->purpose.size) != (size + - sizeof (req->header) + - sizeof (req->member_pub_key) + - sizeof (req->signature))) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + if (GNUNET_OK != + GNUNET_CRYPTO_ecdsa_verify (GNUNET_SIGNATURE_PURPOSE_MULTICAST_REQUEST, + &req->purpose, &req->signature, + &req->member_pub_key)) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/** + * Incoming multicast request message from CADET. + */ +static void +handle_cadet_request (void *cls, + const struct GNUNET_MULTICAST_RequestHeader *req) +{ + struct Channel *chn = cls; + GNUNET_CADET_receive_done (chn->channel); + client_send_origin (&chn->group_pub_hash, + GNUNET_MQ_msg_copy (&req->header)); +} + + +// FIXME: do checks in handle_cadet_replay_request +//static int +//check_cadet_replay_request (void *cls, +// const struct MulticastReplayRequestMessage *req) +//{ +// uint16_t size = ntohs (req->header.size); +// if (size < sizeof (*req)) +// { +// GNUNET_break_op (0); +// return GNUNET_SYSERR; +// } +// +// struct Channel *chn = cls; +// if (NULL == chn) +// { +// GNUNET_break_op (0); +// return GNUNET_SYSERR; +// } +// +// return GNUNET_OK; +//} + + +/** + * Incoming multicast replay request from CADET. + */ +static void +handle_cadet_replay_request (void *cls, + const struct MulticastReplayRequestMessage *req) +{ + struct Channel *chn = cls; + + GNUNET_CADET_receive_done (chn->channel); + + struct MulticastReplayRequestMessage rep = *req; + GNUNET_memcpy (&rep.member_pub_key, &chn->member_pub_key, sizeof (chn->member_pub_key)); + + struct GNUNET_CONTAINER_MultiHashMap * + grp_replay_req = GNUNET_CONTAINER_multihashmap_get (replay_req_cadet, + &chn->group->pub_key_hash); + if (NULL == grp_replay_req) + { + grp_replay_req = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + GNUNET_CONTAINER_multihashmap_put (replay_req_cadet, + &chn->group->pub_key_hash, grp_replay_req, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); + } + struct GNUNET_HashCode key_hash; + replay_key_hash (rep.fragment_id, + rep.message_id, + rep.fragment_offset, + rep.flags, + &key_hash); + GNUNET_CONTAINER_multihashmap_put (grp_replay_req, &key_hash, chn, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); + + client_send_random (&chn->group_pub_hash, + GNUNET_MQ_msg_copy (&rep.header)); +} + + +static int +check_cadet_replay_response (void *cls, + const struct MulticastReplayResponseMessage *res) +{ + struct Channel *chn = cls; + if (NULL == chn) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Incoming multicast replay response from CADET. + */ +static void +handle_cadet_replay_response (void *cls, + const struct MulticastReplayResponseMessage *res) +{ + struct Channel *chn = cls; + GNUNET_CADET_receive_done (chn->channel); + + /* @todo FIXME: got replay error response, send request to other members */ +} + + +static void +group_set_cadet_port_hash (struct Group *grp) +{ + struct CadetPort { + struct GNUNET_CRYPTO_EddsaPublicKey pub_key; + uint32_t app_type; + } port = { + grp->pub_key, + GNUNET_APPLICATION_TYPE_MULTICAST, + }; + GNUNET_CRYPTO_hash (&port, sizeof (port), &grp->cadet_port_hash); +} + + + +/** + * Create new outgoing CADET channel. + * + * @param peer + * Peer to connect to. + * @param group_pub_key + * Public key of group the channel belongs to. + * @param group_pub_hash + * Hash of @a group_pub_key. + * + * @return Channel. + */ +static struct Channel * +cadet_channel_create (struct Group *grp, struct GNUNET_PeerIdentity *peer) +{ + struct Channel *chn = GNUNET_malloc (sizeof (*chn)); + chn->group = grp; + chn->group_pub_key = grp->pub_key; + chn->group_pub_hash = grp->pub_key_hash; + chn->peer = *peer; + chn->direction = DIR_OUTGOING; + chn->is_connected = GNUNET_NO; + chn->join_status = JOIN_WAITING; + + struct GNUNET_MQ_MessageHandler cadet_handlers[] = { + GNUNET_MQ_hd_var_size (cadet_message, + GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE, + struct GNUNET_MULTICAST_MessageHeader, + chn), + + GNUNET_MQ_hd_var_size (cadet_join_decision, + GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_DECISION, + struct MulticastJoinDecisionMessageHeader, + chn), + + GNUNET_MQ_hd_fixed_size (cadet_replay_request, + GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_REQUEST, + struct MulticastReplayRequestMessage, + chn), + + GNUNET_MQ_hd_var_size (cadet_replay_response, + GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE, + struct MulticastReplayResponseMessage, + chn), + + GNUNET_MQ_handler_end () + }; + + chn->channel = GNUNET_CADET_channel_create (cadet, chn, &chn->peer, + &grp->cadet_port_hash, + GNUNET_CADET_OPTION_RELIABLE, + cadet_notify_window_change, + cadet_notify_disconnect, + cadet_handlers); + GNUNET_CONTAINER_multihashmap_put (channels_out, &chn->group_pub_hash, chn, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); + return chn; +} + + +/** + * Destroy outgoing CADET channel. + */ +static void +cadet_channel_destroy (struct Channel *chn) +{ + GNUNET_CADET_channel_destroy (chn->channel); + GNUNET_CONTAINER_multihashmap_remove_all (channels_out, &chn->group_pub_hash); + GNUNET_free (chn); +} + +/** + * Handle a connecting client starting an origin. + */ +static void +handle_client_origin_start (void *cls, + const struct MulticastOriginStartMessage *msg) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + + struct GNUNET_CRYPTO_EddsaPublicKey pub_key; + struct GNUNET_HashCode pub_key_hash; + + GNUNET_CRYPTO_eddsa_key_get_public (&msg->group_key, &pub_key); + GNUNET_CRYPTO_hash (&pub_key, sizeof (pub_key), &pub_key_hash); + + struct Origin * + orig = GNUNET_CONTAINER_multihashmap_get (origins, &pub_key_hash); + struct Group *grp; + + if (NULL == orig) + { + orig = GNUNET_new (struct Origin); + orig->priv_key = msg->group_key; + orig->max_fragment_id = GNUNET_ntohll (msg->max_fragment_id); + + grp = c->group = &orig->group; + grp->origin = orig; + grp->is_origin = GNUNET_YES; + grp->pub_key = pub_key; + grp->pub_key_hash = pub_key_hash; + grp->is_disconnected = GNUNET_NO; + + GNUNET_CONTAINER_multihashmap_put (origins, &grp->pub_key_hash, orig, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); + + group_set_cadet_port_hash (grp); + + struct GNUNET_MQ_MessageHandler cadet_handlers[] = { + GNUNET_MQ_hd_var_size (cadet_message, + GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE, + struct GNUNET_MULTICAST_MessageHeader, + grp), + + GNUNET_MQ_hd_var_size (cadet_request, + GNUNET_MESSAGE_TYPE_MULTICAST_REQUEST, + struct GNUNET_MULTICAST_RequestHeader, + grp), + + GNUNET_MQ_hd_var_size (cadet_join_request, + GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_REQUEST, + struct MulticastJoinRequestMessage, + grp), + + GNUNET_MQ_hd_fixed_size (cadet_replay_request, + GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_REQUEST, + struct MulticastReplayRequestMessage, + grp), + + GNUNET_MQ_hd_var_size (cadet_replay_response, + GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE, + struct MulticastReplayResponseMessage, + grp), + + GNUNET_MQ_handler_end () + }; + + + orig->cadet_port = GNUNET_CADET_open_port (cadet, + &grp->cadet_port_hash, + cadet_notify_connect, + grp, + cadet_notify_window_change, + cadet_notify_disconnect, + cadet_handlers); + } + else + { + grp = &orig->group; + } + + struct ClientList *cl = GNUNET_new (struct ClientList); + cl->client = client; + GNUNET_CONTAINER_DLL_insert (grp->clients_head, grp->clients_tail, cl); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Client connected as origin to group %s.\n", + orig, GNUNET_h2s (&grp->pub_key_hash)); + GNUNET_SERVICE_client_continue (client); +} + + +static int +check_client_member_join (void *cls, + const struct MulticastMemberJoinMessage *msg) +{ + uint16_t msg_size = ntohs (msg->header.size); + struct GNUNET_PeerIdentity *relays = (struct GNUNET_PeerIdentity *) &msg[1]; + uint32_t relay_count = ntohl (msg->relay_count); + + if (0 != relay_count) + { + if (UINT32_MAX / relay_count < sizeof (*relays)){ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "relay_count (%lu) * sizeof (*relays) (%lu) exceeds UINT32_MAX!\n", + (unsigned long)relay_count, + sizeof (*relays)); + return GNUNET_SYSERR; + } + } + uint32_t relay_size = relay_count * sizeof (*relays); + struct GNUNET_MessageHeader *join_msg = NULL; + uint16_t join_msg_size = 0; + if (sizeof (*msg) + relay_size + sizeof (struct GNUNET_MessageHeader) + <= msg_size) + { + join_msg = (struct GNUNET_MessageHeader *) + (((char *) &msg[1]) + relay_size); + join_msg_size = ntohs (join_msg->size); + if (UINT16_MAX - join_msg_size < sizeof (struct MulticastJoinRequestMessage)){ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "join_msg_size (%u) + sizeof (struct MulticastJoinRequestMessage) (%lu) exceeds UINT16_MAX!\n", + (unsigned)join_msg_size, + (unsigned long)sizeof (struct MulticastJoinRequestMessage)); + return GNUNET_SYSERR; + } + } + if (msg_size != (sizeof (*msg) + relay_size + join_msg_size)){ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "msg_size does not match real size of message!\n"); + return GNUNET_SYSERR; + }else{ + return GNUNET_OK; + } +} + + +/** + * Handle a connecting client joining a group. + */ +static void +handle_client_member_join (void *cls, + const struct MulticastMemberJoinMessage *msg) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + + uint16_t msg_size = ntohs (msg->header.size); + + struct GNUNET_CRYPTO_EcdsaPublicKey mem_pub_key; + struct GNUNET_HashCode pub_key_hash, mem_pub_key_hash; + + GNUNET_CRYPTO_ecdsa_key_get_public (&msg->member_key, &mem_pub_key); + GNUNET_CRYPTO_hash (&mem_pub_key, sizeof (mem_pub_key), &mem_pub_key_hash); + GNUNET_CRYPTO_hash (&msg->group_pub_key, sizeof (msg->group_pub_key), &pub_key_hash); + + struct GNUNET_CONTAINER_MultiHashMap * + grp_mem = GNUNET_CONTAINER_multihashmap_get (group_members, &pub_key_hash); + struct Member *mem = NULL; + struct Group *grp; + + if (NULL != grp_mem) + { + mem = GNUNET_CONTAINER_multihashmap_get (grp_mem, &mem_pub_key_hash); + } + + if (NULL == mem) + { + mem = GNUNET_new (struct Member); + mem->origin = msg->origin; + mem->priv_key = msg->member_key; + mem->pub_key = mem_pub_key; + mem->pub_key_hash = mem_pub_key_hash; + mem->max_fragment_id = 0; // FIXME + + grp = c->group = &mem->group; + grp->member = mem; + grp->is_origin = GNUNET_NO; + grp->pub_key = msg->group_pub_key; + grp->pub_key_hash = pub_key_hash; + grp->is_disconnected = GNUNET_NO; + group_set_cadet_port_hash (grp); + + if (NULL == grp_mem) + { + grp_mem = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES); + GNUNET_CONTAINER_multihashmap_put (group_members, &grp->pub_key_hash, grp_mem, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); + } + GNUNET_CONTAINER_multihashmap_put (grp_mem, &mem->pub_key_hash, mem, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); + + // FIXME: should the members hash map have option UNIQUE_FAST? + GNUNET_CONTAINER_multihashmap_put (members, &grp->pub_key_hash, mem, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); + } + else + { + grp = &mem->group; + } + + struct ClientList *cl = GNUNET_new (struct ClientList); + cl->client = client; + GNUNET_CONTAINER_DLL_insert (grp->clients_head, grp->clients_tail, cl); + + char *str = GNUNET_CRYPTO_ecdsa_public_key_to_string (&mem->pub_key); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Client connected to group %s as member %s (%s). size = %d\n", + GNUNET_h2s (&grp->pub_key_hash), + GNUNET_h2s2 (&mem->pub_key_hash), + str, + GNUNET_CONTAINER_multihashmap_size (members)); + GNUNET_free (str); + + if (NULL != mem->join_dcsn) + { /* Already got a join decision, send it to client. */ + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_copy (&mem->join_dcsn->header); + + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), + env); + } + else + { /* First client of the group, send join request. */ + struct GNUNET_PeerIdentity *relays = (struct GNUNET_PeerIdentity *) &msg[1]; + uint32_t relay_count = ntohl (msg->relay_count); + uint16_t relay_size = relay_count * sizeof (*relays); + struct GNUNET_MessageHeader *join_msg = NULL; + uint16_t join_msg_size = 0; + if (sizeof (*msg) + relay_size + sizeof (struct GNUNET_MessageHeader) + <= msg_size) + { + join_msg = (struct GNUNET_MessageHeader *) + (((char *) &msg[1]) + relay_size); + join_msg_size = ntohs (join_msg->size); + } + + uint16_t req_msg_size = sizeof (struct MulticastJoinRequestMessage) + join_msg_size; + struct MulticastJoinRequestMessage * + req = GNUNET_malloc (req_msg_size); + req->header.size = htons (req_msg_size); + req->header.type = htons (GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_REQUEST); + req->group_pub_key = grp->pub_key; + req->peer = this_peer; + GNUNET_CRYPTO_ecdsa_key_get_public (&mem->priv_key, &req->member_pub_key); + if (0 < join_msg_size) + GNUNET_memcpy (&req[1], join_msg, join_msg_size); + + req->member_pub_key = mem->pub_key; + req->purpose.size = htonl (req_msg_size + - sizeof (req->header) + - sizeof (req->reserved) + - sizeof (req->signature)); + req->purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_MULTICAST_REQUEST); + + if (GNUNET_OK != GNUNET_CRYPTO_ecdsa_sign (&mem->priv_key, &req->purpose, + &req->signature)) + { + /* FIXME: handle error */ + GNUNET_assert (0); + } + + if (NULL != mem->join_req) + GNUNET_free (mem->join_req); + mem->join_req = req; + + if (0 == + client_send_origin (&grp->pub_key_hash, + GNUNET_MQ_msg_copy (&mem->join_req->header))) + { /* No local origins, send to remote origin */ + cadet_send_join_request (mem); + } + } + GNUNET_SERVICE_client_continue (client); +} + + +static void +client_send_join_decision (struct Member *mem, + const struct MulticastJoinDecisionMessageHeader *hdcsn) +{ + client_send_group (&mem->group, GNUNET_MQ_msg_copy (&hdcsn->header)); + + const struct MulticastJoinDecisionMessage * + dcsn = (const struct MulticastJoinDecisionMessage *) &hdcsn[1]; + if (GNUNET_YES == ntohl (dcsn->is_admitted)) + { /* Member admitted, store join_decision. */ + uint16_t dcsn_size = ntohs (dcsn->header.size); + mem->join_dcsn = GNUNET_malloc (dcsn_size); + GNUNET_memcpy (mem->join_dcsn, dcsn, dcsn_size); + } + else + { /* Refused entry, but replay would be still possible for past members. */ + } +} + + +static int +check_client_join_decision (void *cls, + const struct MulticastJoinDecisionMessageHeader *hdcsn) +{ + return GNUNET_OK; +} + + +/** + * Join decision from client. + */ +static void +handle_client_join_decision (void *cls, + const struct MulticastJoinDecisionMessageHeader *hdcsn) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Group *grp = c->group; + + if (NULL == grp) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + GNUNET_assert (GNUNET_NO == grp->is_disconnected); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p got join decision from client for group %s..\n", + grp, GNUNET_h2s (&grp->pub_key_hash)); + + struct GNUNET_CONTAINER_MultiHashMap * + grp_mem = GNUNET_CONTAINER_multihashmap_get (group_members, + &grp->pub_key_hash); + struct Member *mem = NULL; + if (NULL != grp_mem) + { + struct GNUNET_HashCode member_key_hash; + GNUNET_CRYPTO_hash (&hdcsn->member_pub_key, sizeof (hdcsn->member_pub_key), + &member_key_hash); + mem = GNUNET_CONTAINER_multihashmap_get (grp_mem, &member_key_hash); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p ..and member %s: %p\n", + grp, GNUNET_h2s (&member_key_hash), mem); + } + + if (NULL != mem) + { /* Found local member */ + client_send_join_decision (mem, hdcsn); + } + else + { /* Look for remote member */ + cadet_send_join_decision (grp, hdcsn); + } + GNUNET_SERVICE_client_continue (client); +} + + +static void +handle_client_part_request (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Group *grp = c->group; + struct GNUNET_MQ_Envelope *env; + + if (NULL == grp) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + GNUNET_assert (GNUNET_NO == grp->is_disconnected); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p got part request from client for group %s.\n", + grp, GNUNET_h2s (&grp->pub_key_hash)); + grp->is_disconnected = GNUNET_YES; + env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_MULTICAST_PART_ACK); + client_send_group (grp, env); + GNUNET_SERVICE_client_continue (client); +} + + +static int +check_client_multicast_message (void *cls, + const struct GNUNET_MULTICAST_MessageHeader *msg) +{ + return GNUNET_OK; +} + + +/** + * Incoming message from a client. + */ +static void +handle_client_multicast_message (void *cls, + const struct GNUNET_MULTICAST_MessageHeader *msg) +{ + // FIXME: what if GNUNET_YES == grp->is_disconnected? Do we allow sending messages? + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Group *grp = c->group; + + if (NULL == grp) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + GNUNET_assert (GNUNET_YES == grp->is_origin); + struct Origin *orig = grp->origin; + + // FIXME: use GNUNET_MQ_msg_copy + /* FIXME: yucky, should use separate message structs for P2P and CS! */ + struct GNUNET_MULTICAST_MessageHeader * + out = (struct GNUNET_MULTICAST_MessageHeader *) GNUNET_copy_message (&msg->header); + out->fragment_id = GNUNET_htonll (++orig->max_fragment_id); + out->purpose.size = htonl (ntohs (out->header.size) + - sizeof (out->header) + - sizeof (out->hop_counter) + - sizeof (out->signature)); + out->purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_MULTICAST_MESSAGE); + + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_sign (&orig->priv_key, &out->purpose, + &out->signature)) + { + GNUNET_assert (0); + } + + client_send_all (&grp->pub_key_hash, GNUNET_MQ_msg_copy (&out->header)); + cadet_send_children (&grp->pub_key_hash, &out->header); + client_send_ack (&grp->pub_key_hash); + GNUNET_free (out); + + GNUNET_SERVICE_client_continue (client); +} + + +static int +check_client_multicast_request (void *cls, + const struct GNUNET_MULTICAST_RequestHeader *req) +{ + return GNUNET_OK; +} + + +/** + * Incoming request from a client. + */ +static void +handle_client_multicast_request (void *cls, + const struct GNUNET_MULTICAST_RequestHeader *req) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Group *grp = c->group; + + if (NULL == grp) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + GNUNET_assert (GNUNET_NO == grp->is_disconnected); + GNUNET_assert (GNUNET_NO == grp->is_origin); + struct Member *mem = grp->member; + + /* FIXME: yucky, should use separate message structs for P2P and CS! */ + struct GNUNET_MULTICAST_RequestHeader * + out = (struct GNUNET_MULTICAST_RequestHeader *) GNUNET_copy_message (&req->header); + out->member_pub_key = mem->pub_key; + out->fragment_id = GNUNET_ntohll (++mem->max_fragment_id); + out->purpose.size = htonl (ntohs (out->header.size) + - sizeof (out->header) + - sizeof (out->member_pub_key) + - sizeof (out->signature)); + out->purpose.purpose = htonl (GNUNET_SIGNATURE_PURPOSE_MULTICAST_REQUEST); + + if (GNUNET_OK != GNUNET_CRYPTO_ecdsa_sign (&mem->priv_key, &out->purpose, + &out->signature)) + { + GNUNET_assert (0); + } + + uint8_t send_ack = GNUNET_YES; + if (0 == + client_send_origin (&grp->pub_key_hash, + GNUNET_MQ_msg_copy (&out->header))) + { /* No local origins, send to remote origin */ + if (NULL != mem->origin_channel) + { + cadet_send_channel (mem->origin_channel, &out->header); + send_ack = GNUNET_NO; + } + else + { + /* FIXME: not yet connected to origin */ + GNUNET_SERVICE_client_drop (client); + GNUNET_free (out); + return; + } + } + if (GNUNET_YES == send_ack) + { + client_send_ack (&grp->pub_key_hash); + } + GNUNET_free (out); + GNUNET_SERVICE_client_continue (client); +} + + +/** + * Incoming replay request from a client. + */ +static void +handle_client_replay_request (void *cls, + const struct MulticastReplayRequestMessage *rep) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Group *grp = c->group; + + if (NULL == grp) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + GNUNET_assert (GNUNET_NO == grp->is_disconnected); + GNUNET_assert (GNUNET_NO == grp->is_origin); + struct Member *mem = grp->member; + + struct GNUNET_CONTAINER_MultiHashMap * + grp_replay_req = GNUNET_CONTAINER_multihashmap_get (replay_req_client, + &grp->pub_key_hash); + if (NULL == grp_replay_req) + { + grp_replay_req = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + GNUNET_CONTAINER_multihashmap_put (replay_req_client, + &grp->pub_key_hash, grp_replay_req, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); + } + + struct GNUNET_HashCode key_hash; + replay_key_hash (rep->fragment_id, rep->message_id, rep->fragment_offset, + rep->flags, &key_hash); + GNUNET_CONTAINER_multihashmap_put (grp_replay_req, &key_hash, client, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); + + if (0 == + client_send_origin (&grp->pub_key_hash, + GNUNET_MQ_msg_copy (&rep->header))) + { /* No local origin, replay from remote members / origin. */ + if (NULL != mem->origin_channel) + { + cadet_send_channel (mem->origin_channel, &rep->header); + } + else + { + /* FIXME: not yet connected to origin */ + + GNUNET_assert (0); + GNUNET_SERVICE_client_drop (client); + return; + } + } + GNUNET_SERVICE_client_continue (client); +} + + +static int +cadet_send_replay_response_cb (void *cls, + const struct GNUNET_HashCode *key_hash, + void *value) +{ + struct Channel *chn = value; + struct GNUNET_MessageHeader *msg = cls; + + cadet_send_channel (chn, msg); + return GNUNET_OK; +} + + +static int +client_send_replay_response_cb (void *cls, + const struct GNUNET_HashCode *key_hash, + void *value) +{ + struct GNUNET_SERVICE_Client *client = value; + struct GNUNET_MessageHeader *msg = cls; + + client_send (client, msg); + return GNUNET_OK; +} + + +static int +check_client_replay_response_end (void *cls, + const struct MulticastReplayResponseMessage *res) +{ + return GNUNET_OK; +} + + +/** + * End of replay response from a client. + */ +static void +handle_client_replay_response_end (void *cls, + const struct MulticastReplayResponseMessage *res) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Group *grp = c->group; + + if (NULL == grp) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + GNUNET_assert (GNUNET_NO == grp->is_disconnected); + + struct GNUNET_HashCode key_hash; + replay_key_hash (res->fragment_id, res->message_id, res->fragment_offset, + res->flags, &key_hash); + + struct GNUNET_CONTAINER_MultiHashMap * + grp_replay_req_cadet = GNUNET_CONTAINER_multihashmap_get (replay_req_cadet, + &grp->pub_key_hash); + if (NULL != grp_replay_req_cadet) + { + GNUNET_CONTAINER_multihashmap_remove_all (grp_replay_req_cadet, &key_hash); + } + struct GNUNET_CONTAINER_MultiHashMap * + grp_replay_req_client = GNUNET_CONTAINER_multihashmap_get (replay_req_client, + &grp->pub_key_hash); + if (NULL != grp_replay_req_client) + { + GNUNET_CONTAINER_multihashmap_remove_all (grp_replay_req_client, &key_hash); + } + GNUNET_SERVICE_client_continue (client); +} + + +static int +check_client_replay_response (void *cls, + const struct MulticastReplayResponseMessage *res) +{ + const struct GNUNET_MessageHeader *msg; + if (GNUNET_MULTICAST_REC_OK == res->error_code) + { + msg = GNUNET_MQ_extract_nested_mh (res); + if (NULL == msg) + { + return GNUNET_SYSERR; + } + } + return GNUNET_OK; +} + + +/** + * Incoming replay response from a client. + * + * Respond with a multicast message on success, or otherwise with an error code. + */ +static void +handle_client_replay_response (void *cls, + const struct MulticastReplayResponseMessage *res) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Group *grp = c->group; + + if (NULL == grp) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + GNUNET_assert (GNUNET_NO == grp->is_disconnected); + + const struct GNUNET_MessageHeader *msg = &res->header; + if (GNUNET_MULTICAST_REC_OK == res->error_code) + { + msg = GNUNET_MQ_extract_nested_mh (res); + } + + struct GNUNET_HashCode key_hash; + replay_key_hash (res->fragment_id, res->message_id, res->fragment_offset, + res->flags, &key_hash); + + struct GNUNET_CONTAINER_MultiHashMap * + grp_replay_req_cadet = GNUNET_CONTAINER_multihashmap_get (replay_req_cadet, + &grp->pub_key_hash); + if (NULL != grp_replay_req_cadet) + { + GNUNET_CONTAINER_multihashmap_get_multiple (grp_replay_req_cadet, &key_hash, + cadet_send_replay_response_cb, + (void *) msg); + } + if (GNUNET_MULTICAST_REC_OK == res->error_code) + { + struct GNUNET_CONTAINER_MultiHashMap * + grp_replay_req_client = GNUNET_CONTAINER_multihashmap_get (replay_req_client, + &grp->pub_key_hash); + if (NULL != grp_replay_req_client) + { + GNUNET_CONTAINER_multihashmap_get_multiple (grp_replay_req_client, &key_hash, + client_send_replay_response_cb, + (void *) msg); + } + } + else + { + handle_client_replay_response_end (c, res); + return; + } + GNUNET_SERVICE_client_continue (client); +} + + +/** + * A new client connected. + * + * @param cls NULL + * @param client client to add + * @param mq message queue for @a client + * @return @a client + */ +static void * +client_notify_connect (void *cls, + struct GNUNET_SERVICE_Client *client, + struct GNUNET_MQ_Handle *mq) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Client connected: %p\n", client); + /* FIXME: send connect ACK */ + + struct Client *c = GNUNET_new (struct Client); + c->client = client; + + return c; +} + + +/** + * Called whenever a client is disconnected. + * Frees our resources associated with that client. + * + * @param cls closure + * @param client identification of the client + * @param app_ctx must match @a client + */ +static void +client_notify_disconnect (void *cls, + struct GNUNET_SERVICE_Client *client, + void *app_ctx) +{ + struct Client *c = app_ctx; + struct Group *grp = c->group; + GNUNET_free (c); + + if (NULL == grp) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%p User context is NULL in client_disconnect()\n", grp); + GNUNET_break (0); + return; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Client (%s) disconnected from group %s\n", + grp, (GNUNET_YES == grp->is_origin) ? "origin" : "member", + GNUNET_h2s (&grp->pub_key_hash)); + + // FIXME (due to protocol change): here we must not remove all clients, + // only the one we were notified about! + struct ClientList *cl = grp->clients_head; + while (NULL != cl) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "iterating clients for group %p\n", + grp); + if (cl->client == client) + { + GNUNET_CONTAINER_DLL_remove (grp->clients_head, grp->clients_tail, cl); + GNUNET_free (cl); + break; + } + cl = cl->next; + } + + while (GNUNET_YES == replay_req_remove_client (grp, client)); + + if (NULL == grp->clients_head) + { /* Last client disconnected. */ + cleanup_group (grp); + } +} + + +/** + * Service started. + * + * @param cls closure + * @param server the initialized server + * @param cfg configuration to use + */ +static void +run (void *cls, + const struct GNUNET_CONFIGURATION_Handle *c, + struct GNUNET_SERVICE_Handle *svc) +{ + cfg = c; + service = svc; + GNUNET_CRYPTO_get_peer_identity (cfg, &this_peer); + + stats = GNUNET_STATISTICS_create ("multicast", cfg); + origins = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES); + members = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES); + group_members = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + channels_in = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES); + channels_out = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES); + replay_req_cadet = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + replay_req_client = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + + cadet = GNUNET_CADET_connect (cfg); + + GNUNET_assert (NULL != cadet); + + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, + NULL); +} + + +/** + * Define "main" method using service macro. + */ +GNUNET_SERVICE_MAIN +("multicast", + GNUNET_SERVICE_OPTION_NONE, + &run, + &client_notify_connect, + &client_notify_disconnect, + NULL, + GNUNET_MQ_hd_fixed_size (client_origin_start, + GNUNET_MESSAGE_TYPE_MULTICAST_ORIGIN_START, + struct MulticastOriginStartMessage, + NULL), + GNUNET_MQ_hd_var_size (client_member_join, + GNUNET_MESSAGE_TYPE_MULTICAST_MEMBER_JOIN, + struct MulticastMemberJoinMessage, + NULL), + GNUNET_MQ_hd_var_size (client_join_decision, + GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_DECISION, + struct MulticastJoinDecisionMessageHeader, + NULL), + GNUNET_MQ_hd_fixed_size (client_part_request, + GNUNET_MESSAGE_TYPE_MULTICAST_PART_REQUEST, + struct GNUNET_MessageHeader, + NULL), + GNUNET_MQ_hd_var_size (client_multicast_message, + GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE, + struct GNUNET_MULTICAST_MessageHeader, + NULL), + GNUNET_MQ_hd_var_size (client_multicast_request, + GNUNET_MESSAGE_TYPE_MULTICAST_REQUEST, + struct GNUNET_MULTICAST_RequestHeader, + NULL), + GNUNET_MQ_hd_fixed_size (client_replay_request, + GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_REQUEST, + struct MulticastReplayRequestMessage, + NULL), + GNUNET_MQ_hd_var_size (client_replay_response, + GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE, + struct MulticastReplayResponseMessage, + NULL), + GNUNET_MQ_hd_var_size (client_replay_response_end, + GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE_END, + struct MulticastReplayResponseMessage, + NULL)); + +/* end of gnunet-service-multicast.c */ diff --git a/src/multicast/multicast.conf.in b/src/multicast/multicast.conf.in new file mode 100644 index 0000000..97a5413 --- /dev/null +++ b/src/multicast/multicast.conf.in @@ -0,0 +1,22 @@ +[multicast] +START_ON_DEMAND = @START_ON_DEMAND@ +BINARY = gnunet-service-multicast + +UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-multicast.sock +UNIX_MATCH_UID = YES +UNIX_MATCH_GID = YES + +@UNIXONLY@PORT = 2109 +HOSTNAME = localhost +ACCEPT_FROM = 127.0.0.1; +ACCEPT_FROM6 = ::1; + +# DISABLE_SOCKET_FORWARDING = NO +# USERNAME = +# MAXBUF = +# TIMEOUT = +# DISABLEV6 = +# BINDTO = +# REJECT_FROM = +# REJECT_FROM6 = +# PREFIX = diff --git a/src/multicast/multicast.h b/src/multicast/multicast.h new file mode 100644 index 0000000..8a3ca14 --- /dev/null +++ b/src/multicast/multicast.h @@ -0,0 +1,303 @@ +/* + This file is part of GNUnet. + Copyright (C) 2012, 2013 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later +*/ + +/** + * @file multicast/multicast.h + * @brief multicast IPC messages + * @author Christian Grothoff + * @author Gabor X Toth + */ +#ifndef MULTICAST_H +#define MULTICAST_H + +#include "platform.h" +#include "gnunet_multicast_service.h" + +GNUNET_NETWORK_STRUCT_BEGIN + + +/** + * Header of a join request sent to the origin or another member. + */ +struct MulticastJoinRequestMessage +{ + /** + * Type: GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_REQUEST + */ + struct GNUNET_MessageHeader header; + + /** + * Always zero. + */ + uint32_t reserved; + + /** + * ECC signature of the rest of the fields of the join request. + * + * Signature must match the public key of the joining member. + */ + struct GNUNET_CRYPTO_EcdsaSignature signature; + + /** + * Purpose for the signature and size of the signed data. + */ + struct GNUNET_CRYPTO_EccSignaturePurpose purpose; + + /** + * Public key of the target group. + */ + struct GNUNET_CRYPTO_EddsaPublicKey group_pub_key; + + /** + * Public key of the joining member. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey member_pub_key; + + /** + * Peer identity of the joining member. + */ + struct GNUNET_PeerIdentity peer; + + /* Followed by struct GNUNET_MessageHeader join_message */ +}; + + +/** + * Header of a join decision message sent to a peer requesting join. + */ +struct MulticastJoinDecisionMessage +{ + /** + * Type: GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_DECISION + */ + struct GNUNET_MessageHeader header; + + /** + * #GNUNET_YES if the peer was admitted + * #GNUNET_NO if entry was refused, + * #GNUNET_SYSERR if the request could not be answered. + */ + int32_t is_admitted; + + /** + * Number of relays given. + */ + uint32_t relay_count; + + /* Followed by relay_count peer identities */ + + /* Followed by the join response message */ +}; + + +/** + * Header added to a struct MulticastJoinDecisionMessage + * when sent between the client and service. + */ +struct MulticastJoinDecisionMessageHeader +{ + /** + * Type: GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_DECISION + */ + struct GNUNET_MessageHeader header; + + /** + * C->S: Peer to send the join decision to. + * S->C: Peer we received the join decision from. + */ + struct GNUNET_PeerIdentity peer; + + /** + * C->S: Public key of the member requesting join. + * S->C: Unused. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey member_pub_key; + + /* Followed by struct MulticastJoinDecisionMessage */ +}; + + +/** + * Message sent from the client to the service to notify the service + * about the result of a membership test. + */ +struct MulticastMembershipTestResultMessage +{ + /** + * Type: GNUNET_MESSAGE_TYPE_MULTICAST_MEMBERSHIP_TEST_RESULT + */ + struct GNUNET_MessageHeader header; + + /** + * Unique ID that identifies the associated membership test. + */ + uint32_t uid; + + /** + * #GNUNET_YES if the peer is a member + * #GNUNET_NO if peer is not a member, + * #GNUNET_SYSERR if the test could not be answered. + */ + int32_t is_admitted; +}; + + +/** + * Message sent from the client to the service OR the service to the + * client asking for a message fragment to be replayed. + */ +struct MulticastReplayRequestMessage +{ + + /** + * The message type should be + * #GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_REQUEST. + */ + struct GNUNET_MessageHeader header; + + /** + * S->C: Public key of the member requesting replay. + * C->S: Unused. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey member_pub_key; + + /** + * ID of the message that is being requested. + */ + uint64_t fragment_id; + + /** + * ID of the message that is being requested. + */ + uint64_t message_id; + + /** + * Offset of the fragment that is being requested. + */ + uint64_t fragment_offset; + + /** + * Additional flags for the request. + */ + uint64_t flags; + + /** + * Replay request ID. + */ + uint32_t uid; +}; + + +/** + * Message sent from the client to the service to give the service + * a replayed message. + */ +struct MulticastReplayResponseMessage +{ + + /** + * Type: GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE + * or GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE_END + */ + struct GNUNET_MessageHeader header; + + /** + * ID of the message that is being requested. + */ + uint64_t fragment_id; + + /** + * ID of the message that is being requested. + */ + uint64_t message_id; + + /** + * Offset of the fragment that is being requested. + */ + uint64_t fragment_offset; + + /** + * Additional flags for the request. + */ + uint64_t flags; + + /** + * An `enum GNUNET_MULTICAST_ReplayErrorCode` identifying issues (in NBO). + */ + int32_t error_code; + + /* followed by replayed message */ +}; + + +/** + * Message sent from the client to the service to notify the service + * about the starting of a multicast group with this peers as its origin. + */ +struct MulticastOriginStartMessage +{ + /** + * Type: GNUNET_MESSAGE_TYPE_MULTICAST_ORIGIN_START + */ + struct GNUNET_MessageHeader header; + + /** + * Always zero. + */ + uint32_t reserved; + + /** + * Private, non-ephemeral key for the multicast group. + */ + struct GNUNET_CRYPTO_EddsaPrivateKey group_key; + + /** + * Last fragment ID sent to the group, used to continue counting fragments if + * we resume operating * a group. + */ + uint64_t max_fragment_id; +}; + + +struct MulticastMemberJoinMessage +{ + /** + * Type: GNUNET_MESSAGE_TYPE_MULTICAST_MEMBER_JOIN + */ + struct GNUNET_MessageHeader header; + + uint32_t relay_count GNUNET_PACKED; + + struct GNUNET_CRYPTO_EddsaPublicKey group_pub_key; + + struct GNUNET_CRYPTO_EcdsaPrivateKey member_key; + + struct GNUNET_PeerIdentity origin; + + /* Followed by struct GNUNET_PeerIdentity relays[relay_count] */ + + /* Followed by struct GNUNET_MessageHeader join_msg */ +}; + + +GNUNET_NETWORK_STRUCT_END + +#endif +/* end of multicast.h */ diff --git a/src/multicast/multicast_api.c b/src/multicast/multicast_api.c new file mode 100644 index 0000000..e5e8302 --- /dev/null +++ b/src/multicast/multicast_api.c @@ -0,0 +1,1399 @@ +/* + This file is part of GNUnet. + Copyright (C) 2012, 2013 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later +*/ + +/** + * @file multicast/multicast_api.c + * @brief Multicast service; implements multicast groups using CADET connections. + * @author Christian Grothoff + * @author Gabor X Toth + */ + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_multicast_service.h" +#include "multicast.h" + +#define LOG(kind,...) GNUNET_log_from (kind, "multicast-api",__VA_ARGS__) + + +/** + * Handle for a request to send a message to all multicast group members + * (from the origin). + */ +struct GNUNET_MULTICAST_OriginTransmitHandle +{ + GNUNET_MULTICAST_OriginTransmitNotify notify; + void *notify_cls; + struct GNUNET_MULTICAST_Origin *origin; + + uint64_t message_id; + uint64_t group_generation; + uint64_t fragment_offset; +}; + + +/** + * Handle for a message to be delivered from a member to the origin. + */ +struct GNUNET_MULTICAST_MemberTransmitHandle +{ + GNUNET_MULTICAST_MemberTransmitNotify notify; + void *notify_cls; + struct GNUNET_MULTICAST_Member *member; + + uint64_t request_id; + uint64_t fragment_offset; +}; + + +struct GNUNET_MULTICAST_Group +{ + /** + * Configuration to use. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Client connection to the service. + */ + struct GNUNET_MQ_Handle *mq; + + /** + * Message to send on connect. + */ + struct GNUNET_MQ_Envelope *connect_env; + + /** + * Time to wait until we try to reconnect on failure. + */ + struct GNUNET_TIME_Relative reconnect_delay; + + /** + * Task for reconnecting when the listener fails. + */ + struct GNUNET_SCHEDULER_Task *reconnect_task; + + GNUNET_MULTICAST_JoinRequestCallback join_req_cb; + GNUNET_MULTICAST_ReplayFragmentCallback replay_frag_cb; + GNUNET_MULTICAST_ReplayMessageCallback replay_msg_cb; + GNUNET_MULTICAST_MessageCallback message_cb; + void *cb_cls; + + /** + * Function called after disconnected from the service. + */ + GNUNET_ContinuationCallback disconnect_cb; + + /** + * Closure for @a disconnect_cb. + */ + void *disconnect_cls; + + /** + * Are we currently transmitting a message? + */ + uint8_t in_transmit; + + /** + * Number of MULTICAST_FRAGMENT_ACK messages we are still waiting for. + */ + uint8_t acks_pending; + + /** + * Is this the origin or a member? + */ + uint8_t is_origin; + + /** + * Is this channel in the process of disconnecting from the service? + * #GNUNET_YES or #GNUNET_NO + */ + uint8_t is_disconnecting; +}; + + +/** + * Handle for the origin of a multicast group. + */ +struct GNUNET_MULTICAST_Origin +{ + struct GNUNET_MULTICAST_Group grp; + struct GNUNET_MULTICAST_OriginTransmitHandle tmit; + + GNUNET_MULTICAST_RequestCallback request_cb; +}; + + +/** + * Handle for a multicast group member. + */ +struct GNUNET_MULTICAST_Member +{ + struct GNUNET_MULTICAST_Group grp; + struct GNUNET_MULTICAST_MemberTransmitHandle tmit; + + GNUNET_MULTICAST_JoinDecisionCallback join_dcsn_cb; + + /** + * Replay fragment -> struct GNUNET_MULTICAST_MemberReplayHandle * + */ + struct GNUNET_CONTAINER_MultiHashMap *replay_reqs; + + uint64_t next_fragment_id; +}; + + +/** + * Handle that identifies a join request. + * + * Used to match calls to #GNUNET_MULTICAST_JoinRequestCallback to the + * corresponding calls to #GNUNET_MULTICAST_join_decision(). + */ +struct GNUNET_MULTICAST_JoinHandle +{ + struct GNUNET_MULTICAST_Group *group; + + /** + * Public key of the member requesting join. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey member_pub_key; + + /** + * Peer identity of the member requesting join. + */ + struct GNUNET_PeerIdentity peer; +}; + + +/** + * Opaque handle to a replay request from the multicast service. + */ +struct GNUNET_MULTICAST_ReplayHandle +{ + struct GNUNET_MULTICAST_Group *grp; + struct MulticastReplayRequestMessage req; +}; + + +/** + * Handle for a replay request. + */ +struct GNUNET_MULTICAST_MemberReplayHandle +{ +}; + + +static void +origin_to_all (struct GNUNET_MULTICAST_Origin *orig); + +static void +member_to_origin (struct GNUNET_MULTICAST_Member *mem); + + +/** + * Check join request message. + */ +static int +check_group_join_request (void *cls, + const struct MulticastJoinRequestMessage *jreq) +{ + uint16_t size = ntohs (jreq->header.size); + + if (sizeof (*jreq) == size) + return GNUNET_OK; + + if (sizeof (*jreq) + sizeof (struct GNUNET_MessageHeader) <= size) + return GNUNET_OK; + + return GNUNET_SYSERR; +} + + +/** + * Receive join request from service. + */ +static void +handle_group_join_request (void *cls, + const struct MulticastJoinRequestMessage *jreq) +{ + struct GNUNET_MULTICAST_Group *grp = cls; + struct GNUNET_MULTICAST_JoinHandle *jh; + const struct GNUNET_MessageHeader *jmsg = NULL; + + if (NULL == grp) + { + GNUNET_break (0); + return; + } + if (NULL == grp->join_req_cb) + return; + + if (sizeof (*jreq) + sizeof (*jmsg) <= ntohs (jreq->header.size)) + jmsg = (const struct GNUNET_MessageHeader *) &jreq[1]; + + jh = GNUNET_malloc (sizeof (*jh)); + jh->group = grp; + jh->member_pub_key = jreq->member_pub_key; + jh->peer = jreq->peer; + grp->join_req_cb (grp->cb_cls, &jreq->member_pub_key, jmsg, jh); + + grp->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS; +} + + +/** + * Check multicast message. + */ +static int +check_group_message (void *cls, + const struct GNUNET_MULTICAST_MessageHeader *mmsg) +{ + return GNUNET_OK; +} + + +/** + * Receive multicast message from service. + */ +static void +handle_group_message (void *cls, + const struct GNUNET_MULTICAST_MessageHeader *mmsg) +{ + struct GNUNET_MULTICAST_Group *grp = cls; + + if (GNUNET_YES == grp->is_disconnecting) + return; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Calling message callback with a message of size %u.\n", + ntohs (mmsg->header.size)); + + if (NULL != grp->message_cb) + grp->message_cb (grp->cb_cls, mmsg); + + grp->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS; +} + + +/** + * Receive message/request fragment acknowledgement from service. + */ +static void +handle_group_fragment_ack (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + struct GNUNET_MULTICAST_Group *grp = cls; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "%p Got fragment ACK. in_transmit=%u, acks_pending=%u\n", + grp, grp->in_transmit, grp->acks_pending); + + if (0 == grp->acks_pending) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "%p Ignoring extraneous fragment ACK.\n", grp); + return; + } + grp->acks_pending--; + + if (GNUNET_YES != grp->in_transmit) + return; + + if (GNUNET_YES == grp->is_origin) + origin_to_all ((struct GNUNET_MULTICAST_Origin *) grp); + else + member_to_origin ((struct GNUNET_MULTICAST_Member *) grp); + + grp->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS; +} + + +/** + * Check unicast request. + */ +static int +check_origin_request (void *cls, + const struct GNUNET_MULTICAST_RequestHeader *req) +{ + return GNUNET_OK; +} + + +/** + * Origin receives unicast request from a member. + */ +static void +handle_origin_request (void *cls, + const struct GNUNET_MULTICAST_RequestHeader *req) +{ + struct GNUNET_MULTICAST_Group *grp; + struct GNUNET_MULTICAST_Origin *orig = cls; + grp = &orig->grp; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Calling request callback with a request of size %u.\n", + ntohs (req->header.size)); + + if (NULL != orig->request_cb) + orig->request_cb (grp->cb_cls, req); + + grp->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS; +} + + +/** + * Receive multicast replay request from service. + */ +static void +handle_group_replay_request (void *cls, + const struct MulticastReplayRequestMessage *rep) + +{ + struct GNUNET_MULTICAST_Group *grp = cls; + + if (GNUNET_YES == grp->is_disconnecting) + return; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got replay request.\n"); + + if (0 != rep->fragment_id) + { + if (NULL != grp->replay_frag_cb) + { + struct GNUNET_MULTICAST_ReplayHandle * rh = GNUNET_malloc (sizeof (*rh)); + rh->grp = grp; + rh->req = *rep; + grp->replay_frag_cb (grp->cb_cls, &rep->member_pub_key, + GNUNET_ntohll (rep->fragment_id), + GNUNET_ntohll (rep->flags), rh); + } + } + else if (0 != rep->message_id) + { + if (NULL != grp->replay_msg_cb) + { + struct GNUNET_MULTICAST_ReplayHandle * rh = GNUNET_malloc (sizeof (*rh)); + rh->grp = grp; + rh->req = *rep; + grp->replay_msg_cb (grp->cb_cls, &rep->member_pub_key, + GNUNET_ntohll (rep->message_id), + GNUNET_ntohll (rep->fragment_offset), + GNUNET_ntohll (rep->flags), rh); + } + } + + grp->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS; +} + + +/** + * Check replay response. + */ +static int +check_member_replay_response (void *cls, + const struct MulticastReplayResponseMessage *res) +{ + uint16_t size = ntohs (res->header.size); + + if (sizeof (*res) == size) + return GNUNET_OK; + + if (sizeof (*res) + sizeof (struct GNUNET_MULTICAST_MessageHeader) <= size) + return GNUNET_OK; + + return GNUNET_SYSERR; +} + + +/** + * Receive replay response from service. + */ +static void +handle_member_replay_response (void *cls, + const struct MulticastReplayResponseMessage *res) +{ + struct GNUNET_MULTICAST_Group *grp; + struct GNUNET_MULTICAST_Member *mem = cls; + grp = &mem->grp; + + if (GNUNET_YES == grp->is_disconnecting) + return; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got replay response.\n"); + + // FIXME: return result +} + + +/** + * Check join decision. + */ +static int +check_member_join_decision (void *cls, + const struct MulticastJoinDecisionMessageHeader *hdcsn) +{ + return GNUNET_OK; // checked in handle below +} + + +/** + * Member receives join decision. + */ +static void +handle_member_join_decision (void *cls, + const struct MulticastJoinDecisionMessageHeader *hdcsn) +{ + struct GNUNET_MULTICAST_Group *grp; + struct GNUNET_MULTICAST_Member *mem = cls; + grp = &mem->grp; + + const struct MulticastJoinDecisionMessage * + dcsn = (const struct MulticastJoinDecisionMessage *) &hdcsn[1]; + + uint16_t dcsn_size = ntohs (dcsn->header.size); + int is_admitted = ntohl (dcsn->is_admitted); + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "%p Member got join decision from multicast: %d\n", + mem, is_admitted); + + const struct GNUNET_MessageHeader *join_resp = NULL; + uint16_t join_resp_size = 0; + + uint16_t relay_count = ntohl (dcsn->relay_count); + const struct GNUNET_PeerIdentity *relays = NULL; + uint16_t relay_size = relay_count * sizeof (*relays); + if (0 < relay_count) + { + if (dcsn_size < sizeof (*dcsn) + relay_size) + { + GNUNET_break_op (0); + is_admitted = GNUNET_SYSERR; + } + else + { + relays = (struct GNUNET_PeerIdentity *) &dcsn[1]; + } + } + + if (sizeof (*dcsn) + relay_size + sizeof (*join_resp) <= dcsn_size) + { + join_resp = (const struct GNUNET_MessageHeader *) ((char *) &dcsn[1] + relay_size); + join_resp_size = ntohs (join_resp->size); + } + if (dcsn_size < sizeof (*dcsn) + relay_size + join_resp_size) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Received invalid join decision message from multicast: %u < %u + %u + %u\n", + dcsn_size , sizeof (*dcsn), relay_size, join_resp_size); + GNUNET_break_op (0); + is_admitted = GNUNET_SYSERR; + } + + if (NULL != mem->join_dcsn_cb) + mem->join_dcsn_cb (grp->cb_cls, is_admitted, &hdcsn->peer, + relay_count, relays, join_resp); + + // FIXME: + //if (GNUNET_YES != is_admitted) + // GNUNET_MULTICAST_member_part (mem); + + grp->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS; +} + + +static void +group_cleanup (struct GNUNET_MULTICAST_Group *grp) +{ + if (NULL != grp->connect_env) + { + GNUNET_MQ_discard (grp->connect_env); + grp->connect_env = NULL; + } + if (NULL != grp->mq) + { + GNUNET_MQ_destroy (grp->mq); + grp->mq = NULL; + } + if (NULL != grp->disconnect_cb) + { + grp->disconnect_cb (grp->disconnect_cls); + grp->disconnect_cb = NULL; + } + GNUNET_free (grp); +} + + +static void +handle_group_part_ack (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + struct GNUNET_MULTICAST_Group *grp = cls; + + group_cleanup (grp); +} + + +/** + * Function to call with the decision made for a join request. + * + * Must be called once and only once in response to an invocation of the + * #GNUNET_MULTICAST_JoinRequestCallback. + * + * @param join + * Join request handle. + * @param is_admitted + * #GNUNET_YES if the join is approved, + * #GNUNET_NO if it is disapproved, + * #GNUNET_SYSERR if we cannot answer the request. + * @param relay_count + * Number of relays given. + * @param relays + * Array of suggested peers that might be useful relays to use + * when joining the multicast group (essentially a list of peers that + * are already part of the multicast group and might thus be willing + * to help with routing). If empty, only this local peer (which must + * be the multicast origin) is a good candidate for building the + * multicast tree. Note that it is unnecessary to specify our own + * peer identity in this array. + * @param join_resp + * Message to send in response to the joining peer; + * can also be used to redirect the peer to a different group at the + * application layer; this response is to be transmitted to the + * peer that issued the request even if admission is denied. + */ +struct GNUNET_MULTICAST_ReplayHandle * +GNUNET_MULTICAST_join_decision (struct GNUNET_MULTICAST_JoinHandle *join, + int is_admitted, + uint16_t relay_count, + const struct GNUNET_PeerIdentity *relays, + const struct GNUNET_MessageHeader *join_resp) +{ + struct GNUNET_MULTICAST_Group *grp = join->group; + uint16_t join_resp_size = (NULL != join_resp) ? ntohs (join_resp->size) : 0; + uint16_t relay_size = relay_count * sizeof (*relays); + + struct MulticastJoinDecisionMessageHeader *hdcsn; + struct MulticastJoinDecisionMessage *dcsn; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (hdcsn, sizeof (*dcsn) + relay_size + join_resp_size, + GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_DECISION); + hdcsn->member_pub_key = join->member_pub_key; + hdcsn->peer = join->peer; + + dcsn = (struct MulticastJoinDecisionMessage *) &hdcsn[1]; + dcsn->header.type = htons (GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_DECISION); + dcsn->header.size = htons (sizeof (*dcsn) + relay_size + join_resp_size); + dcsn->is_admitted = htonl (is_admitted); + dcsn->relay_count = htonl (relay_count); + if (0 < relay_size) + GNUNET_memcpy (&dcsn[1], relays, relay_size); + if (0 < join_resp_size) + GNUNET_memcpy (((char *) &dcsn[1]) + relay_size, join_resp, join_resp_size); + + GNUNET_MQ_send (grp->mq, env); + GNUNET_free (join); + return NULL; +} + + +/** + * Replay a message fragment for the multicast group. + * + * @param rh + * Replay handle identifying which replay operation was requested. + * @param msg + * Replayed message fragment, NULL if not found / an error occurred. + * @param ec + * Error code. See enum GNUNET_MULTICAST_ReplayErrorCode + * If not #GNUNET_MULTICAST_REC_OK, the replay handle is invalidated. + */ +void +GNUNET_MULTICAST_replay_response (struct GNUNET_MULTICAST_ReplayHandle *rh, + const struct GNUNET_MessageHeader *msg, + enum GNUNET_MULTICAST_ReplayErrorCode ec) +{ + uint8_t msg_size = (NULL != msg) ? ntohs (msg->size) : 0; + struct MulticastReplayResponseMessage *res; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (res, msg_size, + GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE); + res->fragment_id = rh->req.fragment_id; + res->message_id = rh->req.message_id; + res->fragment_offset = rh->req.fragment_offset; + res->flags = rh->req.flags; + res->error_code = htonl (ec); + + if (GNUNET_MULTICAST_REC_OK == ec) + { + GNUNET_assert (NULL != msg); + GNUNET_memcpy (&res[1], msg, msg_size); + } + + GNUNET_MQ_send (rh->grp->mq, env); + + if (GNUNET_MULTICAST_REC_OK != ec) + GNUNET_free (rh); +} + + +/** + * Indicate the end of the replay session. + * + * Invalidates the replay handle. + * + * @param rh + * Replay session to end. + */ +void +GNUNET_MULTICAST_replay_response_end (struct GNUNET_MULTICAST_ReplayHandle *rh) +{ + struct MulticastReplayResponseMessage *end; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (end, GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE_END); + + end->fragment_id = rh->req.fragment_id; + end->message_id = rh->req.message_id; + end->fragment_offset = rh->req.fragment_offset; + end->flags = rh->req.flags; + + GNUNET_MQ_send (rh->grp->mq, env); + GNUNET_free (rh); +} + + +/** + * Replay a message for the multicast group. + * + * @param rh + * Replay handle identifying which replay operation was requested. + * @param notify + * Function to call to get the message. + * @param notify_cls + * Closure for @a notify. + */ +void +GNUNET_MULTICAST_replay_response2 (struct GNUNET_MULTICAST_ReplayHandle *rh, + GNUNET_MULTICAST_ReplayTransmitNotify notify, + void *notify_cls) +{ +} + + +static void +origin_connect (struct GNUNET_MULTICAST_Origin *orig); + + +static void +origin_reconnect (void *cls) +{ + origin_connect (cls); +} + + +/** + * Origin client disconnected from service. + * + * Reconnect after backoff period. + */ +static void +origin_disconnected (void *cls, enum GNUNET_MQ_Error error) +{ + struct GNUNET_MULTICAST_Origin *orig = cls; + struct GNUNET_MULTICAST_Group *grp = &orig->grp; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Origin client disconnected (%d), re-connecting\n", + (int) error); + if (NULL != grp->mq) + { + GNUNET_MQ_destroy (grp->mq); + grp->mq = NULL; + } + + grp->reconnect_task = GNUNET_SCHEDULER_add_delayed (grp->reconnect_delay, + origin_reconnect, + orig); + grp->reconnect_delay = GNUNET_TIME_STD_BACKOFF (grp->reconnect_delay); +} + + +/** + * Connect to service as origin. + */ +static void +origin_connect (struct GNUNET_MULTICAST_Origin *orig) +{ + struct GNUNET_MULTICAST_Group *grp = &orig->grp; + + struct GNUNET_MQ_MessageHandler handlers[] = { + GNUNET_MQ_hd_var_size (group_message, + GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE, + struct GNUNET_MULTICAST_MessageHeader, + grp), + GNUNET_MQ_hd_var_size (origin_request, + GNUNET_MESSAGE_TYPE_MULTICAST_REQUEST, + struct GNUNET_MULTICAST_RequestHeader, + orig), + GNUNET_MQ_hd_fixed_size (group_fragment_ack, + GNUNET_MESSAGE_TYPE_MULTICAST_FRAGMENT_ACK, + struct GNUNET_MessageHeader, + grp), + GNUNET_MQ_hd_var_size (group_join_request, + GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_REQUEST, + struct MulticastJoinRequestMessage, + grp), + GNUNET_MQ_hd_fixed_size (group_part_ack, + GNUNET_MESSAGE_TYPE_MULTICAST_PART_ACK, + struct GNUNET_MessageHeader, + grp), + GNUNET_MQ_hd_fixed_size (group_replay_request, + GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_REQUEST, + struct MulticastReplayRequestMessage, + grp), + GNUNET_MQ_handler_end () + }; + + grp->mq = GNUNET_CLIENT_connect (grp->cfg, "multicast", + handlers, origin_disconnected, orig); + GNUNET_assert (NULL != grp->mq); + GNUNET_MQ_send_copy (grp->mq, grp->connect_env); +} + + +/** + * Start a multicast group. + * + * Will advertise the origin in the P2P overlay network under the respective + * public key so that other peer can find this peer to join it. Peers that + * issue GNUNET_MULTICAST_member_join() can then transmit a join request to + * either an existing group member or to the origin. If the joining is + * approved, the member is cleared for @e replay and will begin to receive + * messages transmitted to the group. If joining is disapproved, the failed + * candidate will be given a response. Members in the group can send messages + * to the origin (one at a time). + * + * @param cfg + * Configuration to use. + * @param priv_key + * ECC key that will be used to sign messages for this + * multicast session; public key is used to identify the multicast group; + * @param max_fragment_id + * Maximum fragment ID already sent to the group. + * 0 for a new group. + * @param join_request_cb + * Function called to approve / disapprove joining of a peer. + * @param replay_frag_cb + * Function that can be called to replay a message fragment. + * @param replay_msg_cb + * Function that can be called to replay a message. + * @param request_cb + * Function called with message fragments from group members. + * @param message_cb + * Function called with the message fragments sent to the + * network by GNUNET_MULTICAST_origin_to_all(). These message fragments + * should be stored for answering replay requests later. + * @param cls + * Closure for the various callbacks that follow. + * + * @return Handle for the origin, NULL on error. + */ +struct GNUNET_MULTICAST_Origin * +GNUNET_MULTICAST_origin_start (const struct GNUNET_CONFIGURATION_Handle *cfg, + const struct GNUNET_CRYPTO_EddsaPrivateKey *priv_key, + uint64_t max_fragment_id, + GNUNET_MULTICAST_JoinRequestCallback join_request_cb, + GNUNET_MULTICAST_ReplayFragmentCallback replay_frag_cb, + GNUNET_MULTICAST_ReplayMessageCallback replay_msg_cb, + GNUNET_MULTICAST_RequestCallback request_cb, + GNUNET_MULTICAST_MessageCallback message_cb, + void *cls) +{ + struct GNUNET_MULTICAST_Origin *orig = GNUNET_malloc (sizeof (*orig)); + struct GNUNET_MULTICAST_Group *grp = &orig->grp; + + struct MulticastOriginStartMessage *start; + grp->connect_env = GNUNET_MQ_msg (start, + GNUNET_MESSAGE_TYPE_MULTICAST_ORIGIN_START); + start->max_fragment_id = max_fragment_id; + start->group_key = *priv_key; + + grp->cfg = cfg; + grp->is_origin = GNUNET_YES; + grp->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS; + + grp->cb_cls = cls; + grp->join_req_cb = join_request_cb; + grp->replay_frag_cb = replay_frag_cb; + grp->replay_msg_cb = replay_msg_cb; + grp->message_cb = message_cb; + + orig->request_cb = request_cb; + + origin_connect (orig); + return orig; +} + + +/** + * Stop a multicast group. + * + * @param origin + * Multicast group to stop. + */ +void +GNUNET_MULTICAST_origin_stop (struct GNUNET_MULTICAST_Origin *orig, + GNUNET_ContinuationCallback stop_cb, + void *stop_cls) +{ + struct GNUNET_MULTICAST_Group *grp = &orig->grp; + struct GNUNET_MQ_Envelope *env; + + grp->is_disconnecting = GNUNET_YES; + grp->disconnect_cb = stop_cb; + grp->disconnect_cls = stop_cls; + env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_MULTICAST_PART_REQUEST); + GNUNET_MQ_send (grp->mq, env); +} + + +static void +origin_to_all (struct GNUNET_MULTICAST_Origin *orig) +{ + LOG (GNUNET_ERROR_TYPE_DEBUG, "%p origin_to_all()\n", orig); + struct GNUNET_MULTICAST_Group *grp = &orig->grp; + struct GNUNET_MULTICAST_OriginTransmitHandle *tmit = &orig->tmit; + GNUNET_assert (GNUNET_YES == grp->in_transmit); + + size_t buf_size = GNUNET_MULTICAST_FRAGMENT_MAX_SIZE; + struct GNUNET_MULTICAST_MessageHeader *msg; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (msg, buf_size - sizeof(*msg), + GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE); + + int ret = tmit->notify (tmit->notify_cls, &buf_size, &msg[1]); + + if (! (GNUNET_YES == ret || GNUNET_NO == ret) + || GNUNET_MULTICAST_FRAGMENT_MAX_SIZE < buf_size) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + "%p OriginTransmitNotify() returned error or invalid message size.\n", + orig); + /* FIXME: handle error */ + GNUNET_MQ_discard (env); + return; + } + + if (GNUNET_NO == ret && 0 == buf_size) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "%p OriginTransmitNotify() - transmission paused.\n", orig); + GNUNET_MQ_discard (env); + return; /* Transmission paused. */ + } + + msg->header.size = htons (sizeof (*msg) + buf_size); + msg->message_id = GNUNET_htonll (tmit->message_id); + msg->group_generation = tmit->group_generation; + msg->fragment_offset = GNUNET_htonll (tmit->fragment_offset); + tmit->fragment_offset += sizeof (*msg) + buf_size; + + grp->acks_pending++; + GNUNET_MQ_send (grp->mq, env); + + if (GNUNET_YES == ret) + grp->in_transmit = GNUNET_NO; +} + + +/** + * Send a message to the multicast group. + * + * @param orig + * Handle to the multicast group. + * @param message_id + * Application layer ID for the message. Opaque to multicast. + * @param group_generation + * Group generation of the message. + * Documented in struct GNUNET_MULTICAST_MessageHeader. + * @param notify + * Function to call to get the message. + * @param notify_cls + * Closure for @a notify. + * + * @return Message handle on success, + * NULL on error (i.e. another request is already pending). + */ +struct GNUNET_MULTICAST_OriginTransmitHandle * +GNUNET_MULTICAST_origin_to_all (struct GNUNET_MULTICAST_Origin *orig, + uint64_t message_id, + uint64_t group_generation, + GNUNET_MULTICAST_OriginTransmitNotify notify, + void *notify_cls) +{ + struct GNUNET_MULTICAST_Group *grp = &orig->grp; + if (GNUNET_YES == grp->in_transmit) + return NULL; + grp->in_transmit = GNUNET_YES; + + struct GNUNET_MULTICAST_OriginTransmitHandle *tmit = &orig->tmit; + tmit->origin = orig; + tmit->message_id = message_id; + tmit->fragment_offset = 0; + tmit->group_generation = group_generation; + tmit->notify = notify; + tmit->notify_cls = notify_cls; + + origin_to_all (orig); + return tmit; +} + + +/** + * Resume message transmission to multicast group. + * + * @param th + * Transmission to cancel. + */ +void +GNUNET_MULTICAST_origin_to_all_resume (struct GNUNET_MULTICAST_OriginTransmitHandle *th) +{ + struct GNUNET_MULTICAST_Group *grp = &th->origin->grp; + if (0 != grp->acks_pending || GNUNET_YES != grp->in_transmit) + return; + origin_to_all (th->origin); +} + + +/** + * Cancel request for message transmission to multicast group. + * + * @param th + * Transmission to cancel. + */ +void +GNUNET_MULTICAST_origin_to_all_cancel (struct GNUNET_MULTICAST_OriginTransmitHandle *th) +{ + th->origin->grp.in_transmit = GNUNET_NO; +} + + +static void +member_connect (struct GNUNET_MULTICAST_Member *mem); + + +static void +member_reconnect (void *cls) +{ + member_connect (cls); +} + + +/** + * Member client disconnected from service. + * + * Reconnect after backoff period. + */ +static void +member_disconnected (void *cls, enum GNUNET_MQ_Error error) +{ + struct GNUNET_MULTICAST_Member *mem = cls; + struct GNUNET_MULTICAST_Group *grp = &mem->grp; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Member client disconnected (%d), re-connecting\n", + (int) error); + GNUNET_MQ_destroy (grp->mq); + grp->mq = NULL; + + grp->reconnect_task = GNUNET_SCHEDULER_add_delayed (grp->reconnect_delay, + member_reconnect, + mem); + grp->reconnect_delay = GNUNET_TIME_STD_BACKOFF (grp->reconnect_delay); +} + + +/** + * Connect to service as member. + */ +static void +member_connect (struct GNUNET_MULTICAST_Member *mem) +{ + struct GNUNET_MULTICAST_Group *grp = &mem->grp; + + struct GNUNET_MQ_MessageHandler handlers[] = { + GNUNET_MQ_hd_var_size (group_message, + GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE, + struct GNUNET_MULTICAST_MessageHeader, + grp), + GNUNET_MQ_hd_fixed_size (group_fragment_ack, + GNUNET_MESSAGE_TYPE_MULTICAST_FRAGMENT_ACK, + struct GNUNET_MessageHeader, + grp), + GNUNET_MQ_hd_var_size (group_join_request, + GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_REQUEST, + struct MulticastJoinRequestMessage, + grp), + GNUNET_MQ_hd_var_size (member_join_decision, + GNUNET_MESSAGE_TYPE_MULTICAST_JOIN_DECISION, + struct MulticastJoinDecisionMessageHeader, + mem), + GNUNET_MQ_hd_fixed_size (group_part_ack, + GNUNET_MESSAGE_TYPE_MULTICAST_PART_ACK, + struct GNUNET_MessageHeader, + grp), + GNUNET_MQ_hd_fixed_size (group_replay_request, + GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_REQUEST, + struct MulticastReplayRequestMessage, + grp), + GNUNET_MQ_hd_var_size (member_replay_response, + GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_RESPONSE, + struct MulticastReplayResponseMessage, + mem), + GNUNET_MQ_handler_end () + }; + + grp->mq = GNUNET_CLIENT_connect (grp->cfg, "multicast", + handlers, member_disconnected, mem); + GNUNET_assert (NULL != grp->mq); + GNUNET_MQ_send_copy (grp->mq, grp->connect_env); +} + + +/** + * Join a multicast group. + * + * The entity joining is always the local peer. Further information about the + * candidate can be provided in the @a join_request message. If the join fails, the + * @a message_cb is invoked with a (failure) response and then with NULL. If + * the join succeeds, outstanding (state) messages and ongoing multicast + * messages will be given to the @a message_cb until the member decides to part + * the group. The @a replay_cb function may be called at any time by the + * multicast service to support relaying messages to other members of the group. + * + * @param cfg + * Configuration to use. + * @param group_key + * ECC public key that identifies the group to join. + * @param member_key + * ECC key that identifies the member + * and used to sign requests sent to the origin. + * @param origin + * Peer ID of the origin to send unicast requsets to. If NULL, + * unicast requests are sent back via multiple hops on the reverse path + * of multicast messages. + * @param relay_count + * Number of peers in the @a relays array. + * @param relays + * Peer identities of members of the group, which serve as relays + * and can be used to join the group at. and send the @a join_request to. + * If empty, the @a join_request is sent directly to the @a origin. + * @param join_msg + * Application-dependent join message to be passed to the peer @a origin. + * @param join_request_cb + * Function called to approve / disapprove joining of a peer. + * @param join_decision_cb + * Function called to inform about the join decision. + * @param replay_frag_cb + * Function that can be called to replay message fragments + * this peer already knows from this group. NULL if this + * client is unable to support replay. + * @param replay_msg_cb + * Function that can be called to replay message fragments + * this peer already knows from this group. NULL if this + * client is unable to support replay. + * @param message_cb + * Function to be called for all message fragments we + * receive from the group, excluding those our @a replay_cb + * already has. + * @param cls + * Closure for callbacks. + * + * @return Handle for the member, NULL on error. + */ +struct GNUNET_MULTICAST_Member * +GNUNET_MULTICAST_member_join (const struct GNUNET_CONFIGURATION_Handle *cfg, + const struct GNUNET_CRYPTO_EddsaPublicKey *group_pub_key, + const struct GNUNET_CRYPTO_EcdsaPrivateKey *member_key, + const struct GNUNET_PeerIdentity *origin, + uint16_t relay_count, + const struct GNUNET_PeerIdentity *relays, + const struct GNUNET_MessageHeader *join_msg, + GNUNET_MULTICAST_JoinRequestCallback join_request_cb, + GNUNET_MULTICAST_JoinDecisionCallback join_decision_cb, + GNUNET_MULTICAST_ReplayFragmentCallback replay_frag_cb, + GNUNET_MULTICAST_ReplayMessageCallback replay_msg_cb, + GNUNET_MULTICAST_MessageCallback message_cb, + void *cls) +{ + struct GNUNET_MULTICAST_Member *mem = GNUNET_malloc (sizeof (*mem)); + struct GNUNET_MULTICAST_Group *grp = &mem->grp; + + uint16_t relay_size = relay_count * sizeof (*relays); + uint16_t join_msg_size = (NULL != join_msg) ? ntohs (join_msg->size) : 0; + struct MulticastMemberJoinMessage *join; + grp->connect_env = GNUNET_MQ_msg_extra (join, relay_size + join_msg_size, + GNUNET_MESSAGE_TYPE_MULTICAST_MEMBER_JOIN); + join->group_pub_key = *group_pub_key; + join->member_key = *member_key; + join->origin = *origin; + join->relay_count = ntohl (relay_count); + if (0 < relay_size) + GNUNET_memcpy (&join[1], relays, relay_size); + if (0 < join_msg_size) + GNUNET_memcpy (((char *) &join[1]) + relay_size, join_msg, join_msg_size); + + grp->cfg = cfg; + grp->is_origin = GNUNET_NO; + grp->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS; + + mem->join_dcsn_cb = join_decision_cb; + grp->join_req_cb = join_request_cb; + grp->replay_frag_cb = replay_frag_cb; + grp->replay_msg_cb = replay_msg_cb; + grp->message_cb = message_cb; + grp->cb_cls = cls; + + member_connect (mem); + return mem; +} + + +/** + * Part a multicast group. + * + * Disconnects from all group members and invalidates the @a member handle. + * + * An application-dependent part message can be transmitted beforehand using + * #GNUNET_MULTICAST_member_to_origin()) + * + * @param member + * Membership handle. + */ +void +GNUNET_MULTICAST_member_part (struct GNUNET_MULTICAST_Member *mem, + GNUNET_ContinuationCallback part_cb, + void *part_cls) +{ + struct GNUNET_MULTICAST_Group *grp = &mem->grp; + struct GNUNET_MQ_Envelope *env; + + mem->join_dcsn_cb = NULL; + grp->join_req_cb = NULL; + grp->message_cb = NULL; + grp->replay_msg_cb = NULL; + grp->replay_frag_cb = NULL; + grp->is_disconnecting = GNUNET_YES; + grp->disconnect_cb = part_cb; + grp->disconnect_cls = part_cls; + env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_MULTICAST_PART_REQUEST); + GNUNET_MQ_send (grp->mq, env); +} + + +void +member_replay_request (struct GNUNET_MULTICAST_Member *mem, + uint64_t fragment_id, + uint64_t message_id, + uint64_t fragment_offset, + uint64_t flags) +{ + struct MulticastReplayRequestMessage *rep; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (rep, GNUNET_MESSAGE_TYPE_MULTICAST_REPLAY_REQUEST); + + rep->fragment_id = GNUNET_htonll (fragment_id); + rep->message_id = GNUNET_htonll (message_id); + rep->fragment_offset = GNUNET_htonll (fragment_offset); + rep->flags = GNUNET_htonll (flags); + + GNUNET_MQ_send (mem->grp.mq, env); +} + + +/** + * Request a fragment to be replayed by fragment ID. + * + * Useful if messages below the @e max_known_fragment_id given when joining are + * needed and not known to the client. + * + * @param member + * Membership handle. + * @param fragment_id + * ID of a message fragment that this client would like to see replayed. + * @param flags + * Additional flags for the replay request. + * It is used and defined by GNUNET_MULTICAST_ReplayFragmentCallback + * + * @return Replay request handle. + */ +struct GNUNET_MULTICAST_MemberReplayHandle * +GNUNET_MULTICAST_member_replay_fragment (struct GNUNET_MULTICAST_Member *mem, + uint64_t fragment_id, + uint64_t flags) +{ + member_replay_request (mem, fragment_id, 0, 0, flags); + // FIXME: return something useful + return NULL; +} + + +/** + * Request a message fragment to be replayed. + * + * Useful if messages below the @e max_known_fragment_id given when joining are + * needed and not known to the client. + * + * @param member + * Membership handle. + * @param message_id + * ID of the message this client would like to see replayed. + * @param fragment_offset + * Offset of the fragment within the message to replay. + * @param flags + * Additional flags for the replay request. + * It is used & defined by GNUNET_MULTICAST_ReplayMessageCallback + * + * @return Replay request handle, NULL on error. + */ +struct GNUNET_MULTICAST_MemberReplayHandle * +GNUNET_MULTICAST_member_replay_message (struct GNUNET_MULTICAST_Member *mem, + uint64_t message_id, + uint64_t fragment_offset, + uint64_t flags) +{ + member_replay_request (mem, 0, message_id, fragment_offset, flags); + // FIXME: return something useful + return NULL; +} + + +static void +member_to_origin (struct GNUNET_MULTICAST_Member *mem) +{ + LOG (GNUNET_ERROR_TYPE_DEBUG, "member_to_origin()\n"); + struct GNUNET_MULTICAST_Group *grp = &mem->grp; + struct GNUNET_MULTICAST_MemberTransmitHandle *tmit = &mem->tmit; + GNUNET_assert (GNUNET_YES == grp->in_transmit); + + size_t buf_size = GNUNET_MULTICAST_FRAGMENT_MAX_SIZE; + struct GNUNET_MULTICAST_RequestHeader *req; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (req, buf_size - sizeof(*req), + GNUNET_MESSAGE_TYPE_MULTICAST_REQUEST); + + int ret = tmit->notify (tmit->notify_cls, &buf_size, &req[1]); + + if (! (GNUNET_YES == ret || GNUNET_NO == ret) + || GNUNET_MULTICAST_FRAGMENT_MAX_SIZE < buf_size) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + "MemberTransmitNotify() returned error or invalid message size. " + "ret=%d, buf_size=%u\n", ret, buf_size); + /* FIXME: handle error */ + GNUNET_MQ_discard (env); + return; + } + + if (GNUNET_NO == ret && 0 == buf_size) + { + /* Transmission paused. */ + GNUNET_MQ_discard (env); + return; + } + + req->header.size = htons (sizeof (*req) + buf_size); + req->request_id = GNUNET_htonll (tmit->request_id); + req->fragment_offset = GNUNET_ntohll (tmit->fragment_offset); + tmit->fragment_offset += sizeof (*req) + buf_size; + + GNUNET_MQ_send (grp->mq, env); + + if (GNUNET_YES == ret) + grp->in_transmit = GNUNET_NO; +} + + +/** + * Send a message to the origin of the multicast group. + * + * @param mem + * Membership handle. + * @param request_id + * Application layer ID for the request. Opaque to multicast. + * @param notify + * Callback to call to get the message. + * @param notify_cls + * Closure for @a notify. + * + * @return Handle to cancel request, NULL on error (i.e. request already pending). + */ +struct GNUNET_MULTICAST_MemberTransmitHandle * +GNUNET_MULTICAST_member_to_origin (struct GNUNET_MULTICAST_Member *mem, + uint64_t request_id, + GNUNET_MULTICAST_MemberTransmitNotify notify, + void *notify_cls) +{ + if (GNUNET_YES == mem->grp.in_transmit) + return NULL; + mem->grp.in_transmit = GNUNET_YES; + + struct GNUNET_MULTICAST_MemberTransmitHandle *tmit = &mem->tmit; + tmit->member = mem; + tmit->request_id = request_id; + tmit->fragment_offset = 0; + tmit->notify = notify; + tmit->notify_cls = notify_cls; + + member_to_origin (mem); + return tmit; +} + + +/** + * Resume message transmission to origin. + * + * @param th + * Transmission to cancel. + */ +void +GNUNET_MULTICAST_member_to_origin_resume (struct GNUNET_MULTICAST_MemberTransmitHandle *th) +{ + struct GNUNET_MULTICAST_Group *grp = &th->member->grp; + if (0 != grp->acks_pending || GNUNET_YES != grp->in_transmit) + return; + member_to_origin (th->member); +} + + +/** + * Cancel request for message transmission to origin. + * + * @param th + * Transmission to cancel. + */ +void +GNUNET_MULTICAST_member_to_origin_cancel (struct GNUNET_MULTICAST_MemberTransmitHandle *th) +{ + th->member->grp.in_transmit = GNUNET_NO; +} + + +/* end of multicast_api.c */ diff --git a/src/multicast/test_multicast.c b/src/multicast/test_multicast.c new file mode 100644 index 0000000..70efdcb --- /dev/null +++ b/src/multicast/test_multicast.c @@ -0,0 +1,758 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file multicast/test_multicast.c + * @brief Tests for the Multicast API. + * @author Gabor X Toth + */ + +#include + +#include "platform.h" +#include "gnunet_crypto_lib.h" +#include "gnunet_common.h" +#include "gnunet_util_lib.h" +#include "gnunet_testing_lib.h" +#include "gnunet_multicast_service.h" + +#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30) + +/** + * Return value from 'main'. + */ +static int res; + +/** + * Handle for task for timeout termination. + */ +static struct GNUNET_SCHEDULER_Task * end_badly_task; + +static const struct GNUNET_CONFIGURATION_Handle *cfg; + +struct GNUNET_PeerIdentity this_peer; + +struct GNUNET_MULTICAST_Origin *origin; +struct GNUNET_MULTICAST_Member *member; + +struct GNUNET_CRYPTO_EddsaPrivateKey *group_key; +struct GNUNET_CRYPTO_EddsaPublicKey group_pub_key; + +struct GNUNET_CRYPTO_EcdsaPrivateKey *member_key; +struct GNUNET_CRYPTO_EcdsaPublicKey member_pub_key; + +struct TransmitClosure { + struct GNUNET_MULTICAST_OriginTransmitHandle *orig_tmit; + struct GNUNET_MULTICAST_MemberTransmitHandle *mem_tmit; + char * data[16]; + uint8_t data_delay[16]; + uint8_t data_count; + uint8_t paused; + uint8_t n; +} tmit_cls; + +struct OriginClosure { + uint8_t msgs_expected; + uint8_t n; +} origin_cls; + +struct MemberClosure { + uint8_t msgs_expected; + size_t n; +} member_cls; + +struct GNUNET_MessageHeader *join_req, *join_resp; + +enum +{ + TEST_NONE = 0, + TEST_ORIGIN_START = 1, + TEST_MEMBER_JOIN_REFUSE = 2, + TEST_MEMBER_JOIN_ADMIT = 3, + TEST_ORIGIN_TO_ALL = 4, + TEST_ORIGIN_TO_ALL_RECV = 5, + TEST_MEMBER_TO_ORIGIN = 6, + TEST_MEMBER_REPLAY_ERROR = 7, + TEST_MEMBER_REPLAY_OK = 8, + TEST_MEMBER_PART = 9, + TEST_ORIGIN_STOP = 10, +} test; + +uint64_t replay_fragment_id; +uint64_t replay_flags; + +static void +member_join (int t); + + +/** + * Clean up all resources used. + */ +static void +cleanup () +{ + if (NULL != member) + { + GNUNET_MULTICAST_member_part (member, NULL, NULL); + member = NULL; + } + if (NULL != origin) + { + GNUNET_MULTICAST_origin_stop (origin, NULL, NULL); + origin = NULL; + } +} + + +/** + * Terminate the test case (failure). + * + * @param cls NULL + */ +static void +end_badly (void *cls) +{ + res = 1; + cleanup (); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Test FAILED.\n"); +} + + +/** + * Terminate the test case (success). + * + * @param cls NULL + */ +static void +end_normally (void *cls) +{ + res = 0; + cleanup (); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Test PASSED.\n"); +} + + +/** + * Finish the test case (successfully). + */ +static void +end () +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Ending tests.\n"); + + if (end_badly_task != NULL) + { + GNUNET_SCHEDULER_cancel (end_badly_task); + end_badly_task = NULL; + } + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MILLISECONDS, + &end_normally, NULL); +} + + +static void +tmit_resume (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Transmission resumed.\n"); + struct TransmitClosure *tmit = cls; + if (NULL != tmit->orig_tmit) + GNUNET_MULTICAST_origin_to_all_resume (tmit->orig_tmit); + else if (NULL != tmit->mem_tmit) + GNUNET_MULTICAST_member_to_origin_resume (tmit->mem_tmit); +} + + +static int +tmit_notify (void *cls, size_t *data_size, void *data) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Test #%u: origin_tmit_notify()\n", test); + struct TransmitClosure *tmit = cls; + + if (0 == tmit->data_count) + { + *data_size = 0; + return GNUNET_YES; + } + + uint16_t size = strlen (tmit->data[tmit->n]); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmit notify data: %u bytes available, processing fragment %u/%u (size %u).\n", + (unsigned int) *data_size, + tmit->n + 1, + tmit->data_count, + size); + if (*data_size < size) + { + *data_size = 0; + GNUNET_assert (0); + return GNUNET_SYSERR; + } + + if (GNUNET_YES != tmit->paused && 0 < tmit->data_delay[tmit->n]) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Transmission paused.\n"); + tmit->paused = GNUNET_YES; + GNUNET_SCHEDULER_add_delayed ( + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, + tmit->data_delay[tmit->n]), + tmit_resume, tmit); + *data_size = 0; + return GNUNET_NO; + } + tmit->paused = GNUNET_NO; + + *data_size = size; + GNUNET_memcpy (data, tmit->data[tmit->n], size); + + return ++tmit->n < tmit->data_count ? GNUNET_NO : GNUNET_YES; +} + + +static void +member_recv_join_request (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *member_key, + const struct GNUNET_MessageHeader *join_msg, + struct GNUNET_MULTICAST_JoinHandle *jh) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: member_recv_join_request()\n", test); +} + + +static void +origin_stopped (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: origin_stopped()\n", test); + end (); +} + + +static void +schedule_origin_stop (void *cls) +{ + test = TEST_ORIGIN_STOP; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: origin_stop()\n", test); + GNUNET_MULTICAST_origin_stop (origin, origin_stopped, NULL); + origin = NULL; +} + + +static void +member_parted (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: member_parted()\n", test); + member = NULL; + + switch (test) + { + case TEST_MEMBER_JOIN_REFUSE: + // Test 3 starts here + member_join (TEST_MEMBER_JOIN_ADMIT); + break; + + case TEST_MEMBER_PART: + GNUNET_SCHEDULER_add_now (&schedule_origin_stop, NULL); + break; + + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid test #%d in member_parted()\n", test); + GNUNET_assert (0); + } +} + + +static void +schedule_member_part (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: schedule_member_part()\n", test); + GNUNET_MULTICAST_member_part (member, member_parted, NULL); +} + + +static void +member_part () +{ + test = TEST_MEMBER_PART; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: member_part()\n", test); + // Test 10 starts here + GNUNET_SCHEDULER_add_now (&schedule_member_part, NULL); +} + + +static void +member_replay_ok () +{ + // Execution of test 8 here + test = TEST_MEMBER_REPLAY_OK; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: member_replay_ok()\n", test); + replay_fragment_id = 1; + replay_flags = 1 | 1<<11; + GNUNET_MULTICAST_member_replay_fragment (member, replay_fragment_id, + replay_flags); +} + + +static void +member_replay_error () +{ + test = TEST_MEMBER_REPLAY_ERROR; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: member_replay_error()\n", test); + replay_fragment_id = 1234; + replay_flags = 11 | 1<<11; + GNUNET_MULTICAST_member_replay_fragment (member, replay_fragment_id, + replay_flags); +} + + +static void +origin_recv_replay_msg (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *member_key, + uint64_t message_id, + uint64_t fragment_offset, + uint64_t flags, + struct GNUNET_MULTICAST_ReplayHandle *rh) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: origin_recv_replay_msg()\n", test); + GNUNET_assert (0); +} + + +static void +member_recv_replay_msg (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *member_key, + uint64_t message_id, + uint64_t fragment_offset, + uint64_t flags, + struct GNUNET_MULTICAST_ReplayHandle *rh) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: member_recv_replay_msg()\n", test); + GNUNET_assert (0); +} + + +static void +origin_recv_replay_frag (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *member_key, + uint64_t fragment_id, + uint64_t flags, + struct GNUNET_MULTICAST_ReplayHandle *rh) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: origin_recv_replay_frag()" + " - fragment_id=%" PRIu64 " flags=%" PRIu64 "\n", + test, fragment_id, flags); + GNUNET_assert (replay_fragment_id == fragment_id && replay_flags == flags); + switch (test) + { + case TEST_MEMBER_REPLAY_ERROR: + // Test 8 starts here + GNUNET_MULTICAST_replay_response (rh, NULL, GNUNET_SYSERR); + member_replay_ok (); + break; + + case TEST_MEMBER_REPLAY_OK: + { + struct GNUNET_MULTICAST_MessageHeader mmsg = { + .header = { + .type = htons (GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE), + .size = htons (sizeof (mmsg)), + }, + .fragment_id = GNUNET_htonll (1), + .message_id = GNUNET_htonll (1), + .fragment_offset = 0, + .group_generation = GNUNET_htonll (1), + .flags = 0, + }; + member_cls.n = 0; + member_cls.msgs_expected = 1; + GNUNET_MULTICAST_replay_response (rh, &mmsg.header, GNUNET_MULTICAST_REC_OK); + GNUNET_MULTICAST_replay_response_end (rh); + break; + } + + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid test #%d in origin_recv_replay_frag()\n", test); + GNUNET_assert (0); + } +} + + +static void +member_recv_replay_frag (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *member_key, + uint64_t fragment_id, + uint64_t flags, + struct GNUNET_MULTICAST_ReplayHandle *rh) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: member_recv_replay_frag()\n", test); + GNUNET_assert (0); +} + + +static void +origin_recv_request (void *cls, + const struct GNUNET_MULTICAST_RequestHeader *req) +{ + struct OriginClosure *ocls = cls; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: origin_recv_request()\n", test); + if (++ocls->n != ocls->msgs_expected) + return; + + GNUNET_assert (0 == memcmp (&req->member_pub_key, + &member_pub_key, sizeof (member_pub_key))); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Test #%u: verify message content, take first 3 bytes: %.3s\n", + test, (char *)&req[1]); + GNUNET_assert (0 == memcmp (&req[1], "abc", 3)); + + // Test 7 starts here + member_replay_error (); +} + + +static void +member_to_origin () +{ + test = TEST_MEMBER_TO_ORIGIN; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: member_to_origin()\n", test); + + struct TransmitClosure *tmit = &tmit_cls; + *tmit = (struct TransmitClosure) {}; + tmit->data[0] = "abc def"; + tmit->data[1] = "ghi jkl mno"; + tmit->data_delay[1] = 2; + tmit->data[2] = "pqr stuw xyz"; + tmit->data_count = 3; + + origin_cls.n = 0; + origin_cls.msgs_expected = 1; + + tmit->mem_tmit = GNUNET_MULTICAST_member_to_origin (member, 1, + tmit_notify, tmit); +} + + +static void +member_recv_message (void *cls, + const struct GNUNET_MULTICAST_MessageHeader *msg) +{ + struct MemberClosure *mcls = cls; + + // Test 5 starts here after message has been received from origin + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Test #%u: member_recv_message() %u/%u\n", + test, + (unsigned int) (mcls->n + 1), + mcls->msgs_expected); + if (++mcls->n != mcls->msgs_expected) + return; + + // FIXME: check message content + + switch (test) + { + case TEST_ORIGIN_TO_ALL: + test = TEST_ORIGIN_TO_ALL_RECV; + break; + + case TEST_ORIGIN_TO_ALL_RECV: + // Test 6 starts here + member_to_origin (); + break; + + case TEST_MEMBER_REPLAY_OK: + // Test 9 starts here + GNUNET_assert (replay_fragment_id == GNUNET_ntohll (msg->fragment_id)); + member_part (); + break; + + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid test #%d in origin_recv_message()\n", test); + GNUNET_assert (0); + } +} + + +static void +origin_recv_message (void *cls, + const struct GNUNET_MULTICAST_MessageHeader *msg) +{ + struct OriginClosure *ocls = cls; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: origin_recv_message() %u/%u\n", + test, ocls->n + 1, ocls->msgs_expected); + if (++ocls->n != ocls->msgs_expected) + return; + + // FIXME: check message content + + switch (test) + { + case TEST_ORIGIN_TO_ALL: + // Prepare to execute test 5 + test = TEST_ORIGIN_TO_ALL_RECV; + break; + + case TEST_ORIGIN_TO_ALL_RECV: + // Test 6 starts here + member_to_origin (); + break; + + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid test #%d in origin_recv_message()\n", test); + GNUNET_assert (0); + } +} + + +static void +origin_to_all () +{ + test = TEST_ORIGIN_TO_ALL; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: origin_to_all()\n", test); + + struct TransmitClosure *tmit = &tmit_cls; + *tmit = (struct TransmitClosure) {}; + tmit->data[0] = "ABC DEF"; + tmit->data[1] = GNUNET_malloc (GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD + 1); + uint16_t i; + for (i = 0; i < GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD; i++) + tmit->data[1][i] = (0 == i % 10000) ? '0' + i / 10000 : '_'; + tmit->data[2] = "GHI JKL MNO"; + tmit->data_delay[2] = 2; + tmit->data[3] = "PQR STUW XYZ"; + tmit->data_count = 4; + + origin_cls.n = member_cls.n = 0; + origin_cls.msgs_expected = member_cls.msgs_expected = tmit->data_count; + + tmit->orig_tmit = GNUNET_MULTICAST_origin_to_all (origin, 1, 1, + tmit_notify, tmit); +} + + +static void +member_recv_join_decision (void *cls, + int is_admitted, + const struct GNUNET_PeerIdentity *peer, + uint16_t relay_count, + const struct GNUNET_PeerIdentity *relays, + const struct GNUNET_MessageHeader *join_msg) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: member_recv_join_decision() - is_admitted: %d\n", + test, is_admitted); + + GNUNET_assert (join_msg->size == join_resp->size); + GNUNET_assert (join_msg->type == join_resp->type); + GNUNET_assert (0 == memcmp (join_msg, join_resp, ntohs (join_resp->size))); + + switch (test) + { + case TEST_MEMBER_JOIN_REFUSE: + GNUNET_assert (0 == relay_count); + // Test 3 starts here + GNUNET_SCHEDULER_add_now (&schedule_member_part, NULL); + break; + + case TEST_MEMBER_JOIN_ADMIT: + GNUNET_assert (1 == relay_count); + GNUNET_assert (0 == memcmp (relays, &this_peer, sizeof (this_peer))); + // Test 4 starts here + origin_to_all (); + break; + + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid test #%d in member_recv_join_decision()\n", test); + GNUNET_assert (0); + } +} + +/** + * Test: origin receives join request + */ +static void +origin_recv_join_request (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *mem_key, + const struct GNUNET_MessageHeader *join_msg, + struct GNUNET_MULTICAST_JoinHandle *jh) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: origin_recv_join_request()\n", test); + + GNUNET_assert (0 == memcmp (mem_key, &member_pub_key, sizeof (member_pub_key))); + GNUNET_assert (join_msg->size == join_req->size); + GNUNET_assert (join_msg->type == join_req->type); + GNUNET_assert (0 == memcmp (join_msg, join_req, ntohs (join_req->size))); + + char data[] = "here's the decision"; + uint8_t data_size = strlen (data) + 1; + join_resp = GNUNET_malloc (sizeof (join_resp) + data_size); + join_resp->size = htons (sizeof (join_resp) + data_size); + join_resp->type = htons (456); + GNUNET_memcpy (&join_resp[1], data, data_size); + + switch (test) + { + case TEST_MEMBER_JOIN_REFUSE: + // Test 3 starts here + GNUNET_MULTICAST_join_decision (jh, GNUNET_NO, 0, NULL, join_resp); + break; + + case TEST_MEMBER_JOIN_ADMIT: + // Test 3 is running + GNUNET_MULTICAST_join_decision (jh, GNUNET_YES, 1, &this_peer, join_resp); + break; + + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Invalid test #%d in origin_recv_join_request()\n", test); + GNUNET_assert (0); + break; + } +} + +/** + * Test: member joins multicast group + */ +static void +member_join (int t) +{ + test = t; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: member_join()\n", test); + + member_key = GNUNET_CRYPTO_ecdsa_key_create (); + GNUNET_CRYPTO_ecdsa_key_get_public (member_key, &member_pub_key); + + if (NULL != join_req) + GNUNET_free (join_req); + + char data[] = "let me in!"; + uint8_t data_size = strlen (data) + 1; + join_req = GNUNET_malloc (sizeof (join_req) + data_size); + join_req->size = htons (sizeof (join_req) + data_size); + join_req->type = htons (123); + GNUNET_memcpy (&join_req[1], data, data_size); + + member = GNUNET_MULTICAST_member_join (cfg, &group_pub_key, member_key, + &this_peer, 1, &this_peer, join_req, + member_recv_join_request, + member_recv_join_decision, + member_recv_replay_frag, + member_recv_replay_msg, + member_recv_message, + &member_cls); +} + +/** + * Test: Start a multicast group as origin + */ +static void +origin_start () +{ + test = TEST_ORIGIN_START; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%u: origin_start()\n", test); + + group_key = GNUNET_CRYPTO_eddsa_key_create (); + GNUNET_CRYPTO_eddsa_key_get_public (group_key, &group_pub_key); + + origin = GNUNET_MULTICAST_origin_start (cfg, group_key, 0, + origin_recv_join_request, + origin_recv_replay_frag, + origin_recv_replay_msg, + origin_recv_request, + origin_recv_message, + &origin_cls); + // Test 2 starts here + member_join (TEST_MEMBER_JOIN_REFUSE); +} + + +/** + * Main function of the test, run from scheduler. + * + * @param cls NULL + * @param cfg configuration we use (also to connect to Multicast service) + * @param peer handle to access more of the peer (not used) + */ +static void +#if DEBUG_TEST_MULTICAST +run (void *cls, + char *const *args, + const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *c) +#else +run (void *cls, + const struct GNUNET_CONFIGURATION_Handle *c, + struct GNUNET_TESTING_Peer *peer) +#endif +{ + cfg = c; + end_badly_task = GNUNET_SCHEDULER_add_delayed (TIMEOUT, + &end_badly, NULL); + GNUNET_CRYPTO_get_peer_identity (cfg, &this_peer); + + // Test 1 starts here + origin_start (); +} + + +int +main (int argc, char *argv[]) +{ + res = 1; +#if DEBUG_TEST_MULTICAST + const struct GNUNET_GETOPT_CommandLineOption opts[] = { + GNUNET_GETOPT_OPTION_END + }; + if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv, "test-multicast", + "test-multicast [options]", + opts, &run, NULL)) + return 1; +#else + if (0 != GNUNET_TESTING_peer_run ("test-multicast", "test_multicast.conf", &run, NULL)) + return 1; +#endif + return res; +} + +/* end of test_multicast.c */ diff --git a/src/multicast/test_multicast.conf b/src/multicast/test_multicast.conf new file mode 100644 index 0000000..b2f1a76 --- /dev/null +++ b/src/multicast/test_multicast.conf @@ -0,0 +1,56 @@ +[testbed] +HOSTNAME = localhost + +[arm] +GLOBAL_POSTFIX=-L ERROR + +[multicast] +#PREFIX = tmux new-window gdb -x ./cmd.gdb --args +#PREFIX = valgrind --leak-check=full +UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-multicast.sock + +[vpn] +START_ON_DEMAND = NO + +[peerinfo] +# Do not use shipped gnunet HELLOs +USE_INCLUDED_HELLOS = NO + +# Option to disable all disk IO; only useful for testbed runs +# (large-scale experiments); disables persistence of HELLOs! +NO_IO = YES + +[hostlist] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[nat] +ENABLE_UPNP = NO + +[fs] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[vpn] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[revocation] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[gns] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[namestore] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[namecache] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[topology] +IMMEDIATE_START = NO +START_ON_DEMAND = NO diff --git a/src/multicast/test_multicast_2peers.c b/src/multicast/test_multicast_2peers.c new file mode 100644 index 0000000..ea99602 --- /dev/null +++ b/src/multicast/test_multicast_2peers.c @@ -0,0 +1,520 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file multicast/test_multicast_2peers.c + * @brief Tests for the Multicast API with two peers doing the ping + * pong test. + * @author xrs + */ + +#include + +#include "platform.h" +#include "gnunet_crypto_lib.h" +#include "gnunet_common.h" +#include "gnunet_util_lib.h" +#include "gnunet_testbed_service.h" +#include "gnunet_multicast_service.h" + +#define NUM_PEERS 2 + +static struct GNUNET_TESTBED_Operation *op0; +static struct GNUNET_TESTBED_Operation *op1; +static struct GNUNET_TESTBED_Operation *pi_op0; +static struct GNUNET_TESTBED_Operation *pi_op1; + +static struct GNUNET_TESTBED_Peer **peers; +const struct GNUNET_PeerIdentity *peer_id[2]; + +static struct GNUNET_SCHEDULER_Task *timeout_tid; + +static struct GNUNET_MULTICAST_Origin *origin; +static struct GNUNET_MULTICAST_Member *member; + +struct GNUNET_CRYPTO_EddsaPrivateKey *group_key; +struct GNUNET_CRYPTO_EddsaPublicKey group_pub_key; + +struct GNUNET_CRYPTO_EcdsaPrivateKey *member_key; +struct GNUNET_CRYPTO_EcdsaPublicKey member_pub_key; + +/** + * Global result for testcase. + */ +static int result; + + +/** + * Function run on CTRL-C or shutdown (i.e. success/timeout/etc.). + * Cleans up. + */ +static void +shutdown_task (void *cls) +{ + if (NULL != op0) + { + GNUNET_TESTBED_operation_done (op0); + op0 = NULL; + } + if (NULL != op1) + { + GNUNET_TESTBED_operation_done (op1); + op1 = NULL; + } + if (NULL != pi_op0) + { + GNUNET_TESTBED_operation_done (pi_op0); + pi_op0 = NULL; + } + if (NULL != pi_op1) + { + GNUNET_TESTBED_operation_done (pi_op1); + pi_op1 = NULL; + } + if (NULL != timeout_tid) + { + GNUNET_SCHEDULER_cancel (timeout_tid); + timeout_tid = NULL; + } +} + + +static void +timeout_task (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Timeout!\n"); + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_shutdown (); +} + + +static void +member_join_request (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key, + const struct GNUNET_MessageHeader *join_msg, + struct GNUNET_MULTICAST_JoinHandle *jh) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Member sent a join request.\n"); + +} + + +static int +notify (void *cls, + size_t *data_size, + void *data) +{ + + char text[] = "ping"; + *data_size = strlen(text)+1; + GNUNET_memcpy(data, text, *data_size); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Member sents message to origin: %s\n", text); + + return GNUNET_YES; +} + + +static void +member_join_decision (void *cls, + int is_admitted, + const struct GNUNET_PeerIdentity *peer, + uint16_t relay_count, + const struct GNUNET_PeerIdentity *relays, + const struct GNUNET_MessageHeader *join_msg) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Member received a decision from origin: %s\n", + (GNUNET_YES == is_admitted) + ? "accepted" + : "rejected"); + + if (GNUNET_YES == is_admitted) + { + struct GNUNET_MULTICAST_MemberTransmitHandle *req; + + // FIXME: move to MQ-style API! + req = GNUNET_MULTICAST_member_to_origin (member, + 0, + ¬ify, + NULL); + } +} + + +static void +member_message (void *cls, + const struct GNUNET_MULTICAST_MessageHeader *msg) +{ + if (0 != strncmp ("pong", (char *)&msg[1], 4)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "member did not receive pong\n"); + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_shutdown (); + } + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "member receives: %s\n", (char *)&msg[1]); + + // Testcase ends here. + result = GNUNET_YES; + GNUNET_SCHEDULER_shutdown (); +} + + +static void +origin_join_request (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key, + const struct GNUNET_MessageHeader *join_msg, + struct GNUNET_MULTICAST_JoinHandle *jh) +{ + struct GNUNET_MessageHeader *join_resp; + + uint8_t data_size = ntohs (join_msg->size); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "origin got a join request...\n"); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "origin receives: '%s'\n", (char *)&join_msg[1]); + + const char data[] = "Come in!"; + data_size = strlen (data) + 1; + join_resp = GNUNET_malloc (sizeof (join_resp) + data_size); + join_resp->size = htons (sizeof (join_resp) + data_size); + join_resp->type = htons (123); + GNUNET_memcpy (&join_resp[1], data, data_size); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "origin sends: '%s'\n", data); + + GNUNET_MULTICAST_join_decision (jh, + GNUNET_YES, + 0, + NULL, + join_resp); + GNUNET_free (join_resp); + result = GNUNET_OK; +} + + +int +origin_notify (void *cls, + size_t *data_size, + void *data) +{ + char text[] = "pong"; + + *data_size = strlen(text)+1; + GNUNET_memcpy (data, + text, + *data_size); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "origin sends (to all): %s\n", text); + + return GNUNET_YES; +} + + +static void +origin_request (void *cls, + const struct GNUNET_MULTICAST_RequestHeader *req) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "origin receives: %s\n", (char *)&req[1]); + + if (0 != strncmp ("ping", (char *)&req[1], 4)) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "origin didn't reveice a correct request"); + + GNUNET_MULTICAST_origin_to_all (origin, + 0, + 0, + origin_notify, + NULL); +} + + +static void +origin_message (void *cls, + const struct GNUNET_MULTICAST_MessageHeader *msg) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "origin message msg\n"); +} + + +static void +service_connect1 (void *cls, + struct GNUNET_TESTBED_Operation *op, + void *ca_result, + const char *emsg) +{ + member = ca_result; + + if (NULL == member) + { + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_shutdown (); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Connected to multicast service of member\n"); + } +} + + +static void +multicast_da1 (void *cls, + void * op_result) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Member parting from multicast group\n"); + + GNUNET_MULTICAST_member_part (member, NULL, NULL); +} + + +static void * +multicast_ca1 (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct GNUNET_MessageHeader *join_msg; + void *ret; + + // Get members keys + member_key = GNUNET_CRYPTO_ecdsa_key_create (); + GNUNET_CRYPTO_ecdsa_key_get_public (member_key, &member_pub_key); + + char data[] = "Hi, can I enter?"; + uint8_t data_size = strlen (data) + 1; + join_msg = GNUNET_malloc (sizeof (join_msg) + data_size); + join_msg->size = htons (sizeof (join_msg) + data_size); + join_msg->type = htons (123); + GNUNET_memcpy (&join_msg[1], data, data_size); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Members tries to join multicast group\n"); + + ret = GNUNET_MULTICAST_member_join (cfg, + &group_pub_key, + member_key, + peer_id[0], + 0, + NULL, + join_msg, /* join message */ + member_join_request, + member_join_decision, + NULL, /* no test for member_replay_frag */ + NULL, /* no test for member_replay_msg */ + member_message, + NULL); + GNUNET_free (join_msg); + return ret; +} + + +static void +peer_information_cb (void *cls, + struct GNUNET_TESTBED_Operation *op, + const struct GNUNET_TESTBED_PeerInformation *pinfo, + const char *emsg) +{ + int i = (int) (long) cls; + + if (NULL == pinfo) + { + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_shutdown (); + } + + peer_id[i] = pinfo->result.id; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Got peer information of %s (%s)\n", (0==i)?"origin":"member" ,GNUNET_i2s(pinfo->result.id)); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Create member peer\n"); + + if (0 == i) + { + /* connect to multicast service of member */ + op1 = GNUNET_TESTBED_service_connect (NULL, /* Closure for operation */ + peers[1], /* The peer whose service to connect to */ + "multicast", /* The name of the service */ + service_connect1, /* callback to call after a handle to service + is opened */ + NULL, /* closure for the above callback */ + multicast_ca1, /* callback to call with peer's configuration; + this should open the needed service connection */ + multicast_da1, /* callback to be called when closing the + opened service connection */ + NULL); /* closure for the above two callbacks */ + } +} + + +/** + * Test logic of peer "0" being origin starts here. + * + * @param cls closure, for the example: NULL + * @param op should be equal to "dht_op" + * @param ca_result result of the connect operation, the + * connection to the DHT service + * @param emsg error message, if testbed somehow failed to + * connect to the DHT. + */ +static void +service_connect0 (void *cls, + struct GNUNET_TESTBED_Operation *op, + void *ca_result, + const char *emsg) +{ + origin = ca_result; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Connected to multicast service of origin\n"); + + // Get GNUnet identity of origin + pi_op0 = GNUNET_TESTBED_peer_get_information (peers[0], + GNUNET_TESTBED_PIT_IDENTITY, + peer_information_cb, + (void *) 0); + // Get GNUnet identity of member + pi_op1 = GNUNET_TESTBED_peer_get_information (peers[1], + GNUNET_TESTBED_PIT_IDENTITY, + peer_information_cb, + (void *) 1); + + /* Connection to service successful. Here we'd usually do something with + * the service. */ + result = GNUNET_OK; + //GNUNET_SCHEDULER_shutdown (); /* Also kills the testbed */ +} + + + +/** + * Function run when service multicast has started and is providing us + * with a configuration file. + */ +static void * +multicast_ca0 (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + group_key = GNUNET_CRYPTO_eddsa_key_create (); + GNUNET_CRYPTO_eddsa_key_get_public (group_key, &group_pub_key); + + return GNUNET_MULTICAST_origin_start (cfg, + group_key, + 0, + origin_join_request, + NULL, /* no test for origin_replay_frag */ + NULL, /* no test for origin_replay_msg */ + origin_request, + origin_message, + NULL); +} + +static void +multicast_da0 (void *cls, + void *op_result) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Origin closes multicast group\n"); + + GNUNET_MULTICAST_origin_stop (origin, NULL, NULL); +} + + +/** + * Main function inovked from TESTBED once all of the + * peers are up and running. This one then connects + * just to the multicast service of peer 0 and 1. + * Peer 0 is going to be origin. + * Peer 1 is going to be one member. + * Origin will start a multicast group and the member will try to join it. + * After that we execute some multicast test. + * + * @param cls closure + * @param h the run handle + * @param peers started peers for the test + * @param num_peers size of the 'peers' array + * @param links_succeeded number of links between peers that were created + * @param links_failed number of links testbed was unable to establish + */ +static void +testbed_master (void *cls, + struct GNUNET_TESTBED_RunHandle *h, + unsigned int num_peers, + struct GNUNET_TESTBED_Peer **p, + unsigned int links_succeeded, + unsigned int links_failed) +{ + /* Testbed is ready with peers running and connected in a pre-defined overlay + topology (FIXME) */ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Connected to testbed_master()\n"); + + peers = p; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Create origin peer\n"); + op0 = GNUNET_TESTBED_service_connect (NULL, /* Closure for operation */ + peers[0], /* The peer whose service to connect to */ + "multicast", /* The name of the service */ + service_connect0, /* callback to call after a handle to service + is opened */ + NULL, /* closure for the above callback */ + multicast_ca0, /* callback to call with peer's configuration; + this should open the needed service connection */ + multicast_da0, /* callback to be called when closing the + opened service connection */ + NULL); /* closure for the above two callbacks */ + + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL); /* Schedule a new task on shutdown */ + + /* Schedule the shutdown task with a delay of a few Seconds */ + timeout_tid = GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 50), + &timeout_task, NULL); +} + + +int +main (int argc, char *argv[]) +{ + int ret; + + result = GNUNET_SYSERR; + ret = GNUNET_TESTBED_test_run + ("test-multicast-2peers", /* test case name */ + "test_multicast.conf", /* template configuration */ + NUM_PEERS, /* number of peers to start */ + 0LL, /* Event mask - set to 0 for no event notifications */ + NULL, /* Controller event callback */ + NULL, /* Closure for controller event callback */ + testbed_master, /* continuation callback to be called when testbed setup is complete */ + NULL); /* Closure for the test_master callback */ + if ( (GNUNET_OK != ret) || (GNUNET_OK != result) ) + return 1; + return 0; +} + + +/* end of test_multicast_2peers.c */ diff --git a/src/multicast/test_multicast_line.conf b/src/multicast/test_multicast_line.conf new file mode 100644 index 0000000..c1ce7c6 --- /dev/null +++ b/src/multicast/test_multicast_line.conf @@ -0,0 +1,63 @@ +[testbed] +HOSTNAME = localhost +OVERLAY_TOPOLOGY = LINE + +[arm] +GLOBAL_POSTFIX=-L ERROR + +[multicast] +#PREFIX = tmux new-window gdb -x ./cmd.gdb --args +#PREFIX = valgrind --leak-check=full +UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-multicast.sock + +[vpn] +START_ON_DEMAND = NO + +[peerinfo] +# Do not use shipped gnunet HELLOs +USE_INCLUDED_HELLOS = NO + +# Option to disable all disk IO; only useful for testbed runs +# (large-scale experiments); disables persistence of HELLOs! +NO_IO = YES + +[cadet] +ID_ANNOUNCE_TIME = 5 s + +[hostlist] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[nat] +ENABLE_UPNP = NO + +[fs] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[vpn] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[revocation] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[gns] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[namestore] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[namecache] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[topology] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[nse] +WORKBITS = 0 diff --git a/src/multicast/test_multicast_multipeer.c b/src/multicast/test_multicast_multipeer.c new file mode 100644 index 0000000..9b44e05 --- /dev/null +++ b/src/multicast/test_multicast_multipeer.c @@ -0,0 +1,643 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file multicast/test_multicast_multipeers.c + * @brief Tests for the Multicast API with multiple peers. + * @author xrs + */ + +#include + +#include "platform.h" +#include "gnunet_crypto_lib.h" +#include "gnunet_common.h" +#include "gnunet_util_lib.h" +#include "gnunet_testbed_service.h" +#include "gnunet_multicast_service.h" + +#define PEERS_REQUESTED 12 + +struct MulticastPeerContext +{ + int peer; /* peer number */ + struct GNUNET_CRYPTO_EcdsaPrivateKey *key; + const struct GNUNET_PeerIdentity *id; + struct GNUNET_TESTBED_Operation *op; /* not yet in use */ + struct GNUNET_TESTBED_Operation *pi_op; /* not yet in use */ + int test_ok; +}; + +enum pingpong +{ + PING = 1, + PONG = 2 +}; + +struct pingpong_msg +{ + int peer; + enum pingpong msg; +}; + +static void service_connect (void *cls, + struct GNUNET_TESTBED_Operation *op, + void *ca_result, + const char *emsg); + +static struct MulticastPeerContext **multicast_peers; +static struct GNUNET_TESTBED_Peer **peers; + +static struct GNUNET_TESTBED_Operation *op[PEERS_REQUESTED]; +static struct GNUNET_TESTBED_Operation *pi_op[PEERS_REQUESTED]; + +static struct GNUNET_MULTICAST_Origin *origin; +static struct GNUNET_MULTICAST_Member *members[PEERS_REQUESTED]; /* first element always empty */ + +static struct GNUNET_SCHEDULER_Task *timeout_tid; + +static struct GNUNET_CRYPTO_EddsaPrivateKey *group_key; +static struct GNUNET_CRYPTO_EddsaPublicKey group_pub_key; +static struct GNUNET_HashCode group_pub_key_hash; + +/** + * Global result for testcase. + */ +static int result; + +/** + * Function run on CTRL-C or shutdown (i.e. success/timeout/etc.). + * Cleans up. + */ +static void +shutdown_task (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "shutdown_task!\n"); + for (int i=0;ikey); + GNUNET_free (multicast_peers[i]); + multicast_peers[i] = NULL; + } + GNUNET_free (multicast_peers); + multicast_peers = NULL; + } + + if (NULL != timeout_tid) + { + GNUNET_SCHEDULER_cancel (timeout_tid); + timeout_tid = NULL; + } +} + + +static void +timeout_task (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Timeout!\n"); + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_shutdown (); +} + + +static void +member_join_request (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key, + const struct GNUNET_MessageHeader *join_msg, + struct GNUNET_MULTICAST_JoinHandle *jh) +{ + struct MulticastPeerContext *mc_peer = (struct MulticastPeerContext*)cls; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Peer #%u (%s) sent a join request.\n", + mc_peer->peer, + GNUNET_i2s (multicast_peers[mc_peer->peer]->id)); +} + + +static int +notify (void *cls, + size_t *data_size, + void *data) +{ + struct MulticastPeerContext *mc_peer = (struct MulticastPeerContext*)cls; + + struct pingpong_msg *pp_msg = GNUNET_new (struct pingpong_msg); + pp_msg->peer = mc_peer->peer; + pp_msg->msg = PING; + + *data_size = sizeof (struct pingpong_msg); + GNUNET_memcpy(data, pp_msg, *data_size); + GNUNET_free (pp_msg); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Peer #%u sents ping to origin\n", mc_peer->peer); + + return GNUNET_YES; +} + + +static void +member_join_decision (void *cls, + int is_admitted, + const struct GNUNET_PeerIdentity *peer, + uint16_t relay_count, + const struct GNUNET_PeerIdentity *relays, + const struct GNUNET_MessageHeader *join_msg) +{ + struct MulticastPeerContext *mc_peer = (struct MulticastPeerContext*)cls; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Peer #%u (%s) received a decision from origin: %s\n", + mc_peer->peer, + GNUNET_i2s (multicast_peers[mc_peer->peer]->id), + (GNUNET_YES == is_admitted)?"accepted":"rejected"); + + if (GNUNET_YES == is_admitted) + { + GNUNET_MULTICAST_member_to_origin (members[mc_peer->peer], + 0, + notify, + cls); + + } +} + + +static void +member_replay_frag () +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "member replay frag...\n"); +} + + +static void +member_replay_msg () +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "member replay msg...\n"); +} + + +static void +origin_disconnected_cb (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Origin disconnected. Shutting down.\n"); + result = GNUNET_YES; + GNUNET_SCHEDULER_shutdown (); +} + + +static void +member_disconnected_cb (void *cls) +{ + for (int i = 1; i < PEERS_REQUESTED; ++i) + if (GNUNET_NO == multicast_peers[i]->test_ok) + return; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "All member disconnected. Stopping origin.\n"); + GNUNET_MULTICAST_origin_stop (origin, origin_disconnected_cb, cls); +} + + +static void +member_message (void *cls, + const struct GNUNET_MULTICAST_MessageHeader *msg) +{ + struct MulticastPeerContext *mc_peer = (struct MulticastPeerContext*)cls; + struct pingpong_msg *pp_msg = (struct pingpong_msg*) &(msg[1]); + + if (PONG == pp_msg->msg && mc_peer->peer == pp_msg->peer) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "peer #%i (%s) receives a pong\n", + mc_peer->peer, + GNUNET_i2s (multicast_peers[mc_peer->peer]->id)); + mc_peer->test_ok = GNUNET_OK; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "peer #%u (%s) parting from multicast group\n", + mc_peer->peer, + GNUNET_i2s (multicast_peers[mc_peer->peer]->id)); + + GNUNET_MULTICAST_member_part (members[mc_peer->peer], member_disconnected_cb, cls); + } +} + + +static void +origin_join_request (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key, + const struct GNUNET_MessageHeader *join_msg, + struct GNUNET_MULTICAST_JoinHandle *jh) +{ + struct GNUNET_MessageHeader *join_resp; + + uint8_t data_size = ntohs (join_msg->size); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "origin got a join request...\n"); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "origin receives: '%s'\n", (char *)&join_msg[1]); + + char data[] = "Come in!"; + data_size = strlen (data) + 1; + join_resp = GNUNET_malloc (sizeof (join_resp) + data_size); + join_resp->size = htons (sizeof (join_resp) + data_size); + join_resp->type = htons (123); + GNUNET_memcpy (&join_resp[1], data, data_size); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "origin sends: '%s'\n", data); + + GNUNET_MULTICAST_join_decision (jh, + GNUNET_YES, + 0, + NULL, + join_resp); + + result = GNUNET_OK; +} + + +static void +origin_replay_frag (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key, + uint64_t fragment_id, + uint64_t flags, + struct GNUNET_MULTICAST_ReplayHandle *rh) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "origin replay fraq msg\n"); +} + + +static void +origin_replay_msg (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *member_pub_key, + uint64_t message_id, + uint64_t fragment_offset, + uint64_t flags, + struct GNUNET_MULTICAST_ReplayHandle *rh) +{ + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "origin replay msg\n"); +} + + +static int +origin_notify (void *cls, + size_t *data_size, + void *data) +{ + struct pingpong_msg *rcv_pp_msg = (struct pingpong_msg*)cls; + struct pingpong_msg *pp_msg = GNUNET_new (struct pingpong_msg); + + pp_msg->peer = rcv_pp_msg->peer; + pp_msg->msg = PONG; + *data_size = sizeof (struct pingpong_msg); + GNUNET_memcpy(data, pp_msg, *data_size); + GNUNET_free (pp_msg); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "origin sends pong\n"); + + return GNUNET_YES; +} + + +static void +origin_request (void *cls, + const struct GNUNET_MULTICAST_RequestHeader *req) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "origin receives a msg\n"); + + req++; + struct pingpong_msg *pp_msg = (struct pingpong_msg *) req; + + if (1 != pp_msg->msg) { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "origin didn't reveice a correct request"); + } + + GNUNET_MULTICAST_origin_to_all (origin, + 0, + 0, + origin_notify, + pp_msg); +} + + +static void +origin_message (void *cls, + const struct GNUNET_MULTICAST_MessageHeader *msg) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "origin message msg\n"); +} + + +static void +multicast_disconnect (void *cls, + void *op_result) +{ + +} + + +static void * +multicast_connect (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct MulticastPeerContext *multicast_peer = cls; + struct GNUNET_MessageHeader *join_msg; + char data[64]; + + if (0 == multicast_peer->peer) + { + group_key = GNUNET_CRYPTO_eddsa_key_create (); + GNUNET_CRYPTO_eddsa_key_get_public (group_key, &group_pub_key); + + GNUNET_CRYPTO_hash (&group_pub_key, sizeof (group_pub_key), &group_pub_key_hash); + origin = GNUNET_MULTICAST_origin_start (cfg, + group_key, + 0, + origin_join_request, + origin_replay_frag, + origin_replay_msg, + origin_request, + origin_message, + cls); + if (NULL == origin) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Peer #%u could not create a multicast group", + multicast_peer->peer); + return NULL; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Peer #%u connected as origin to group %s\n", + multicast_peer->peer, + GNUNET_h2s (&group_pub_key_hash)); + return origin; + } + else + { + multicast_peer->key = GNUNET_CRYPTO_ecdsa_key_create (); + + sprintf(data, "Hi, I am peer #%u (%s). Can I enter?", + multicast_peer->peer, + GNUNET_i2s (multicast_peers[multicast_peer->peer]->id)); + uint8_t data_size = strlen (data) + 1; + join_msg = GNUNET_malloc (sizeof (join_msg) + data_size); + join_msg->size = htons (sizeof (join_msg) + data_size); + join_msg->type = htons (123); + GNUNET_memcpy (&join_msg[1], data, data_size); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Peer #%u (%s) tries to join multicast group %s\n", + multicast_peer->peer, + GNUNET_i2s (multicast_peers[multicast_peer->peer]->id), + GNUNET_h2s (&group_pub_key_hash)); + + members[multicast_peer->peer] = + GNUNET_MULTICAST_member_join (cfg, + &group_pub_key, + multicast_peer->key, + multicast_peers[0]->id, + 0, + NULL, + join_msg, /* join message */ + member_join_request, + member_join_decision, + member_replay_frag, + member_replay_msg, + member_message, + cls); + return members[multicast_peer->peer]; + } +} + + +static void +peer_information_cb (void *cls, + struct GNUNET_TESTBED_Operation *operation, + const struct GNUNET_TESTBED_PeerInformation *pinfo, + const char *emsg) +{ + struct MulticastPeerContext *mc_peer = (struct MulticastPeerContext*)cls; + + if (NULL == pinfo) { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "got no peer information\n"); + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_shutdown (); + } + + multicast_peers[mc_peer->peer]->id = pinfo->result.id; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Got peer information of %s (%s)\n", + (0 == mc_peer->peer)? "origin" : "member", + GNUNET_i2s (pinfo->result.id)); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Create peer #%u (%s)\n", + mc_peer->peer, + GNUNET_i2s (multicast_peers[mc_peer->peer]->id)); + + if (0 != mc_peer->peer) + { + /* connect to multicast service of members */ + op[mc_peer->peer] = + GNUNET_TESTBED_service_connect (/* Closure for operation */ + NULL, + /* The peer whose service to connect to */ + peers[mc_peer->peer], + /* The name of the service */ + "multicast", + /* called after a handle to service is opened */ + service_connect, + /* closure for the above callback */ + cls, + /* called when opening the service connection */ + multicast_connect, + /* called when closing the service connection */ + multicast_disconnect, + /* closure for the above two callbacks */ + cls); + } +} + + +static void +service_connect (void *cls, + struct GNUNET_TESTBED_Operation *op, + void *ca_result, + const char *emsg) +{ + struct MulticastPeerContext *mc_peer = (struct MulticastPeerContext*)cls; + + if (NULL == ca_result) + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Connection adapter not created for peer #%u (%s)\n", + mc_peer->peer, + GNUNET_i2s (multicast_peers[mc_peer->peer]->id)); + + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_shutdown(); + } + + if (0 == mc_peer->peer) + { + // Get GNUnet identity of members + for (int i = 0; ipeer = i; + multicast_peers[i]->test_ok = GNUNET_NO; + } + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Create origin peer\n"); + op[0] = + GNUNET_TESTBED_service_connect (/* Closure for operation */ + NULL, + /* The peer whose service to connect to */ + peers[0], + /* The name of the service */ + "multicast", + /* called after a handle to service is opened */ + service_connect, + /* closure for the above callback */ + multicast_peers[0], + /* called when opening the service connection */ + multicast_connect, + /* called when closing the service connection */ + multicast_disconnect, + /* closure for the above two callbacks */ + multicast_peers[0]); + /* Schedule a new task on shutdown */ + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL); + /* Schedule the shutdown task with a delay of a few Seconds */ + timeout_tid = + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply + (GNUNET_TIME_UNIT_SECONDS, 400), + &timeout_task, + NULL); +} + + +int +main (int argc, char *argv[]) +{ + int ret; + char const *config_file; + + if (strstr (argv[0], "_line") != NULL) + { + config_file = "test_multicast_line.conf"; + } + else if (strstr(argv[0], "_star") != NULL) + { + config_file = "test_multicast_star.conf"; + } + else + { + config_file = "test_multicast_star.conf"; + } + + result = GNUNET_SYSERR; + ret = + GNUNET_TESTBED_test_run ("test-multicast-multipeer", + config_file, + /* number of peers to start */ + PEERS_REQUESTED, + /* Event mask - set to 0 for no event notifications */ + 0LL, + /* Controller event callback */ + NULL, + /* Closure for controller event callback */ + NULL, + /* called when testbed setup is complete */ + testbed_master, + /* Closure for the test_master callback */ + NULL); + if ( (GNUNET_OK != ret) || (GNUNET_OK != result) ) + return 1; + return 0; +} + +/* end of test_multicast_multipeer.c */ diff --git a/src/multicast/test_multicast_star.conf b/src/multicast/test_multicast_star.conf new file mode 100644 index 0000000..516c0e3 --- /dev/null +++ b/src/multicast/test_multicast_star.conf @@ -0,0 +1,64 @@ +[testbed] +HOSTNAME = localhost +OVERLAY_TOPOLOGY = STAR + +[arm] +GLOBAL_POSTFIX=-L ERROR + +[multicast] +#PREFIX = tmux new-window gdb -x ./cmd.gdb --args +#PREFIX = valgrind --leak-check=full +UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-multicast.sock + +[vpn] +START_ON_DEMAND = NO + +[peerinfo] +# Do not use shipped gnunet HELLOs +USE_INCLUDED_HELLOS = NO + +# Option to disable all disk IO; only useful for testbed runs +# (large-scale experiments); disables persistence of HELLOs! +NO_IO = YES + +[cadet] +ID_ANNOUNCE_TIME = 5 s + +[hostlist] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[nat] +ENABLE_UPNP = NO + +[fs] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[vpn] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[revocation] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[gns] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[namestore] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[namecache] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[topology] +IMMEDIATE_START = NO +START_ON_DEMAND = NO + +[nse] +WORKBITS = 0 + diff --git a/src/psyc/.gitignore b/src/psyc/.gitignore new file mode 100644 index 0000000..14a1753 --- /dev/null +++ b/src/psyc/.gitignore @@ -0,0 +1,2 @@ +gnunet-service-psyc +test_psyc diff --git a/src/psyc/Makefile.am b/src/psyc/Makefile.am new file mode 100644 index 0000000..511e3e3 --- /dev/null +++ b/src/psyc/Makefile.am @@ -0,0 +1,77 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +pkgcfgdir= $(pkgdatadir)/config.d/ + +libexecdir= $(pkglibdir)/libexec/ + +pkgcfg_DATA = \ + psyc.conf + + +if MINGW + WINFLAGS = -Wl,--no-undefined -Wl,--export-all-symbols +endif + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +lib_LTLIBRARIES = libgnunetpsyc.la + +libgnunetpsyc_la_SOURCES = \ + psyc_api.c psyc.h +libgnunetpsyc_la_LIBADD = \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(top_builddir)/src/psycutil/libgnunetpsycutil.la \ + $(GN_LIBINTL) $(XLIB) +libgnunetpsyc_la_LDFLAGS = \ + $(GN_LIB_LDFLAGS) $(WINFLAGS) \ + -version-info 0:0:0 + +bin_PROGRAMS = + +libexec_PROGRAMS = \ + gnunet-service-psyc + +gnunet_service_psyc_SOURCES = \ + gnunet-service-psyc.c +gnunet_service_psyc_LDADD = \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(top_builddir)/src/statistics/libgnunetstatistics.la \ + $(top_builddir)/src/multicast/libgnunetmulticast.la \ + $(top_builddir)/src/psycstore/libgnunetpsycstore.la \ + $(top_builddir)/src/psycutil/libgnunetpsycutil.la \ + $(GN_LIBINTL) +gnunet_service_psyc_CFLAGS = $(AM_CFLAGS) + + +if HAVE_TESTING +check_PROGRAMS = \ + test_psyc +# test_psyc2 +endif + +if ENABLE_TEST_RUN +AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@};export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME; +TESTS = $(check_PROGRAMS) +endif + +test_psyc_SOURCES = \ + test_psyc.c +test_psyc_LDADD = \ + libgnunetpsyc.la \ + $(top_builddir)/src/psycutil/libgnunetpsycutil.la \ + $(top_builddir)/src/testing/libgnunettesting.la \ + $(top_builddir)/src/util/libgnunetutil.la +#test_psyc2_SOURCES = \ +# test_psyc2.c +#test_psyc2_LDADD = \ +# libgnunetpsyc.la \ +# $(top_builddir)/src/psycutil/libgnunetpsycutil.la \ +# $(top_builddir)/src/testbed/libgnunettestbed.la \ +# $(top_builddir)/src/util/libgnunetutil.la + +EXTRA_DIST = \ + test_psyc.conf diff --git a/src/psyc/gnunet-service-psyc.c b/src/psyc/gnunet-service-psyc.c new file mode 100644 index 0000000..6f2f7a9 --- /dev/null +++ b/src/psyc/gnunet-service-psyc.c @@ -0,0 +1,2860 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file psyc/gnunet-service-psyc.c + * @brief PSYC service + * @author Gabor X Toth + */ + +#include + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_constants.h" +#include "gnunet_protocols.h" +#include "gnunet_statistics_service.h" +#include "gnunet_multicast_service.h" +#include "gnunet_psycstore_service.h" +#include "gnunet_psyc_service.h" +#include "gnunet_psyc_util_lib.h" +#include "psyc.h" + + +/** + * Handle to our current configuration. + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Service handle. + */ +static struct GNUNET_SERVICE_Handle *service; + +/** + * Handle to the statistics service. + */ +static struct GNUNET_STATISTICS_Handle *stats; + +/** + * Handle to the PSYCstore. + */ +static struct GNUNET_PSYCSTORE_Handle *store; + +/** + * All connected masters. + * Channel's pub_key_hash -> struct Master + */ +static struct GNUNET_CONTAINER_MultiHashMap *masters; + +/** + * All connected slaves. + * Channel's pub_key_hash -> struct Slave + */ +static struct GNUNET_CONTAINER_MultiHashMap *slaves; + +/** + * Connected slaves per channel. + * Channel's pub_key_hash -> Slave's pub_key -> struct Slave + */ +static struct GNUNET_CONTAINER_MultiHashMap *channel_slaves; + + +/** + * Message in the transmission queue. + */ +struct TransmitMessage +{ + struct TransmitMessage *prev; + struct TransmitMessage *next; + + struct GNUNET_SERVICE_Client *client; + + /** + * ID assigned to the message. + */ + uint64_t id; + + /** + * Size of message. + */ + uint16_t size; + + /** + * Type of first message part. + */ + uint16_t first_ptype; + + /** + * Type of last message part. + */ + uint16_t last_ptype; + + /* Followed by message */ +}; + + +/** + * Cache for received message fragments. + * Message fragments are only sent to clients after all modifiers arrived. + * + * chan_key -> MultiHashMap chan_msgs + */ +static struct GNUNET_CONTAINER_MultiHashMap *recv_cache; + + +/** + * Entry in the chan_msgs hashmap of @a recv_cache: + * fragment_id -> RecvCacheEntry + */ +struct RecvCacheEntry +{ + struct GNUNET_MULTICAST_MessageHeader *mmsg; + uint16_t ref_count; +}; + + +/** + * Entry in the @a recv_frags hash map of a @a Channel. + * message_id -> FragmentQueue + */ +struct FragmentQueue +{ + /** + * Fragment IDs stored in @a recv_cache. + */ + struct GNUNET_CONTAINER_Heap *fragments; + + /** + * Total size of received fragments. + */ + uint64_t size; + + /** + * Total size of received header fragments (METHOD & MODIFIERs) + */ + uint64_t header_size; + + /** + * The @a state_delta field from struct GNUNET_PSYC_MessageMethod. + */ + uint64_t state_delta; + + /** + * The @a flags field from struct GNUNET_PSYC_MessageMethod. + */ + uint32_t flags; + + /** + * Receive state of message. + * + * @see MessageFragmentState + */ + uint8_t state; + + /** + * Whether the state is already modified in PSYCstore. + */ + uint8_t state_is_modified; + + /** + * Is the message queued for delivery to the client? + * i.e. added to the recv_msgs queue + */ + uint8_t is_queued; +}; + + +/** + * List of connected clients. + */ +struct ClientList +{ + struct ClientList *prev; + struct ClientList *next; + + struct GNUNET_SERVICE_Client *client; +}; + + +struct Operation +{ + struct Operation *prev; + struct Operation *next; + + struct GNUNET_SERVICE_Client *client; + struct Channel *channel; + uint64_t op_id; + uint32_t flags; +}; + + +/** + * Common part of the client context for both a channel master and slave. + */ +struct Channel +{ + struct ClientList *clients_head; + struct ClientList *clients_tail; + + struct Operation *op_head; + struct Operation *op_tail; + + struct TransmitMessage *tmit_head; + struct TransmitMessage *tmit_tail; + + /** + * Current PSYCstore operation. + */ + struct GNUNET_PSYCSTORE_OperationHandle *store_op; + + /** + * Received fragments not yet sent to the client. + * message_id -> FragmentQueue + */ + struct GNUNET_CONTAINER_MultiHashMap *recv_frags; + + /** + * Received message IDs not yet sent to the client. + */ + struct GNUNET_CONTAINER_Heap *recv_msgs; + + /** + * Public key of the channel. + */ + struct GNUNET_CRYPTO_EddsaPublicKey pub_key; + + /** + * Hash of @a pub_key. + */ + struct GNUNET_HashCode pub_key_hash; + + /** + * Last message ID sent to the client. + * 0 if there is no such message. + */ + uint64_t max_message_id; + + /** + * ID of the last stateful message, where the state operations has been + * processed and saved to PSYCstore and which has been sent to the client. + * 0 if there is no such message. + */ + uint64_t max_state_message_id; + + /** + * Expected value size for the modifier being received from the PSYC service. + */ + uint32_t tmit_mod_value_size_expected; + + /** + * Actual value size for the modifier being received from the PSYC service. + */ + uint32_t tmit_mod_value_size; + + /** + * Is this channel ready to receive messages from client? + * #GNUNET_YES or #GNUNET_NO + */ + uint8_t is_ready; + + /** + * Is the client disconnected? + * #GNUNET_YES or #GNUNET_NO + */ + uint8_t is_disconnecting; + + /** + * Is this a channel master (#GNUNET_YES), or slave (#GNUNET_NO)? + */ + uint8_t is_master; + + union { + struct Master *master; + struct Slave *slave; + }; +}; + + +/** + * Client context for a channel master. + */ +struct Master +{ + /** + * Channel struct common for Master and Slave + */ + struct Channel channel; + + /** + * Private key of the channel. + */ + struct GNUNET_CRYPTO_EddsaPrivateKey priv_key; + + /** + * Handle for the multicast origin. + */ + struct GNUNET_MULTICAST_Origin *origin; + + /** + * Transmit handle for multicast. + */ + struct GNUNET_MULTICAST_OriginTransmitHandle *tmit_handle; + + /** + * Incoming join requests from multicast. + * member_pub_key -> struct GNUNET_MULTICAST_JoinHandle * + */ + struct GNUNET_CONTAINER_MultiHashMap *join_reqs; + + /** + * Last message ID transmitted to this channel. + * + * Incremented before sending a message, thus the message_id in messages sent + * starts from 1. + */ + uint64_t max_message_id; + + /** + * ID of the last message with state operations transmitted to the channel. + * 0 if there is no such message. + */ + uint64_t max_state_message_id; + + /** + * Maximum group generation transmitted to the channel. + */ + uint64_t max_group_generation; + + /** + * @see enum GNUNET_PSYC_Policy + */ + enum GNUNET_PSYC_Policy policy; +}; + + +/** + * Client context for a channel slave. + */ +struct Slave +{ + /** + * Channel struct common for Master and Slave + */ + struct Channel channel; + + /** + * Private key of the slave. + */ + struct GNUNET_CRYPTO_EcdsaPrivateKey priv_key; + + /** + * Public key of the slave. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey pub_key; + + /** + * Hash of @a pub_key. + */ + struct GNUNET_HashCode pub_key_hash; + + /** + * Handle for the multicast member. + */ + struct GNUNET_MULTICAST_Member *member; + + /** + * Transmit handle for multicast. + */ + struct GNUNET_MULTICAST_MemberTransmitHandle *tmit_handle; + + /** + * Peer identity of the origin. + */ + struct GNUNET_PeerIdentity origin; + + /** + * Number of items in @a relays. + */ + uint32_t relay_count; + + /** + * Relays that multicast can use to connect. + */ + struct GNUNET_PeerIdentity *relays; + + /** + * Join request to be transmitted to the master on join. + */ + struct GNUNET_PSYC_Message *join_msg; + + /** + * Join decision received from multicast. + */ + struct GNUNET_PSYC_JoinDecisionMessage *join_dcsn; + + /** + * Maximum request ID for this channel. + */ + uint64_t max_request_id; + + /** + * Join flags. + */ + enum GNUNET_PSYC_SlaveJoinFlags join_flags; +}; + + +/** + * Client context. + */ +struct Client { + struct GNUNET_SERVICE_Client *client; + struct Channel *channel; +}; + + +struct ReplayRequestKey +{ + uint64_t fragment_id; + uint64_t message_id; + uint64_t fragment_offset; + uint64_t flags; +}; + + +static void +transmit_message (struct Channel *chn); + +static uint64_t +message_queue_run (struct Channel *chn); + +static uint64_t +message_queue_drop (struct Channel *chn); + + +static void +schedule_transmit_message (void *cls) +{ + struct Channel *chn = cls; + + transmit_message (chn); +} + + +/** + * Task run during shutdown. + * + * @param cls unused + */ +static void +shutdown_task (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "shutting down...\n"); + GNUNET_PSYCSTORE_disconnect (store); + if (NULL != stats) + { + GNUNET_STATISTICS_destroy (stats, GNUNET_YES); + stats = NULL; + } +} + + +static struct Operation * +op_add (struct Channel *chn, struct GNUNET_SERVICE_Client *client, + uint64_t op_id, uint32_t flags) +{ + struct Operation *op = GNUNET_malloc (sizeof (*op)); + op->client = client; + op->channel = chn; + op->op_id = op_id; + op->flags = flags; + GNUNET_CONTAINER_DLL_insert (chn->op_head, chn->op_tail, op); + return op; +} + + +static void +op_remove (struct Operation *op) +{ + GNUNET_CONTAINER_DLL_remove (op->channel->op_head, op->channel->op_tail, op); + GNUNET_free (op); +} + + +/** + * Clean up master data structures after a client disconnected. + */ +static void +cleanup_master (struct Master *mst) +{ + struct Channel *chn = &mst->channel; + + GNUNET_CONTAINER_multihashmap_destroy (mst->join_reqs); + GNUNET_CONTAINER_multihashmap_remove (masters, &chn->pub_key_hash, mst); +} + + +/** + * Clean up slave data structures after a client disconnected. + */ +static void +cleanup_slave (struct Slave *slv) +{ + struct Channel *chn = &slv->channel; + struct GNUNET_CONTAINER_MultiHashMap * + chn_slv = GNUNET_CONTAINER_multihashmap_get (channel_slaves, + &chn->pub_key_hash); + GNUNET_assert (NULL != chn_slv); + GNUNET_CONTAINER_multihashmap_remove (chn_slv, &slv->pub_key_hash, slv); + + if (0 == GNUNET_CONTAINER_multihashmap_size (chn_slv)) + { + GNUNET_CONTAINER_multihashmap_remove (channel_slaves, &chn->pub_key_hash, + chn_slv); + GNUNET_CONTAINER_multihashmap_destroy (chn_slv); + } + GNUNET_CONTAINER_multihashmap_remove (slaves, &chn->pub_key_hash, slv); + + if (NULL != slv->join_msg) + { + GNUNET_free (slv->join_msg); + slv->join_msg = NULL; + } + if (NULL != slv->relays) + { + GNUNET_free (slv->relays); + slv->relays = NULL; + } + GNUNET_CONTAINER_multihashmap_remove (slaves, &chn->pub_key_hash, slv); +} + + +/** + * Clean up channel data structures after a client disconnected. + */ +static void +cleanup_channel (struct Channel *chn) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Cleaning up channel %s. master? %u\n", + chn, + GNUNET_h2s (&chn->pub_key_hash), + chn->is_master); + message_queue_drop (chn); + GNUNET_CONTAINER_multihashmap_destroy (chn->recv_frags); + chn->recv_frags = NULL; + + if (NULL != chn->store_op) + { + GNUNET_PSYCSTORE_operation_cancel (chn->store_op); + chn->store_op = NULL; + } + + (GNUNET_YES == chn->is_master) + ? cleanup_master (chn->master) + : cleanup_slave (chn->slave); + GNUNET_free (chn); +} + + +/** + * Called whenever a client is disconnected. + * Frees our resources associated with that client. + * + * @param cls closure + * @param client identification of the client + * @param app_ctx must match @a client + */ +static void +client_notify_disconnect (void *cls, + struct GNUNET_SERVICE_Client *client, + void *app_ctx) +{ + struct Client *c = app_ctx; + struct Channel *chn = c->channel; + GNUNET_free (c); + + if (NULL == chn) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p User context is NULL in client_notify_disconnect ()\n", + chn); + GNUNET_break (0); + return; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Client %p (%s) disconnected from channel %s\n", + chn, + client, + (GNUNET_YES == chn->is_master) ? "master" : "slave", + GNUNET_h2s (&chn->pub_key_hash)); + + struct ClientList *cli = chn->clients_head; + while (NULL != cli) + { + if (cli->client == client) + { + GNUNET_CONTAINER_DLL_remove (chn->clients_head, chn->clients_tail, cli); + GNUNET_free (cli); + break; + } + cli = cli->next; + } + + struct Operation *op = chn->op_head; + while (NULL != op) + { + if (op->client == client) + { + op->client = NULL; + break; + } + op = op->next; + } + + if (NULL == chn->clients_head) + { /* Last client disconnected. */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Last client (%s) disconnected from channel %s\n", + chn, + (GNUNET_YES == chn->is_master) ? "master" : "slave", + GNUNET_h2s (&chn->pub_key_hash)); + chn->is_disconnecting = GNUNET_YES; + cleanup_channel (chn); + } +} + + +/** + * A new client connected. + * + * @param cls NULL + * @param client client to add + * @param mq message queue for @a client + * @return @a client + */ +static void * +client_notify_connect (void *cls, + struct GNUNET_SERVICE_Client *client, + struct GNUNET_MQ_Handle *mq) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Client connected: %p\n", client); + + struct Client *c = GNUNET_malloc (sizeof (*c)); + c->client = client; + + return c; +} + + +/** + * Send message to all clients connected to the channel. + */ +static void +client_send_msg (const struct Channel *chn, + const struct GNUNET_MessageHeader *msg) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending message to clients of channel %p.\n", + chn); + + struct ClientList *cli = chn->clients_head; + while (NULL != cli) + { + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_copy (msg); + + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (cli->client), + env); + cli = cli->next; + } +} + + +/** + * Send a result code back to the client. + * + * @param client + * Client that should receive the result code. + * @param result_code + * Code to transmit. + * @param op_id + * Operation ID in network byte order. + * @param data + * Data payload or NULL. + * @param data_size + * Size of @a data. + */ +static void +client_send_result (struct GNUNET_SERVICE_Client *client, uint64_t op_id, + int64_t result_code, const void *data, uint16_t data_size) +{ + struct GNUNET_OperationResultMessage *res; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (res, + data_size, + GNUNET_MESSAGE_TYPE_PSYC_RESULT_CODE); + res->result_code = GNUNET_htonll (result_code); + res->op_id = op_id; + if (0 < data_size) + GNUNET_memcpy (&res[1], data, data_size); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Sending result to client for OP ID %" PRIu64 ": %" PRId64 " (size: %u)\n", + client, + GNUNET_ntohll (op_id), + result_code, + data_size); + + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env); +} + + +/** + * Closure for join_mem_test_cb() + */ +struct JoinMemTestClosure +{ + struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key; + struct Channel *channel; + struct GNUNET_MULTICAST_JoinHandle *join_handle; + struct GNUNET_PSYC_JoinRequestMessage *join_msg; +}; + + +/** + * Membership test result callback used for join requests. + */ +static void +join_mem_test_cb (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + struct JoinMemTestClosure *jcls = cls; + + if (GNUNET_NO == result && GNUNET_YES == jcls->channel->is_master) + { /* Pass on join request to client if this is a master channel */ + struct Master *mst = jcls->channel->master; + struct GNUNET_HashCode slave_pub_hash; + GNUNET_CRYPTO_hash (&jcls->slave_pub_key, sizeof (jcls->slave_pub_key), + &slave_pub_hash); + GNUNET_CONTAINER_multihashmap_put (mst->join_reqs, &slave_pub_hash, jcls->join_handle, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); + client_send_msg (jcls->channel, &jcls->join_msg->header); + } + else + { + if (GNUNET_SYSERR == result) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not perform membership test (%.*s)\n", + err_msg_size, err_msg); + } + // FIXME: add relays + GNUNET_MULTICAST_join_decision (jcls->join_handle, result, 0, NULL, NULL); + } + GNUNET_free (jcls->join_msg); + GNUNET_free (jcls); +} + + +/** + * Incoming join request from multicast. + */ +static void +mcast_recv_join_request (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_pub_key, + const struct GNUNET_MessageHeader *join_msg, + struct GNUNET_MULTICAST_JoinHandle *jh) +{ + struct Channel *chn = cls; + uint16_t join_msg_size = 0; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Got join request.\n", + chn); + if (NULL != join_msg) + { + if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE == ntohs (join_msg->type)) + { + join_msg_size = ntohs (join_msg->size); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "%p Got join message with invalid type %u.\n", + chn, + ntohs (join_msg->type)); + } + } + + struct GNUNET_PSYC_JoinRequestMessage * + req = GNUNET_malloc (sizeof (*req) + join_msg_size); + req->header.size = htons (sizeof (*req) + join_msg_size); + req->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_JOIN_REQUEST); + req->slave_pub_key = *slave_pub_key; + if (0 < join_msg_size) + GNUNET_memcpy (&req[1], join_msg, join_msg_size); + + struct JoinMemTestClosure *jcls = GNUNET_malloc (sizeof (*jcls)); + jcls->slave_pub_key = *slave_pub_key; + jcls->channel = chn; + jcls->join_handle = jh; + jcls->join_msg = req; + + GNUNET_PSYCSTORE_membership_test (store, &chn->pub_key, slave_pub_key, + chn->max_message_id, 0, + &join_mem_test_cb, jcls); +} + + +/** + * Join decision received from multicast. + */ +static void +mcast_recv_join_decision (void *cls, int is_admitted, + const struct GNUNET_PeerIdentity *peer, + uint16_t relay_count, + const struct GNUNET_PeerIdentity *relays, + const struct GNUNET_MessageHeader *join_resp) +{ + struct Slave *slv = cls; + struct Channel *chn = &slv->channel; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Got join decision: %d\n", + slv, + is_admitted); + if (GNUNET_YES == chn->is_ready) + { + /* Already admitted */ + return; + } + + uint16_t join_resp_size = (NULL != join_resp) ? ntohs (join_resp->size) : 0; + struct GNUNET_PSYC_JoinDecisionMessage * + dcsn = slv->join_dcsn = GNUNET_malloc (sizeof (*dcsn) + join_resp_size); + dcsn->header.size = htons (sizeof (*dcsn) + join_resp_size); + dcsn->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_JOIN_DECISION); + dcsn->is_admitted = htonl (is_admitted); + if (0 < join_resp_size) + GNUNET_memcpy (&dcsn[1], join_resp, join_resp_size); + + client_send_msg (chn, &dcsn->header); + + if (GNUNET_YES == is_admitted + && ! (GNUNET_PSYC_SLAVE_JOIN_LOCAL & slv->join_flags)) + { + chn->is_ready = GNUNET_YES; + } +} + + +static int +store_recv_fragment_replay (void *cls, + struct GNUNET_MULTICAST_MessageHeader *msg, + enum GNUNET_PSYCSTORE_MessageFlags flags) +{ + struct GNUNET_MULTICAST_ReplayHandle *rh = cls; + + GNUNET_MULTICAST_replay_response (rh, &msg->header, GNUNET_MULTICAST_REC_OK); + return GNUNET_YES; +} + + +/** + * Received result of GNUNET_PSYCSTORE_fragment_get() for multicast replay. + */ +static void +store_recv_fragment_replay_result (void *cls, + int64_t result, + const char *err_msg, + uint16_t err_msg_size) +{ + struct GNUNET_MULTICAST_ReplayHandle *rh = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Fragment replay: PSYCSTORE returned %" PRId64 " (%.*s)\n", + rh, + result, + err_msg_size, + err_msg); + switch (result) + { + case GNUNET_YES: + break; + + case GNUNET_NO: + GNUNET_MULTICAST_replay_response (rh, NULL, + GNUNET_MULTICAST_REC_NOT_FOUND); + return; + + case GNUNET_PSYCSTORE_MEMBERSHIP_TEST_FAILED: + GNUNET_MULTICAST_replay_response (rh, NULL, + GNUNET_MULTICAST_REC_ACCESS_DENIED); + return; + + case GNUNET_SYSERR: + GNUNET_MULTICAST_replay_response (rh, NULL, + GNUNET_MULTICAST_REC_INTERNAL_ERROR); + return; + } + /* GNUNET_MULTICAST_replay_response frees 'rh' when passed + * an error code, so it must be ensured no further processing + * is attempted on 'rh'. Maybe this should be refactored as + * it doesn't look very intuitive. --lynX + */ + GNUNET_MULTICAST_replay_response_end (rh); +} + + +/** + * Incoming fragment replay request from multicast. + */ +static void +mcast_recv_replay_fragment (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_pub_key, + uint64_t fragment_id, uint64_t flags, + struct GNUNET_MULTICAST_ReplayHandle *rh) + +{ + struct Channel *chn = cls; + GNUNET_PSYCSTORE_fragment_get (store, &chn->pub_key, slave_pub_key, + fragment_id, fragment_id, + &store_recv_fragment_replay, + &store_recv_fragment_replay_result, rh); +} + + +/** + * Incoming message replay request from multicast. + */ +static void +mcast_recv_replay_message (void *cls, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_pub_key, + uint64_t message_id, + uint64_t fragment_offset, + uint64_t flags, + struct GNUNET_MULTICAST_ReplayHandle *rh) +{ + struct Channel *chn = cls; + GNUNET_PSYCSTORE_message_get (store, &chn->pub_key, slave_pub_key, + message_id, message_id, 1, NULL, + &store_recv_fragment_replay, + &store_recv_fragment_replay_result, rh); +} + + +/** + * Convert an uint64_t in network byte order to a HashCode + * that can be used as key in a MultiHashMap + */ +static inline void +hash_key_from_nll (struct GNUNET_HashCode *key, uint64_t n) +{ + /* use little-endian order, as idx_of MultiHashMap casts key to unsigned int */ + /* TODO: use built-in byte swap functions if available */ + + n = ((n << 8) & 0xFF00FF00FF00FF00ULL) | ((n >> 8) & 0x00FF00FF00FF00FFULL); + n = ((n << 16) & 0xFFFF0000FFFF0000ULL) | ((n >> 16) & 0x0000FFFF0000FFFFULL); + + *key = (struct GNUNET_HashCode) {}; + *((uint64_t *) key) + = (n << 32) | (n >> 32); +} + + +/** + * Convert an uint64_t in host byte order to a HashCode + * that can be used as key in a MultiHashMap + */ +static inline void +hash_key_from_hll (struct GNUNET_HashCode *key, uint64_t n) +{ +#if __BYTE_ORDER == __BIG_ENDIAN + hash_key_from_nll (key, n); +#elif __BYTE_ORDER == __LITTLE_ENDIAN + *key = (struct GNUNET_HashCode) {}; + *((uint64_t *) key) = n; +#else + #error byteorder undefined +#endif +} + + +/** + * Initialize PSYC message header. + */ +static inline void +psyc_msg_init (struct GNUNET_PSYC_MessageHeader *pmsg, + const struct GNUNET_MULTICAST_MessageHeader *mmsg, uint32_t flags) +{ + uint16_t size = ntohs (mmsg->header.size); + uint16_t psize = sizeof (*pmsg) + size - sizeof (*mmsg); + + pmsg->header.size = htons (psize); + pmsg->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE); + pmsg->message_id = mmsg->message_id; + pmsg->fragment_offset = mmsg->fragment_offset; + pmsg->flags = htonl (flags); + + GNUNET_memcpy (&pmsg[1], &mmsg[1], size - sizeof (*mmsg)); +} + + +/** + * Create a new PSYC message from a multicast message for sending it to clients. + */ +static inline struct GNUNET_PSYC_MessageHeader * +psyc_msg_new (const struct GNUNET_MULTICAST_MessageHeader *mmsg, uint32_t flags) +{ + struct GNUNET_PSYC_MessageHeader *pmsg; + uint16_t size = ntohs (mmsg->header.size); + uint16_t psize = sizeof (*pmsg) + size - sizeof (*mmsg); + + pmsg = GNUNET_malloc (psize); + psyc_msg_init (pmsg, mmsg, flags); + return pmsg; +} + + +/** + * Send multicast message to all clients connected to the channel. + */ +static void +client_send_mcast_msg (struct Channel *chn, + const struct GNUNET_MULTICAST_MessageHeader *mmsg, + uint32_t flags) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Sending multicast message to client. fragment_id: %" PRIu64 ", message_id: %" PRIu64 "\n", + chn, + GNUNET_ntohll (mmsg->fragment_id), + GNUNET_ntohll (mmsg->message_id)); + + struct GNUNET_PSYC_MessageHeader * + pmsg = GNUNET_PSYC_message_header_create (mmsg, flags); + client_send_msg (chn, &pmsg->header); + GNUNET_free (pmsg); +} + + +/** + * Send multicast request to all clients connected to the channel. + */ +static void +client_send_mcast_req (struct Master *mst, + const struct GNUNET_MULTICAST_RequestHeader *req) +{ + struct Channel *chn = &mst->channel; + + struct GNUNET_PSYC_MessageHeader *pmsg; + uint16_t size = ntohs (req->header.size); + uint16_t psize = sizeof (*pmsg) + size - sizeof (*req); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Sending multicast request to client. fragment_id: %" PRIu64 ", message_id: %" PRIu64 "\n", + chn, + GNUNET_ntohll (req->fragment_id), + GNUNET_ntohll (req->request_id)); + + pmsg = GNUNET_malloc (psize); + pmsg->header.size = htons (psize); + pmsg->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE); + pmsg->message_id = req->request_id; + pmsg->fragment_offset = req->fragment_offset; + pmsg->flags = htonl (GNUNET_PSYC_MESSAGE_REQUEST); + pmsg->slave_pub_key = req->member_pub_key; + GNUNET_memcpy (&pmsg[1], &req[1], size - sizeof (*req)); + + client_send_msg (chn, &pmsg->header); + + /* FIXME: save req to PSYCstore so that it can be resent later to clients */ + + GNUNET_free (pmsg); +} + + +/** + * Insert a multicast message fragment into the queue belonging to the message. + * + * @param chn Channel. + * @param mmsg Multicast message fragment. + * @param msg_id_hash Message ID of @a mmsg in a struct GNUNET_HashCode. + * @param first_ptype First PSYC message part type in @a mmsg. + * @param last_ptype Last PSYC message part type in @a mmsg. + */ +static void +fragment_queue_insert (struct Channel *chn, + const struct GNUNET_MULTICAST_MessageHeader *mmsg, + uint16_t first_ptype, uint16_t last_ptype) +{ + const uint16_t size = ntohs (mmsg->header.size); + const uint64_t frag_offset = GNUNET_ntohll (mmsg->fragment_offset); + struct GNUNET_CONTAINER_MultiHashMap + *chan_msgs = GNUNET_CONTAINER_multihashmap_get (recv_cache, + &chn->pub_key_hash); + + struct GNUNET_HashCode msg_id_hash; + hash_key_from_nll (&msg_id_hash, mmsg->message_id); + + struct FragmentQueue + *fragq = GNUNET_CONTAINER_multihashmap_get (chn->recv_frags, &msg_id_hash); + + if (NULL == fragq) + { + fragq = GNUNET_malloc (sizeof (*fragq)); + fragq->state = MSG_FRAG_STATE_HEADER; + fragq->fragments + = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN); + + GNUNET_CONTAINER_multihashmap_put (chn->recv_frags, &msg_id_hash, fragq, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); + + if (NULL == chan_msgs) + { + chan_msgs = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + GNUNET_CONTAINER_multihashmap_put (recv_cache, &chn->pub_key_hash, chan_msgs, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); + } + } + + struct GNUNET_HashCode frag_id_hash; + hash_key_from_nll (&frag_id_hash, mmsg->fragment_id); + struct RecvCacheEntry + *cache_entry = GNUNET_CONTAINER_multihashmap_get (chan_msgs, &frag_id_hash); + if (NULL == cache_entry) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Adding message fragment to cache. message_id: %" PRIu64 ", fragment_id: %" PRIu64 "\n", + chn, + GNUNET_ntohll (mmsg->message_id), + GNUNET_ntohll (mmsg->fragment_id)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p header_size: %" PRIu64 " + %u\n", + chn, + fragq->header_size, + size); + cache_entry = GNUNET_malloc (sizeof (*cache_entry)); + cache_entry->ref_count = 1; + cache_entry->mmsg = GNUNET_malloc (size); + GNUNET_memcpy (cache_entry->mmsg, mmsg, size); + GNUNET_CONTAINER_multihashmap_put (chan_msgs, &frag_id_hash, cache_entry, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); + } + else + { + cache_entry->ref_count++; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Message fragment is already in cache. message_id: %" PRIu64 ", fragment_id: %" PRIu64 ", ref_count: %u\n", + chn, + GNUNET_ntohll (mmsg->message_id), + GNUNET_ntohll (mmsg->fragment_id), + cache_entry->ref_count); + } + + if (MSG_FRAG_STATE_HEADER == fragq->state) + { + if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD == first_ptype) + { + struct GNUNET_PSYC_MessageMethod * + pmeth = (struct GNUNET_PSYC_MessageMethod *) &mmsg[1]; + fragq->state_delta = GNUNET_ntohll (pmeth->state_delta); + fragq->flags = ntohl (pmeth->flags); + } + + if (last_ptype < GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_DATA) + { + fragq->header_size += size; + } + else if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD == first_ptype + || frag_offset == fragq->header_size) + { /* header is now complete */ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Header of message %" PRIu64 " is complete.\n", + chn, + GNUNET_ntohll (mmsg->message_id)); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Adding message %" PRIu64 " to queue.\n", + chn, + GNUNET_ntohll (mmsg->message_id)); + fragq->state = MSG_FRAG_STATE_DATA; + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Header of message %" PRIu64 " is NOT complete yet: %" PRIu64 " != %" PRIu64 "\n", + chn, + GNUNET_ntohll (mmsg->message_id), + frag_offset, + fragq->header_size); + } + } + + switch (last_ptype) + { + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END: + if (frag_offset == fragq->size) + fragq->state = MSG_FRAG_STATE_END; + else + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Message %" PRIu64 " is NOT complete yet: %" PRIu64 " != %" PRIu64 "\n", + chn, + GNUNET_ntohll (mmsg->message_id), + frag_offset, + fragq->size); + break; + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL: + /* Drop message without delivering to client if it's a single fragment */ + fragq->state = + (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD == first_ptype) + ? MSG_FRAG_STATE_DROP + : MSG_FRAG_STATE_CANCEL; + } + + switch (fragq->state) + { + case MSG_FRAG_STATE_DATA: + case MSG_FRAG_STATE_END: + case MSG_FRAG_STATE_CANCEL: + if (GNUNET_NO == fragq->is_queued) + { + GNUNET_CONTAINER_heap_insert (chn->recv_msgs, NULL, + GNUNET_ntohll (mmsg->message_id)); + fragq->is_queued = GNUNET_YES; + } + } + + fragq->size += size; + GNUNET_CONTAINER_heap_insert (fragq->fragments, NULL, + GNUNET_ntohll (mmsg->fragment_id)); +} + + +/** + * Run fragment queue of a message. + * + * Send fragments of a message in order to client, after all modifiers arrived + * from multicast. + * + * @param chn + * Channel. + * @param msg_id + * ID of the message @a fragq belongs to. + * @param fragq + * Fragment queue of the message. + * @param drop + * Drop message without delivering to client? + * #GNUNET_YES or #GNUNET_NO. + */ +static void +fragment_queue_run (struct Channel *chn, uint64_t msg_id, + struct FragmentQueue *fragq, uint8_t drop) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Running message fragment queue for message %" PRIu64 " (state: %u).\n", + chn, + msg_id, + fragq->state); + + struct GNUNET_CONTAINER_MultiHashMap + *chan_msgs = GNUNET_CONTAINER_multihashmap_get (recv_cache, + &chn->pub_key_hash); + GNUNET_assert (NULL != chan_msgs); + uint64_t frag_id; + + while (GNUNET_YES == GNUNET_CONTAINER_heap_peek2 (fragq->fragments, NULL, + &frag_id)) + { + struct GNUNET_HashCode frag_id_hash; + hash_key_from_hll (&frag_id_hash, frag_id); + struct RecvCacheEntry *cache_entry + = GNUNET_CONTAINER_multihashmap_get (chan_msgs, &frag_id_hash); + if (cache_entry != NULL) + { + if (GNUNET_NO == drop) + { + client_send_mcast_msg (chn, cache_entry->mmsg, 0); + } + if (cache_entry->ref_count <= 1) + { + GNUNET_CONTAINER_multihashmap_remove (chan_msgs, &frag_id_hash, + cache_entry); + GNUNET_free (cache_entry->mmsg); + GNUNET_free (cache_entry); + } + else + { + cache_entry->ref_count--; + } + } +#if CACHE_AGING_IMPLEMENTED + else if (GNUNET_NO == drop) + { + /* TODO: fragment not in cache anymore, retrieve it from PSYCstore */ + } +#endif + + GNUNET_CONTAINER_heap_remove_root (fragq->fragments); + } + + if (MSG_FRAG_STATE_END <= fragq->state) + { + struct GNUNET_HashCode msg_id_hash; + hash_key_from_hll (&msg_id_hash, msg_id); + + GNUNET_CONTAINER_multihashmap_remove (chn->recv_frags, &msg_id_hash, fragq); + GNUNET_CONTAINER_heap_destroy (fragq->fragments); + GNUNET_free (fragq); + } + else + { + fragq->is_queued = GNUNET_NO; + } +} + + +struct StateModifyClosure +{ + struct Channel *channel; + uint64_t msg_id; + struct GNUNET_HashCode msg_id_hash; +}; + + +void +store_recv_state_modify_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + struct StateModifyClosure *mcls = cls; + struct Channel *chn = mcls->channel; + uint64_t msg_id = mcls->msg_id; + + struct FragmentQueue * + fragq = GNUNET_CONTAINER_multihashmap_get (chn->recv_frags, &mcls->msg_id_hash); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p GNUNET_PSYCSTORE_state_modify() returned %" PRId64 " (%.*s)\n", + chn, result, err_msg_size, err_msg); + + switch (result) + { + case GNUNET_OK: + case GNUNET_NO: + if (NULL != fragq) + fragq->state_is_modified = GNUNET_YES; + if (chn->max_state_message_id < msg_id) + chn->max_state_message_id = msg_id; + if (chn->max_message_id < msg_id) + chn->max_message_id = msg_id; + + if (NULL != fragq) + fragment_queue_run (chn, msg_id, fragq, MSG_FRAG_STATE_DROP == fragq->state); + GNUNET_CONTAINER_heap_remove_root (chn->recv_msgs); + message_queue_run (chn); + break; + + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%p GNUNET_PSYCSTORE_state_modify() failed with error %" PRId64 " (%.*s)\n", + chn, result, err_msg_size, err_msg); + /** @todo FIXME: handle state_modify error */ + } +} + + +/** + * Run message queue. + * + * Send messages in queue to client in order after a message has arrived from + * multicast, according to the following: + * - A message is only sent if all of its modifiers arrived. + * - A stateful message is only sent if the previous stateful message + * has already been delivered to the client. + * + * @param chn Channel. + * + * @return Number of messages removed from queue and sent to client. + */ +static uint64_t +message_queue_run (struct Channel *chn) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Running message queue.\n", chn); + uint64_t n = 0; + uint64_t msg_id; + + while (GNUNET_YES == GNUNET_CONTAINER_heap_peek2 (chn->recv_msgs, NULL, + &msg_id)) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Processing message %" PRIu64 " in queue.\n", chn, msg_id); + struct GNUNET_HashCode msg_id_hash; + hash_key_from_hll (&msg_id_hash, msg_id); + + struct FragmentQueue * + fragq = GNUNET_CONTAINER_multihashmap_get (chn->recv_frags, &msg_id_hash); + + if (NULL == fragq || fragq->state <= MSG_FRAG_STATE_HEADER) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p No fragq (%p) or header not complete.\n", + chn, fragq); + break; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Fragment queue entry: state: %u, state delta: " + "%" PRIu64 " - %" PRIu64 " ?= %" PRIu64 "\n", + chn, fragq->state, msg_id, fragq->state_delta, chn->max_state_message_id); + + if (MSG_FRAG_STATE_DATA <= fragq->state) + { + /* Check if there's a missing message before the current one */ + if (GNUNET_PSYC_STATE_NOT_MODIFIED == fragq->state_delta) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "%p state NOT modified\n", chn); + + if (!(fragq->flags & GNUNET_PSYC_MESSAGE_ORDER_ANY) + && (chn->max_message_id != msg_id - 1 + && chn->max_message_id != msg_id)) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Out of order message. " + "(%" PRIu64 " != %" PRIu64 " - 1)\n", + chn, chn->max_message_id, msg_id); + break; + // FIXME: keep track of messages processed in this queue run, + // and only stop after reaching the end + } + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "%p state modified\n", chn); + if (GNUNET_YES != fragq->state_is_modified) + { + if (msg_id - fragq->state_delta != chn->max_state_message_id) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Out of order stateful message. " + "(%" PRIu64 " - %" PRIu64 " != %" PRIu64 ")\n", + chn, msg_id, fragq->state_delta, chn->max_state_message_id); + break; + // FIXME: keep track of messages processed in this queue run, + // and only stop after reaching the end + } + + struct StateModifyClosure *mcls = GNUNET_malloc (sizeof (*mcls)); + mcls->channel = chn; + mcls->msg_id = msg_id; + mcls->msg_id_hash = msg_id_hash; + + /* Apply modifiers to state in PSYCstore */ + GNUNET_PSYCSTORE_state_modify (store, &chn->pub_key, msg_id, + fragq->state_delta, + store_recv_state_modify_result, mcls); + break; // continue after asynchronous state modify result + } + } + chn->max_message_id = msg_id; + } + fragment_queue_run (chn, msg_id, fragq, MSG_FRAG_STATE_DROP == fragq->state); + GNUNET_CONTAINER_heap_remove_root (chn->recv_msgs); + n++; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Removed %" PRIu64 " messages from queue.\n", chn, n); + return n; +} + + +/** + * Drop message queue of a channel. + * + * Remove all messages in queue without sending it to clients. + * + * @param chn Channel. + * + * @return Number of messages removed from queue. + */ +static uint64_t +message_queue_drop (struct Channel *chn) +{ + uint64_t n = 0; + uint64_t msg_id; + while (GNUNET_YES == GNUNET_CONTAINER_heap_peek2 (chn->recv_msgs, NULL, + &msg_id)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "%p Dropping message %" PRIu64 " from queue.\n", chn, msg_id); + struct GNUNET_HashCode msg_id_hash; + hash_key_from_hll (&msg_id_hash, msg_id); + + struct FragmentQueue * + fragq = GNUNET_CONTAINER_multihashmap_get (chn->recv_frags, &msg_id_hash); + GNUNET_assert (NULL != fragq); + fragment_queue_run (chn, msg_id, fragq, GNUNET_YES); + GNUNET_CONTAINER_heap_remove_root (chn->recv_msgs); + n++; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Removed %" PRIu64 " messages from queue.\n", chn, n); + return n; +} + + +/** + * Received result of GNUNET_PSYCSTORE_fragment_store(). + */ +static void +store_recv_fragment_store_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + struct Channel *chn = cls; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p GNUNET_PSYCSTORE_fragment_store() returned %" PRId64 " (%.*s)\n", + chn, result, err_msg_size, err_msg); +} + + +/** + * Handle incoming message fragment from multicast. + * + * Store it using PSYCstore and send it to the clients of the channel in order. + */ +static void +mcast_recv_message (void *cls, const struct GNUNET_MULTICAST_MessageHeader *mmsg) +{ + struct Channel *chn = cls; + uint16_t size = ntohs (mmsg->header.size); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Received multicast message of size %u. " + "fragment_id=%" PRIu64 ", message_id=%" PRIu64 + ", fragment_offset=%" PRIu64 ", flags=%" PRIu64 "\n", + chn, size, + GNUNET_ntohll (mmsg->fragment_id), + GNUNET_ntohll (mmsg->message_id), + GNUNET_ntohll (mmsg->fragment_offset), + GNUNET_ntohll (mmsg->flags)); + + GNUNET_PSYCSTORE_fragment_store (store, &chn->pub_key, mmsg, 0, + &store_recv_fragment_store_result, chn); + + uint16_t first_ptype = 0, last_ptype = 0; + int check = GNUNET_PSYC_receive_check_parts (size - sizeof (*mmsg), + (const char *) &mmsg[1], + &first_ptype, &last_ptype); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Message check result %d, first part type %u, last part type %u\n", + chn, check, first_ptype, last_ptype); + if (GNUNET_SYSERR == check) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "%p Dropping incoming multicast message with invalid parts.\n", + chn); + GNUNET_break_op (0); + return; + } + + fragment_queue_insert (chn, mmsg, first_ptype, last_ptype); + message_queue_run (chn); +} + + +/** + * Incoming request fragment from multicast for a master. + * + * @param cls Master. + * @param req The request. + */ +static void +mcast_recv_request (void *cls, + const struct GNUNET_MULTICAST_RequestHeader *req) +{ + struct Master *mst = cls; + uint16_t size = ntohs (req->header.size); + + char *str = GNUNET_CRYPTO_ecdsa_public_key_to_string (&req->member_pub_key); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Received multicast request of size %u from %s.\n", + mst, size, str); + GNUNET_free (str); + + uint16_t first_ptype = 0, last_ptype = 0; + if (GNUNET_SYSERR + == GNUNET_PSYC_receive_check_parts (size - sizeof (*req), + (const char *) &req[1], + &first_ptype, &last_ptype)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "%p Dropping incoming multicast request with invalid parts.\n", + mst); + GNUNET_break_op (0); + return; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Message parts: first: type %u, last: type %u\n", + first_ptype, last_ptype); + + /* FIXME: in-order delivery */ + client_send_mcast_req (mst, req); +} + + +/** + * Response from PSYCstore with the current counter values for a channel master. + */ +static void +store_recv_master_counters (void *cls, int result, uint64_t max_fragment_id, + uint64_t max_message_id, uint64_t max_group_generation, + uint64_t max_state_message_id) +{ + struct Master *mst = cls; + struct Channel *chn = &mst->channel; + chn->store_op = NULL; + + struct GNUNET_PSYC_CountersResultMessage res; + res.header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_MASTER_START_ACK); + res.header.size = htons (sizeof (res)); + res.result_code = htonl (result); + res.max_message_id = GNUNET_htonll (max_message_id); + + if (GNUNET_OK == result || GNUNET_NO == result) + { + mst->max_message_id = max_message_id; + chn->max_message_id = max_message_id; + chn->max_state_message_id = max_state_message_id; + mst->max_group_generation = max_group_generation; + mst->origin + = GNUNET_MULTICAST_origin_start (cfg, &mst->priv_key, max_fragment_id, + mcast_recv_join_request, + mcast_recv_replay_fragment, + mcast_recv_replay_message, + mcast_recv_request, + mcast_recv_message, chn); + chn->is_ready = GNUNET_YES; + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%p GNUNET_PSYCSTORE_counters_get() " + "returned %d for channel %s.\n", + chn, result, GNUNET_h2s (&chn->pub_key_hash)); + } + + client_send_msg (chn, &res.header); +} + + +/** + * Response from PSYCstore with the current counter values for a channel slave. + */ +void +store_recv_slave_counters (void *cls, int result, uint64_t max_fragment_id, + uint64_t max_message_id, uint64_t max_group_generation, + uint64_t max_state_message_id) +{ + struct Slave *slv = cls; + struct Channel *chn = &slv->channel; + chn->store_op = NULL; + + struct GNUNET_PSYC_CountersResultMessage res; + res.header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_SLAVE_JOIN_ACK); + res.header.size = htons (sizeof (res)); + res.result_code = htonl (result); + res.max_message_id = GNUNET_htonll (max_message_id); + + if (GNUNET_YES == result || GNUNET_NO == result) + { + chn->max_message_id = max_message_id; + chn->max_state_message_id = max_state_message_id; + slv->member + = GNUNET_MULTICAST_member_join (cfg, &chn->pub_key, &slv->priv_key, + &slv->origin, + slv->relay_count, slv->relays, + &slv->join_msg->header, + mcast_recv_join_request, + mcast_recv_join_decision, + mcast_recv_replay_fragment, + mcast_recv_replay_message, + mcast_recv_message, chn); + if (NULL != slv->join_msg) + { + GNUNET_free (slv->join_msg); + slv->join_msg = NULL; + } + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%p GNUNET_PSYCSTORE_counters_get() " + "returned %d for channel %s.\n", + chn, result, GNUNET_h2s (&chn->pub_key_hash)); + } + + client_send_msg (chn, &res.header); +} + + +static void +channel_init (struct Channel *chn) +{ + chn->recv_msgs + = GNUNET_CONTAINER_heap_create (GNUNET_CONTAINER_HEAP_ORDER_MIN); + chn->recv_frags = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); +} + + +/** + * Handle a connecting client starting a channel master. + */ +static void +handle_client_master_start (void *cls, + const struct MasterStartRequest *req) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + + struct GNUNET_CRYPTO_EddsaPublicKey pub_key; + struct GNUNET_HashCode pub_key_hash; + + GNUNET_CRYPTO_eddsa_key_get_public (&req->channel_key, &pub_key); + GNUNET_CRYPTO_hash (&pub_key, sizeof (pub_key), &pub_key_hash); + + struct Master * + mst = GNUNET_CONTAINER_multihashmap_get (masters, &pub_key_hash); + struct Channel *chn; + + if (NULL == mst) + { + mst = GNUNET_malloc (sizeof (*mst)); + mst->policy = ntohl (req->policy); + mst->priv_key = req->channel_key; + mst->join_reqs = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + + chn = c->channel = &mst->channel; + chn->master = mst; + chn->is_master = GNUNET_YES; + chn->pub_key = pub_key; + chn->pub_key_hash = pub_key_hash; + channel_init (chn); + + GNUNET_CONTAINER_multihashmap_put (masters, &chn->pub_key_hash, chn, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); + chn->store_op = GNUNET_PSYCSTORE_counters_get (store, &chn->pub_key, + store_recv_master_counters, mst); + } + else + { + chn = &mst->channel; + + struct GNUNET_PSYC_CountersResultMessage *res; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (res, GNUNET_MESSAGE_TYPE_PSYC_MASTER_START_ACK); + res->result_code = htonl (GNUNET_OK); + res->max_message_id = GNUNET_htonll (mst->max_message_id); + + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env); + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Client connected as master to channel %s.\n", + mst, GNUNET_h2s (&chn->pub_key_hash)); + + struct ClientList *cli = GNUNET_malloc (sizeof (*cli)); + cli->client = client; + GNUNET_CONTAINER_DLL_insert (chn->clients_head, chn->clients_tail, cli); + + GNUNET_SERVICE_client_continue (client); +} + + +static int +check_client_slave_join (void *cls, + const struct SlaveJoinRequest *req) +{ + return GNUNET_OK; +} + + +/** + * Handle a connecting client joining as a channel slave. + */ +static void +handle_client_slave_join (void *cls, + const struct SlaveJoinRequest *req) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + + uint16_t req_size = ntohs (req->header.size); + + struct GNUNET_CRYPTO_EcdsaPublicKey slv_pub_key; + struct GNUNET_HashCode pub_key_hash, slv_pub_hash; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "got join request from client %p\n", + client); + GNUNET_CRYPTO_ecdsa_key_get_public (&req->slave_key, &slv_pub_key); + GNUNET_CRYPTO_hash (&slv_pub_key, sizeof (slv_pub_key), &slv_pub_hash); + GNUNET_CRYPTO_hash (&req->channel_pub_key, sizeof (req->channel_pub_key), &pub_key_hash); + + struct GNUNET_CONTAINER_MultiHashMap * + chn_slv = GNUNET_CONTAINER_multihashmap_get (channel_slaves, &pub_key_hash); + struct Slave *slv = NULL; + struct Channel *chn; + + if (NULL != chn_slv) + { + slv = GNUNET_CONTAINER_multihashmap_get (chn_slv, &slv_pub_hash); + } + if (NULL == slv) + { + slv = GNUNET_malloc (sizeof (*slv)); + slv->priv_key = req->slave_key; + slv->pub_key = slv_pub_key; + slv->pub_key_hash = slv_pub_hash; + slv->origin = req->origin; + slv->relay_count = ntohl (req->relay_count); + slv->join_flags = ntohl (req->flags); + + const struct GNUNET_PeerIdentity * + relays = (const struct GNUNET_PeerIdentity *) &req[1]; + uint16_t relay_size = slv->relay_count * sizeof (*relays); + uint16_t join_msg_size = 0; + + if (sizeof (*req) + relay_size + sizeof (struct GNUNET_MessageHeader) + <= req_size) + { + struct GNUNET_PSYC_Message * + join_msg = (struct GNUNET_PSYC_Message *) (((char *) &req[1]) + relay_size); + join_msg_size = ntohs (join_msg->header.size); + slv->join_msg = GNUNET_malloc (join_msg_size); + GNUNET_memcpy (slv->join_msg, join_msg, join_msg_size); + } + if (sizeof (*req) + relay_size + join_msg_size != req_size) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%u + %u + %u != %u\n", + (unsigned int) sizeof (*req), + relay_size, + join_msg_size, + req_size); + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + GNUNET_free (slv); + return; + } + if (0 < slv->relay_count) + { + slv->relays = GNUNET_malloc (relay_size); + GNUNET_memcpy (slv->relays, &req[1], relay_size); + } + + chn = c->channel = &slv->channel; + chn->slave = slv; + chn->is_master = GNUNET_NO; + chn->pub_key = req->channel_pub_key; + chn->pub_key_hash = pub_key_hash; + channel_init (chn); + + if (NULL == chn_slv) + { + chn_slv = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES); + GNUNET_CONTAINER_multihashmap_put (channel_slaves, &chn->pub_key_hash, chn_slv, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); + } + GNUNET_CONTAINER_multihashmap_put (chn_slv, &slv->pub_key_hash, chn, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); + GNUNET_CONTAINER_multihashmap_put (slaves, &chn->pub_key_hash, chn, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); + chn->store_op = GNUNET_PSYCSTORE_counters_get (store, &chn->pub_key, + &store_recv_slave_counters, slv); + } + else + { + chn = &slv->channel; + + struct GNUNET_PSYC_CountersResultMessage *res; + + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (res, GNUNET_MESSAGE_TYPE_PSYC_SLAVE_JOIN_ACK); + res->result_code = htonl (GNUNET_OK); + res->max_message_id = GNUNET_htonll (chn->max_message_id); + + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env); + + if (GNUNET_PSYC_SLAVE_JOIN_LOCAL & slv->join_flags) + { + mcast_recv_join_decision (slv, GNUNET_YES, + NULL, 0, NULL, NULL); + } + else if (NULL == slv->member) + { + slv->member + = GNUNET_MULTICAST_member_join (cfg, &chn->pub_key, &slv->priv_key, + &slv->origin, + slv->relay_count, slv->relays, + &slv->join_msg->header, + &mcast_recv_join_request, + &mcast_recv_join_decision, + &mcast_recv_replay_fragment, + &mcast_recv_replay_message, + &mcast_recv_message, chn); + if (NULL != slv->join_msg) + { + GNUNET_free (slv->join_msg); + slv->join_msg = NULL; + } + } + else if (NULL != slv->join_dcsn) + { + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_copy (&slv->join_dcsn->header); + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env); + } + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Client %p connected as slave to channel %s.\n", + client, + GNUNET_h2s (&chn->pub_key_hash)); + + struct ClientList *cli = GNUNET_malloc (sizeof (*cli)); + cli->client = client; + GNUNET_CONTAINER_DLL_insert (chn->clients_head, chn->clients_tail, cli); + + GNUNET_SERVICE_client_continue (client); +} + + +struct JoinDecisionClosure +{ + int32_t is_admitted; + struct GNUNET_MessageHeader *msg; +}; + + +/** + * Iterator callback for sending join decisions to multicast. + */ +static int +mcast_send_join_decision (void *cls, const struct GNUNET_HashCode *pub_key_hash, + void *value) +{ + struct JoinDecisionClosure *jcls = cls; + struct GNUNET_MULTICAST_JoinHandle *jh = value; + // FIXME: add relays + GNUNET_MULTICAST_join_decision (jh, jcls->is_admitted, 0, NULL, jcls->msg); + return GNUNET_YES; +} + + +static int +check_client_join_decision (void *cls, + const struct GNUNET_PSYC_JoinDecisionMessage *dcsn) +{ + return GNUNET_OK; +} + + +/** + * Join decision from client. + */ +static void +handle_client_join_decision (void *cls, + const struct GNUNET_PSYC_JoinDecisionMessage *dcsn) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Channel *chn = c->channel; + if (NULL == chn) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + GNUNET_assert (GNUNET_YES == chn->is_master); + struct Master *mst = chn->master; + + struct JoinDecisionClosure jcls; + jcls.is_admitted = ntohl (dcsn->is_admitted); + jcls.msg + = (sizeof (*dcsn) + sizeof (*jcls.msg) <= ntohs (dcsn->header.size)) + ? (struct GNUNET_MessageHeader *) &dcsn[1] + : NULL; + + struct GNUNET_HashCode slave_pub_hash; + GNUNET_CRYPTO_hash (&dcsn->slave_pub_key, sizeof (dcsn->slave_pub_key), + &slave_pub_hash); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Got join decision (%d) from client for channel %s..\n", + mst, jcls.is_admitted, GNUNET_h2s (&chn->pub_key_hash)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p ..and slave %s.\n", + mst, GNUNET_h2s (&slave_pub_hash)); + + GNUNET_CONTAINER_multihashmap_get_multiple (mst->join_reqs, &slave_pub_hash, + &mcast_send_join_decision, &jcls); + GNUNET_CONTAINER_multihashmap_remove_all (mst->join_reqs, &slave_pub_hash); + GNUNET_SERVICE_client_continue (client); +} + + +static void +channel_part_cb (void *cls) +{ + struct GNUNET_SERVICE_Client *client = cls; + struct GNUNET_MQ_Envelope *env; + + env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_PSYC_PART_ACK); + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), + env); +} + + +static void +handle_client_part_request (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + struct Client *c = cls; + + c->channel->is_disconnecting = GNUNET_YES; + if (GNUNET_YES == c->channel->is_master) + { + struct Master *mst = (struct Master *) c->channel; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got part request from master %p\n", + mst); + GNUNET_assert (NULL != mst->origin); + GNUNET_MULTICAST_origin_stop (mst->origin, channel_part_cb, c->client); + } + else + { + struct Slave *slv = (struct Slave *) c->channel; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got part request from slave %p\n", + slv); + GNUNET_assert (NULL != slv->member); + GNUNET_MULTICAST_member_part (slv->member, channel_part_cb, c->client); + } + GNUNET_SERVICE_client_continue (c->client); +} + + +/** + * Send acknowledgement to a client. + * + * Sent after a message fragment has been passed on to multicast. + * + * @param chn The channel struct for the client. + */ +static void +send_message_ack (struct Channel *chn, struct GNUNET_SERVICE_Client *client) +{ + struct GNUNET_MessageHeader *res; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (res, GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_ACK); + + /* FIXME? */ + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env); +} + + +/** + * Callback for the transmit functions of multicast. + */ +static int +transmit_notify (void *cls, size_t *data_size, void *data) +{ + struct Channel *chn = cls; + struct TransmitMessage *tmit_msg = chn->tmit_head; + + if (NULL == tmit_msg || *data_size < tmit_msg->size) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p transmit_notify: nothing to send.\n", chn); + if (NULL != tmit_msg && *data_size < tmit_msg->size) + GNUNET_break (0); + *data_size = 0; + return GNUNET_NO; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p transmit_notify: sending %u bytes.\n", chn, tmit_msg->size); + + *data_size = tmit_msg->size; + GNUNET_memcpy (data, &tmit_msg[1], *data_size); + + int ret + = (tmit_msg->last_ptype < GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END) + ? GNUNET_NO + : GNUNET_YES; + + /* FIXME: handle disconnecting clients */ + if (NULL != tmit_msg->client) + send_message_ack (chn, tmit_msg->client); + + GNUNET_CONTAINER_DLL_remove (chn->tmit_head, chn->tmit_tail, tmit_msg); + + if (NULL != chn->tmit_head) + { + GNUNET_SCHEDULER_add_now (&schedule_transmit_message, chn); + } + else if (GNUNET_YES == chn->is_disconnecting + && tmit_msg->last_ptype < GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END) + { + /* FIXME: handle partial message (when still in_transmit) */ + GNUNET_free (tmit_msg); + return GNUNET_SYSERR; + } + GNUNET_free (tmit_msg); + return ret; +} + + +/** + * Callback for the transmit functions of multicast. + */ +static int +master_transmit_notify (void *cls, size_t *data_size, void *data) +{ + int ret = transmit_notify (cls, data_size, data); + + if (GNUNET_YES == ret) + { + struct Master *mst = cls; + mst->tmit_handle = NULL; + } + return ret; +} + + +/** + * Callback for the transmit functions of multicast. + */ +static int +slave_transmit_notify (void *cls, size_t *data_size, void *data) +{ + int ret = transmit_notify (cls, data_size, data); + + if (GNUNET_YES == ret) + { + struct Slave *slv = cls; + slv->tmit_handle = NULL; + } + return ret; +} + + +/** + * Transmit a message from a channel master to the multicast group. + */ +static void +master_transmit_message (struct Master *mst) +{ + struct Channel *chn = &mst->channel; + struct TransmitMessage *tmit_msg = chn->tmit_head; + if (NULL == tmit_msg) + return; + if (NULL == mst->tmit_handle) + { + mst->tmit_handle = GNUNET_MULTICAST_origin_to_all (mst->origin, + tmit_msg->id, + mst->max_group_generation, + &master_transmit_notify, + mst); + } + else + { + GNUNET_MULTICAST_origin_to_all_resume (mst->tmit_handle); + } +} + + +/** + * Transmit a message from a channel slave to the multicast group. + */ +static void +slave_transmit_message (struct Slave *slv) +{ + if (NULL == slv->channel.tmit_head) + return; + if (NULL == slv->tmit_handle) + { + slv->tmit_handle = GNUNET_MULTICAST_member_to_origin (slv->member, + slv->channel.tmit_head->id, + &slave_transmit_notify, + slv); + } + else + { + GNUNET_MULTICAST_member_to_origin_resume (slv->tmit_handle); + } +} + + +static void +transmit_message (struct Channel *chn) +{ + chn->is_master + ? master_transmit_message (chn->master) + : slave_transmit_message (chn->slave); +} + + +/** + * Queue a message from a channel master for sending to the multicast group. + */ +static void +master_queue_message (struct Master *mst, struct TransmitMessage *tmit_msg) +{ + if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD == tmit_msg->first_ptype) + { + tmit_msg->id = ++mst->max_message_id; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p master_queue_message: message_id=%" PRIu64 "\n", + mst, tmit_msg->id); + struct GNUNET_PSYC_MessageMethod *pmeth + = (struct GNUNET_PSYC_MessageMethod *) &tmit_msg[1]; + + if (pmeth->flags & GNUNET_PSYC_MASTER_TRANSMIT_STATE_RESET) + { + pmeth->state_delta = GNUNET_htonll (GNUNET_PSYC_STATE_RESET); + } + else if (pmeth->flags & GNUNET_PSYC_MASTER_TRANSMIT_STATE_MODIFY) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p master_queue_message: state_delta=%" PRIu64 "\n", + mst, tmit_msg->id - mst->max_state_message_id); + pmeth->state_delta = GNUNET_htonll (tmit_msg->id + - mst->max_state_message_id); + mst->max_state_message_id = tmit_msg->id; + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p master_queue_message: state not modified\n", mst); + pmeth->state_delta = GNUNET_htonll (GNUNET_PSYC_STATE_NOT_MODIFIED); + } + + if (pmeth->flags & GNUNET_PSYC_MASTER_TRANSMIT_STATE_HASH) + { + /// @todo add state_hash to PSYC header + } + } +} + + +/** + * Queue a message from a channel slave for sending to the multicast group. + */ +static void +slave_queue_message (struct Slave *slv, struct TransmitMessage *tmit_msg) +{ + if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD == tmit_msg->first_ptype) + { + struct GNUNET_PSYC_MessageMethod *pmeth + = (struct GNUNET_PSYC_MessageMethod *) &tmit_msg[1]; + pmeth->state_delta = GNUNET_htonll (GNUNET_PSYC_STATE_NOT_MODIFIED); + tmit_msg->id = ++slv->max_request_id; + } +} + + +/** + * Queue PSYC message parts for sending to multicast. + * + * @param chn + * Channel to send to. + * @param client + * Client the message originates from. + * @param data_size + * Size of @a data. + * @param data + * Concatenated message parts. + * @param first_ptype + * First message part type in @a data. + * @param last_ptype + * Last message part type in @a data. + */ +static struct TransmitMessage * +queue_message (struct Channel *chn, + struct GNUNET_SERVICE_Client *client, + size_t data_size, + const void *data, + uint16_t first_ptype, uint16_t last_ptype) +{ + struct TransmitMessage * + tmit_msg = GNUNET_malloc (sizeof (*tmit_msg) + data_size); + GNUNET_memcpy (&tmit_msg[1], data, data_size); + tmit_msg->client = client; + tmit_msg->size = data_size; + tmit_msg->first_ptype = first_ptype; + tmit_msg->last_ptype = last_ptype; + + /* FIXME: separate queue per message ID */ + + GNUNET_CONTAINER_DLL_insert_tail (chn->tmit_head, chn->tmit_tail, tmit_msg); + + chn->is_master + ? master_queue_message (chn->master, tmit_msg) + : slave_queue_message (chn->slave, tmit_msg); + return tmit_msg; +} + + +/** + * Cancel transmission of current message. + * + * @param chn Channel to send to. + * @param client Client the message originates from. + */ +static void +transmit_cancel (struct Channel *chn, struct GNUNET_SERVICE_Client *client) +{ + uint16_t type = GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL; + + struct GNUNET_MessageHeader msg; + msg.size = htons (sizeof (msg)); + msg.type = htons (type); + + queue_message (chn, client, sizeof (msg), &msg, type, type); + transmit_message (chn); + + /* FIXME: cleanup */ +} + + +static int +check_client_psyc_message (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + return GNUNET_OK; +} + + +/** + * Incoming message from a master or slave client. + */ +static void +handle_client_psyc_message (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Channel *chn = c->channel; + if (NULL == chn) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Received message from client.\n", chn); + GNUNET_PSYC_log_message (GNUNET_ERROR_TYPE_DEBUG, msg); + + if (GNUNET_YES != chn->is_ready) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "%p Channel is not ready yet, disconnecting client %p.\n", + chn, + client); + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + uint16_t size = ntohs (msg->size); + if (GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD < size - sizeof (*msg)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%p Message payload too large: %u < %u.\n", + chn, + (unsigned int) GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD, + (unsigned int) (size - sizeof (*msg))); + GNUNET_break (0); + transmit_cancel (chn, client); + GNUNET_SERVICE_client_drop (client); + return; + } + + uint16_t first_ptype = 0, last_ptype = 0; + if (GNUNET_SYSERR + == GNUNET_PSYC_receive_check_parts (size - sizeof (*msg), + (const char *) &msg[1], + &first_ptype, &last_ptype)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%p Received invalid message part from client.\n", chn); + GNUNET_break (0); + transmit_cancel (chn, client); + GNUNET_SERVICE_client_drop (client); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Received message with first part type %u and last part type %u.\n", + chn, first_ptype, last_ptype); + + queue_message (chn, client, size - sizeof (*msg), &msg[1], + first_ptype, last_ptype); + transmit_message (chn); + /* FIXME: send a few ACKs even before transmit_notify is called */ + + GNUNET_SERVICE_client_continue (client); +}; + + +/** + * Received result of GNUNET_PSYCSTORE_membership_store() + */ +static void +store_recv_membership_store_result (void *cls, + int64_t result, + const char *err_msg, + uint16_t err_msg_size) +{ + struct Operation *op = cls; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p GNUNET_PSYCSTORE_membership_store() returned %" PRId64 " (%.*s)\n", + op->channel, + result, + (int) err_msg_size, + err_msg); + + if (NULL != op->client) + client_send_result (op->client, op->op_id, result, err_msg, err_msg_size); + op_remove (op); +} + + +/** + * Client requests to add/remove a slave in the membership database. + */ +static void +handle_client_membership_store (void *cls, + const struct ChannelMembershipStoreRequest *req) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Channel *chn = c->channel; + if (NULL == chn) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + struct Operation *op = op_add (chn, client, req->op_id, 0); + + uint64_t announced_at = GNUNET_ntohll (req->announced_at); + uint64_t effective_since = GNUNET_ntohll (req->effective_since); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Received membership store request from client.\n", chn); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p did_join: %u, announced_at: %" PRIu64 ", effective_since: %" PRIu64 "\n", + chn, req->did_join, announced_at, effective_since); + + GNUNET_PSYCSTORE_membership_store (store, &chn->pub_key, &req->slave_pub_key, + req->did_join, announced_at, effective_since, + 0, /* FIXME: group_generation */ + &store_recv_membership_store_result, op); + GNUNET_SERVICE_client_continue (client); +} + + +/** + * Received a fragment for GNUNET_PSYCSTORE_fragment_get(), + * in response to a history request from a client. + */ +static int +store_recv_fragment_history (void *cls, + struct GNUNET_MULTICAST_MessageHeader *mmsg, + enum GNUNET_PSYCSTORE_MessageFlags flags) +{ + struct Operation *op = cls; + if (NULL == op->client) + { /* Requesting client already disconnected. */ + return GNUNET_NO; + } + struct Channel *chn = op->channel; + + struct GNUNET_PSYC_MessageHeader *pmsg; + uint16_t msize = ntohs (mmsg->header.size); + uint16_t psize = sizeof (*pmsg) + msize - sizeof (*mmsg); + + struct GNUNET_OperationResultMessage * + res = GNUNET_malloc (sizeof (*res) + psize); + res->header.size = htons (sizeof (*res) + psize); + res->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_HISTORY_RESULT); + res->op_id = op->op_id; + res->result_code = GNUNET_htonll (GNUNET_OK); + + pmsg = (struct GNUNET_PSYC_MessageHeader *) &res[1]; + GNUNET_PSYC_message_header_init (pmsg, mmsg, flags | GNUNET_PSYC_MESSAGE_HISTORIC); + GNUNET_memcpy (&res[1], pmsg, psize); + + /** @todo FIXME: send only to requesting client */ + client_send_msg (chn, &res->header); + + GNUNET_free (res); + return GNUNET_YES; +} + + +/** + * Received the result of GNUNET_PSYCSTORE_fragment_get(), + * in response to a history request from a client. + */ +static void +store_recv_fragment_history_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + struct Operation *op = cls; + if (NULL == op->client) + { /* Requesting client already disconnected. */ + return; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p History replay #%" PRIu64 ": " + "PSYCSTORE returned %" PRId64 " (%.*s)\n", + op->channel, GNUNET_ntohll (op->op_id), result, err_msg_size, err_msg); + + if (op->flags & GNUNET_PSYC_HISTORY_REPLAY_REMOTE) + { + /** @todo Multicast replay request for messages not found locally. */ + } + + client_send_result (op->client, op->op_id, result, err_msg, err_msg_size); + op_remove (op); +} + + +static int +check_client_history_replay (void *cls, + const struct GNUNET_PSYC_HistoryRequestMessage *req) +{ + return GNUNET_OK; +} + + +/** + * Client requests channel history. + */ +static void +handle_client_history_replay (void *cls, + const struct GNUNET_PSYC_HistoryRequestMessage *req) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Channel *chn = c->channel; + if (NULL == chn) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + uint16_t size = ntohs (req->header.size); + const char *method_prefix = (const char *) &req[1]; + + if (size < sizeof (*req) + 1 + || '\0' != method_prefix[size - sizeof (*req) - 1]) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%p History replay #%" PRIu64 ": " + "invalid method prefix. size: %u < %u?\n", + chn, + GNUNET_ntohll (req->op_id), + size, + (unsigned int) sizeof (*req) + 1); + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + struct Operation *op = op_add (chn, client, req->op_id, ntohl (req->flags)); + + if (0 == req->message_limit) + { + GNUNET_PSYCSTORE_message_get (store, &chn->pub_key, NULL, + GNUNET_ntohll (req->start_message_id), + GNUNET_ntohll (req->end_message_id), + 0, method_prefix, + &store_recv_fragment_history, + &store_recv_fragment_history_result, op); + } + else + { + GNUNET_PSYCSTORE_message_get_latest (store, &chn->pub_key, NULL, + GNUNET_ntohll (req->message_limit), + method_prefix, + &store_recv_fragment_history, + &store_recv_fragment_history_result, + op); + } + GNUNET_SERVICE_client_continue (client); +} + + +/** + * Received state var from PSYCstore, send it to client. + */ +static int +store_recv_state_var (void *cls, const char *name, + const void *value, uint32_t value_size) +{ + struct Operation *op = cls; + struct GNUNET_OperationResultMessage *res; + struct GNUNET_MQ_Envelope *env; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p state_get #%" PRIu64 " - received var from PSYCstore: %s\n", + op->channel, GNUNET_ntohll (op->op_id), name); + + if (NULL != name) /* First part */ + { + uint16_t name_size = strnlen (name, GNUNET_PSYC_MODIFIER_MAX_PAYLOAD) + 1; + struct GNUNET_PSYC_MessageModifier *mod; + env = GNUNET_MQ_msg_extra (res, + sizeof (*mod) + name_size + value_size, + GNUNET_MESSAGE_TYPE_PSYC_STATE_RESULT); + res->op_id = op->op_id; + + mod = (struct GNUNET_PSYC_MessageModifier *) &res[1]; + mod->header.size = htons (sizeof (*mod) + name_size + value_size); + mod->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER); + mod->name_size = htons (name_size); + mod->value_size = htonl (value_size); + mod->oper = htons (GNUNET_PSYC_OP_ASSIGN); + GNUNET_memcpy (&mod[1], name, name_size); + GNUNET_memcpy (((char *) &mod[1]) + name_size, value, value_size); + } + else /* Continuation */ + { + struct GNUNET_MessageHeader *mod; + env = GNUNET_MQ_msg_extra (res, + sizeof (*mod) + value_size, + GNUNET_MESSAGE_TYPE_PSYC_STATE_RESULT); + res->op_id = op->op_id; + + mod = (struct GNUNET_MessageHeader *) &res[1]; + mod->size = htons (sizeof (*mod) + value_size); + mod->type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT); + GNUNET_memcpy (&mod[1], value, value_size); + } + + // FIXME: client might have been disconnected + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (op->client), env); + return GNUNET_YES; +} + + +/** + * Received result of GNUNET_PSYCSTORE_state_get() + * or GNUNET_PSYCSTORE_state_get_prefix() + */ +static void +store_recv_state_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + struct Operation *op = cls; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p state_get #%" PRIu64 ": " + "PSYCSTORE returned %" PRId64 " (%.*s)\n", + op->channel, GNUNET_ntohll (op->op_id), result, err_msg_size, err_msg); + + // FIXME: client might have been disconnected + client_send_result (op->client, op->op_id, result, err_msg, err_msg_size); + op_remove (op); +} + + +static int +check_client_state_get (void *cls, + const struct StateRequest *req) +{ + struct Client *c = cls; + struct Channel *chn = c->channel; + if (NULL == chn) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + uint16_t name_size = ntohs (req->header.size) - sizeof (*req); + const char *name = (const char *) &req[1]; + if (0 == name_size || '\0' != name[name_size - 1]) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/** + * Client requests best matching state variable from PSYCstore. + */ +static void +handle_client_state_get (void *cls, + const struct StateRequest *req) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Channel *chn = c->channel; + + const char *name = (const char *) &req[1]; + struct Operation *op = op_add (chn, client, req->op_id, 0); + GNUNET_PSYCSTORE_state_get (store, &chn->pub_key, name, + &store_recv_state_var, + &store_recv_state_result, op); + GNUNET_SERVICE_client_continue (client); +} + + +static int +check_client_state_get_prefix (void *cls, + const struct StateRequest *req) +{ + struct Client *c = cls; + struct Channel *chn = c->channel; + if (NULL == chn) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + uint16_t name_size = ntohs (req->header.size) - sizeof (*req); + const char *name = (const char *) &req[1]; + if (0 == name_size || '\0' != name[name_size - 1]) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/** + * Client requests state variables with a given prefix from PSYCstore. + */ +static void +handle_client_state_get_prefix (void *cls, + const struct StateRequest *req) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Channel *chn = c->channel; + + const char *name = (const char *) &req[1]; + struct Operation *op = op_add (chn, client, req->op_id, 0); + GNUNET_PSYCSTORE_state_get_prefix (store, &chn->pub_key, name, + &store_recv_state_var, + &store_recv_state_result, op); + GNUNET_SERVICE_client_continue (client); +} + + +/** + * Initialize the PSYC service. + * + * @param cls Closure. + * @param server The initialized server. + * @param c Configuration to use. + */ +static void +run (void *cls, + const struct GNUNET_CONFIGURATION_Handle *c, + struct GNUNET_SERVICE_Handle *svc) +{ + cfg = c; + service = svc; + store = GNUNET_PSYCSTORE_connect (cfg); + stats = GNUNET_STATISTICS_create ("psyc", cfg); + masters = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES); + slaves = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES); + channel_slaves = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + recv_cache = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL); +} + + +/** + * Define "main" method using service macro. + */ +GNUNET_SERVICE_MAIN +("psyc", + GNUNET_SERVICE_OPTION_NONE, + &run, + &client_notify_connect, + &client_notify_disconnect, + NULL, + GNUNET_MQ_hd_fixed_size (client_master_start, + GNUNET_MESSAGE_TYPE_PSYC_MASTER_START, + struct MasterStartRequest, + NULL), + GNUNET_MQ_hd_var_size (client_slave_join, + GNUNET_MESSAGE_TYPE_PSYC_SLAVE_JOIN, + struct SlaveJoinRequest, + NULL), + GNUNET_MQ_hd_var_size (client_join_decision, + GNUNET_MESSAGE_TYPE_PSYC_JOIN_DECISION, + struct GNUNET_PSYC_JoinDecisionMessage, + NULL), + GNUNET_MQ_hd_fixed_size (client_part_request, + GNUNET_MESSAGE_TYPE_PSYC_PART_REQUEST, + struct GNUNET_MessageHeader, + NULL), + GNUNET_MQ_hd_var_size (client_psyc_message, + GNUNET_MESSAGE_TYPE_PSYC_MESSAGE, + struct GNUNET_MessageHeader, + NULL), + GNUNET_MQ_hd_fixed_size (client_membership_store, + GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_MEMBERSHIP_STORE, + struct ChannelMembershipStoreRequest, + NULL), + GNUNET_MQ_hd_var_size (client_history_replay, + GNUNET_MESSAGE_TYPE_PSYC_HISTORY_REPLAY, + struct GNUNET_PSYC_HistoryRequestMessage, + NULL), + GNUNET_MQ_hd_var_size (client_state_get, + GNUNET_MESSAGE_TYPE_PSYC_STATE_GET, + struct StateRequest, + NULL), + GNUNET_MQ_hd_var_size (client_state_get_prefix, + GNUNET_MESSAGE_TYPE_PSYC_STATE_GET_PREFIX, + struct StateRequest, + NULL)); + +/* end of gnunet-service-psyc.c */ diff --git a/src/psyc/psyc.conf.in b/src/psyc/psyc.conf.in new file mode 100644 index 0000000..764ccfa --- /dev/null +++ b/src/psyc/psyc.conf.in @@ -0,0 +1,12 @@ +[psyc] +START_ON_DEMAND = @START_ON_DEMAND@ +BINARY = gnunet-service-psyc + +UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-psyc.sock +UNIX_MATCH_UID = YES +UNIX_MATCH_GID = YES + +@UNIXONLY@PORT = 2115 +HOSTNAME = localhost +ACCEPT_FROM = 127.0.0.1; +ACCEPT_FROM6 = ::1; diff --git a/src/psyc/psyc.h b/src/psyc/psyc.h new file mode 100644 index 0000000..74bbf3e --- /dev/null +++ b/src/psyc/psyc.h @@ -0,0 +1,178 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file psyc/psyc.h + * @brief Common type definitions for the PSYC service and API. + * @author Gabor X Toth + */ + +#ifndef PSYC_H +#define PSYC_H + +#include "platform.h" +#include "gnunet_psyc_service.h" + + +int +GNUNET_PSYC_check_message_parts (uint16_t data_size, const char *data, + uint16_t *first_ptype, uint16_t *last_ptype); + +void +GNUNET_PSYC_log_message (enum GNUNET_ErrorType kind, + const struct GNUNET_MessageHeader *msg); + + +enum MessageState +{ + MSG_STATE_START = 0, + MSG_STATE_HEADER = 1, + MSG_STATE_METHOD = 2, + MSG_STATE_MODIFIER = 3, + MSG_STATE_MOD_CONT = 4, + MSG_STATE_DATA = 5, + MSG_STATE_END = 6, + MSG_STATE_CANCEL = 7, + MSG_STATE_ERROR = 8, +}; + + +enum MessageFragmentState +{ + MSG_FRAG_STATE_START = 0, + MSG_FRAG_STATE_HEADER = 1, + MSG_FRAG_STATE_DATA = 2, + MSG_FRAG_STATE_END = 3, + MSG_FRAG_STATE_CANCEL = 4, + MSG_FRAG_STATE_DROP = 5, +}; + + +GNUNET_NETWORK_STRUCT_BEGIN + + +/**** library -> service ****/ + + +struct MasterStartRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYC_MASTER_START + */ + struct GNUNET_MessageHeader header; + + uint32_t policy GNUNET_PACKED; + + struct GNUNET_CRYPTO_EddsaPrivateKey channel_key; +}; + + +struct SlaveJoinRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYC_SLAVE_JOIN + */ + struct GNUNET_MessageHeader header; + + uint32_t relay_count GNUNET_PACKED; + + struct GNUNET_CRYPTO_EddsaPublicKey channel_pub_key; + + struct GNUNET_CRYPTO_EcdsaPrivateKey slave_key; + + struct GNUNET_PeerIdentity origin; + + uint32_t flags GNUNET_PACKED; + + /* Followed by struct GNUNET_PeerIdentity relays[relay_count] */ + + /* Followed by struct GNUNET_MessageHeader join_msg */ +}; + + +struct ChannelMembershipStoreRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_MEMBERSHIP_STORE + */ + struct GNUNET_MessageHeader header; + + uint32_t reserved GNUNET_PACKED; + + uint64_t op_id GNUNET_PACKED; + + struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key; + + uint64_t announced_at GNUNET_PACKED; + + uint64_t effective_since GNUNET_PACKED; + + uint8_t did_join; +}; + + +struct HistoryRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_HISTORY_REQUEST + */ + struct GNUNET_MessageHeader header; + + uint32_t reserved GNUNET_PACKED; + + /** + * ID for this operation. + */ + uint64_t op_id GNUNET_PACKED; + + uint64_t start_message_id GNUNET_PACKED; + + uint64_t end_message_id GNUNET_PACKED; + + uint64_t message_limit GNUNET_PACKED; +}; + + +struct StateRequest +{ + /** + * Types: + * - GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_STATE_GET + * - GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_STATE_GET_PREFIX + */ + struct GNUNET_MessageHeader header; + + uint32_t reserved GNUNET_PACKED; + + /** + * ID for this operation. + */ + uint64_t op_id GNUNET_PACKED; + + /* Followed by NUL-terminated name. */ +}; + + +/**** service -> library ****/ + + +GNUNET_NETWORK_STRUCT_END + +#endif diff --git a/src/psyc/psyc_api.c b/src/psyc/psyc_api.c new file mode 100644 index 0000000..37ea112 --- /dev/null +++ b/src/psyc/psyc_api.c @@ -0,0 +1,1584 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file psyc/psyc_api.c + * @brief PSYC service; high-level access to the PSYC protocol + * note that clients of this API are NOT expected to + * understand the PSYC message format, only the semantics! + * Parsing (and serializing) the PSYC stream format is done + * within the implementation of the libgnunetpsyc library, + * and this API deliberately exposes as little as possible + * of the actual data stream format to the application! + * @author Gabor X Toth + */ + +#include + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_multicast_service.h" +#include "gnunet_psyc_service.h" +#include "gnunet_psyc_util_lib.h" +#include "psyc.h" + +#define LOG(kind,...) GNUNET_log_from (kind, "psyc-api",__VA_ARGS__) + + +/** + * Handle to access PSYC channel operations for both the master and slaves. + */ +struct GNUNET_PSYC_Channel +{ + /** + * Configuration to use. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Client connection to the service. + */ + struct GNUNET_MQ_Handle *mq; + + /** + * Message to send on connect. + */ + struct GNUNET_MQ_Envelope *connect_env; + + /** + * Time to wait until we try to reconnect on failure. + */ + struct GNUNET_TIME_Relative reconnect_delay; + + /** + * Task for reconnecting when the listener fails. + */ + struct GNUNET_SCHEDULER_Task *reconnect_task; + + /** + * Async operations. + */ + struct GNUNET_OP_Handle *op; + + /** + * Transmission handle; + */ + struct GNUNET_PSYC_TransmitHandle *tmit; + + /** + * Receipt handle; + */ + struct GNUNET_PSYC_ReceiveHandle *recv; + + /** + * Function called after disconnected from the service. + */ + GNUNET_ContinuationCallback disconnect_cb; + + /** + * Closure for @a disconnect_cb. + */ + void *disconnect_cls; + + /** + * Are we polling for incoming messages right now? + */ + uint8_t in_receive; + + /** + * Is this a master or slave channel? + */ + uint8_t is_master; + + /** + * Is this channel in the process of disconnecting from the service? + * #GNUNET_YES or #GNUNET_NO + */ + uint8_t is_disconnecting; +}; + + +/** + * Handle for the master of a PSYC channel. + */ +struct GNUNET_PSYC_Master +{ + struct GNUNET_PSYC_Channel chn; + + GNUNET_PSYC_MasterStartCallback start_cb; + + /** + * Join request callback. + */ + GNUNET_PSYC_JoinRequestCallback join_req_cb; + + /** + * Closure for the callbacks. + */ + void *cb_cls; +}; + + +/** + * Handle for a PSYC channel slave. + */ +struct GNUNET_PSYC_Slave +{ + struct GNUNET_PSYC_Channel chn; + + GNUNET_PSYC_SlaveConnectCallback connect_cb; + + GNUNET_PSYC_JoinDecisionCallback join_dcsn_cb; + + /** + * Closure for the callbacks. + */ + void *cb_cls; +}; + + +/** + * Handle that identifies a join request. + * + * Used to match calls to #GNUNET_PSYC_JoinRequestCallback to the + * corresponding calls to GNUNET_PSYC_join_decision(). + */ +struct GNUNET_PSYC_JoinHandle +{ + struct GNUNET_PSYC_Master *mst; + struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key; +}; + + +/** + * Handle for a pending PSYC transmission operation. + */ +struct GNUNET_PSYC_SlaveTransmitHandle +{ + +}; + + +struct GNUNET_PSYC_HistoryRequest +{ + /** + * Channel. + */ + struct GNUNET_PSYC_Channel *chn; + + /** + * Operation ID. + */ + uint64_t op_id; + + /** + * Message handler. + */ + struct GNUNET_PSYC_ReceiveHandle *recv; + + /** + * Function to call when the operation finished. + */ + GNUNET_ResultCallback result_cb; + + /** + * Closure for @a result_cb. + */ + void *cls; +}; + + +struct GNUNET_PSYC_StateRequest +{ + /** + * Channel. + */ + struct GNUNET_PSYC_Channel *chn; + + /** + * Operation ID. + */ + uint64_t op_id; + + /** + * State variable result callback. + */ + GNUNET_PSYC_StateVarCallback var_cb; + + /** + * Function to call when the operation finished. + */ + GNUNET_ResultCallback result_cb; + + /** + * Closure for @a result_cb. + */ + void *cls; +}; + + +static int +check_channel_result (void *cls, + const struct GNUNET_OperationResultMessage *res) +{ + return GNUNET_OK; +} + + +static void +handle_channel_result (void *cls, + const struct GNUNET_OperationResultMessage *res) +{ + struct GNUNET_PSYC_Channel *chn = cls; + + uint16_t size = ntohs (res->header.size); + if (size < sizeof (*res)) + { /* Error, message too small. */ + GNUNET_break (0); + return; + } + + uint16_t data_size = size - sizeof (*res); + const char *data = (0 < data_size) ? (void *) &res[1] : NULL; + GNUNET_OP_result (chn->op, GNUNET_ntohll (res->op_id), + GNUNET_ntohll (res->result_code), + data, data_size, NULL); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "handle_channel_result: Received result message with OP ID %" PRIu64 "\n", + GNUNET_ntohll (res->op_id)); +} + + +static void +op_recv_history_result (void *cls, int64_t result, + const void *data, uint16_t data_size) +{ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Received history replay result: %" PRId64 ".\n", result); + + struct GNUNET_PSYC_HistoryRequest *hist = cls; + + if (NULL != hist->result_cb) + hist->result_cb (hist->cls, result, data, data_size); + + GNUNET_PSYC_receive_destroy (hist->recv); + GNUNET_free (hist); +} + + +static void +op_recv_state_result (void *cls, int64_t result, + const void *data, uint16_t data_size) +{ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Received state request result: %" PRId64 ".\n", result); + + struct GNUNET_PSYC_StateRequest *sr = cls; + + if (NULL != sr->result_cb) + sr->result_cb (sr->cls, result, data, data_size); + + GNUNET_free (sr); +} + + +static int +check_channel_history_result (void *cls, + const struct GNUNET_OperationResultMessage *res) +{ + struct GNUNET_PSYC_MessageHeader * + pmsg = (struct GNUNET_PSYC_MessageHeader *) GNUNET_MQ_extract_nested_mh (res); + uint16_t size = ntohs (res->header.size); + + if ( (NULL == pmsg) || + (size < sizeof (*res) + sizeof (*pmsg)) ) + { /* Error, message too small. */ + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +static void +handle_channel_history_result (void *cls, + const struct GNUNET_OperationResultMessage *res) +{ + struct GNUNET_PSYC_Channel *chn = cls; + struct GNUNET_PSYC_MessageHeader * + pmsg = (struct GNUNET_PSYC_MessageHeader *) GNUNET_MQ_extract_nested_mh (res); + GNUNET_ResultCallback result_cb = NULL; + struct GNUNET_PSYC_HistoryRequest *hist = NULL; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "%p Received historic fragment for message #%" PRIu64 ".\n", + chn, + GNUNET_ntohll (pmsg->message_id)); + + if (GNUNET_YES != GNUNET_OP_get (chn->op, + GNUNET_ntohll (res->op_id), + &result_cb, (void *) &hist, NULL)) + { /* Operation not found. */ + LOG (GNUNET_ERROR_TYPE_WARNING, + "%p Replay operation not found for historic fragment of message #%" + PRIu64 ".\n", + chn, GNUNET_ntohll (pmsg->message_id)); + return; + } + + GNUNET_PSYC_receive_message (hist->recv, + (const struct GNUNET_PSYC_MessageHeader *) pmsg); +} + + +static int +check_channel_state_result (void *cls, + const struct GNUNET_OperationResultMessage *res) +{ + const struct GNUNET_MessageHeader *mod = GNUNET_MQ_extract_nested_mh (res); + uint16_t mod_size; + uint16_t size; + + if (NULL == mod) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + mod_size = ntohs (mod->size); + size = ntohs (res->header.size); + if (size - sizeof (*res) != mod_size) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +static void +handle_channel_state_result (void *cls, + const struct GNUNET_OperationResultMessage *res) +{ + struct GNUNET_PSYC_Channel *chn = cls; + + GNUNET_ResultCallback result_cb = NULL; + struct GNUNET_PSYC_StateRequest *sr = NULL; + + if (GNUNET_YES != GNUNET_OP_get (chn->op, + GNUNET_ntohll (res->op_id), + &result_cb, (void *) &sr, NULL)) + { /* Operation not found. */ + return; + } + + const struct GNUNET_MessageHeader *mod = GNUNET_MQ_extract_nested_mh (res); + if (NULL == mod) + { + GNUNET_break_op (0); + return; + } + uint16_t mod_size = ntohs (mod->size); + + switch (ntohs (mod->type)) + { + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER: + { + const struct GNUNET_PSYC_MessageModifier * + pmod = (const struct GNUNET_PSYC_MessageModifier *) mod; + + const char *name = (const char *) &pmod[1]; + uint16_t name_size = ntohs (pmod->name_size); + if (0 == name_size + || mod_size - sizeof (*pmod) < name_size + || '\0' != name[name_size - 1]) + { + GNUNET_break_op (0); + return; + } + sr->var_cb (sr->cls, mod, name, name + name_size, + ntohs (pmod->header.size) - sizeof (*pmod), + ntohs (pmod->value_size)); + break; + } + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT: + sr->var_cb (sr->cls, mod, NULL, (const char *) &mod[1], + mod_size - sizeof (*mod), 0); + break; + } +} + + +static int +check_channel_message (void *cls, + const struct GNUNET_PSYC_MessageHeader *pmsg) +{ + return GNUNET_OK; +} + + +static void +handle_channel_message (void *cls, + const struct GNUNET_PSYC_MessageHeader *pmsg) +{ + struct GNUNET_PSYC_Channel *chn = cls; + + GNUNET_PSYC_receive_message (chn->recv, pmsg); +} + + +static void +handle_channel_message_ack (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + struct GNUNET_PSYC_Channel *chn = cls; + + GNUNET_PSYC_transmit_got_ack (chn->tmit); +} + + +static void +handle_master_start_ack (void *cls, + const struct GNUNET_PSYC_CountersResultMessage *cres) +{ + struct GNUNET_PSYC_Master *mst = cls; + + int32_t result = ntohl (cres->result_code); + if (GNUNET_OK != result && GNUNET_NO != result) + { + LOG (GNUNET_ERROR_TYPE_ERROR, "Could not start master: %ld\n", result); + GNUNET_break (0); + /* FIXME: disconnect */ + } + if (NULL != mst->start_cb) + mst->start_cb (mst->cb_cls, result, GNUNET_ntohll (cres->max_message_id)); +} + + +static int +check_master_join_request (void *cls, + const struct GNUNET_PSYC_JoinRequestMessage *req) +{ + if ( ((sizeof (*req) + sizeof (struct GNUNET_PSYC_Message)) <= ntohs (req->header.size)) && + (NULL == GNUNET_MQ_extract_nested_mh (req)) ) + { + GNUNET_break_op (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +static void +handle_master_join_request (void *cls, + const struct GNUNET_PSYC_JoinRequestMessage *req) +{ + struct GNUNET_PSYC_Master *mst = cls; + + if (NULL == mst->join_req_cb) + return; + + const struct GNUNET_PSYC_Message *join_msg = NULL; + if (sizeof (*req) + sizeof (*join_msg) <= ntohs (req->header.size)) + { + join_msg = (struct GNUNET_PSYC_Message *) GNUNET_MQ_extract_nested_mh (req); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Received join_msg of type %u and size %u.\n", + ntohs (join_msg->header.type), + ntohs (join_msg->header.size)); + } + + struct GNUNET_PSYC_JoinHandle *jh = GNUNET_malloc (sizeof (*jh)); + jh->mst = mst; + jh->slave_pub_key = req->slave_pub_key; + + if (NULL != mst->join_req_cb) + mst->join_req_cb (mst->cb_cls, req, &req->slave_pub_key, join_msg, jh); +} + + +static void +handle_slave_join_ack (void *cls, + const struct GNUNET_PSYC_CountersResultMessage *cres) +{ + struct GNUNET_PSYC_Slave *slv = cls; + + int32_t result = ntohl (cres->result_code); + if (GNUNET_YES != result && GNUNET_NO != result) + { + LOG (GNUNET_ERROR_TYPE_ERROR, "Could not join slave.\n"); + GNUNET_break (0); + /* FIXME: disconnect */ + } + if (NULL != slv->connect_cb) + slv->connect_cb (slv->cb_cls, result, GNUNET_ntohll (cres->max_message_id)); +} + + +static int +check_slave_join_decision (void *cls, + const struct GNUNET_PSYC_JoinDecisionMessage *dcsn) +{ + return GNUNET_OK; +} + + +static void +handle_slave_join_decision (void *cls, + const struct GNUNET_PSYC_JoinDecisionMessage *dcsn) +{ + struct GNUNET_PSYC_Slave *slv = cls; + + struct GNUNET_PSYC_Message *pmsg = NULL; + if (ntohs (dcsn->header.size) <= sizeof (*dcsn) + sizeof (*pmsg)) + pmsg = (struct GNUNET_PSYC_Message *) &dcsn[1]; + + if (NULL != slv->join_dcsn_cb) + slv->join_dcsn_cb (slv->cb_cls, dcsn, ntohl (dcsn->is_admitted), pmsg); +} + + +static void +channel_cleanup (struct GNUNET_PSYC_Channel *chn) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "cleaning up channel %p\n", + chn); + if (NULL != chn->tmit) + { + GNUNET_PSYC_transmit_destroy (chn->tmit); + chn->tmit = NULL; + } + if (NULL != chn->recv) + { + + GNUNET_PSYC_receive_destroy (chn->recv); + chn->recv = NULL; + } + if (NULL != chn->connect_env) + { + GNUNET_MQ_discard (chn->connect_env); + chn->connect_env = NULL; + } + if (NULL != chn->mq) + { + GNUNET_MQ_destroy (chn->mq); + chn->mq = NULL; + } + if (NULL != chn->disconnect_cb) + { + chn->disconnect_cb (chn->disconnect_cls); + chn->disconnect_cb = NULL; + } + GNUNET_free (chn); +} + + +static void +handle_channel_part_ack (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + struct GNUNET_PSYC_Channel *chn = cls; + + channel_cleanup (chn); +} + + +/*** MASTER ***/ + + +static void +master_connect (struct GNUNET_PSYC_Master *mst); + + +static void +master_reconnect (void *cls) +{ + master_connect (cls); +} + + +/** + * Master client disconnected from service. + * + * Reconnect after backoff period. + */ +static void +master_disconnected (void *cls, enum GNUNET_MQ_Error error) +{ + struct GNUNET_PSYC_Master *mst = cls; + struct GNUNET_PSYC_Channel *chn = &mst->chn; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Master client disconnected (%d), re-connecting\n", + (int) error); + if (NULL != chn->tmit) + { + GNUNET_PSYC_transmit_destroy (chn->tmit); + chn->tmit = NULL; + } + if (NULL != chn->mq) + { + GNUNET_MQ_destroy (chn->mq); + chn->mq = NULL; + } + chn->reconnect_task = GNUNET_SCHEDULER_add_delayed (chn->reconnect_delay, + master_reconnect, + mst); + chn->reconnect_delay = GNUNET_TIME_STD_BACKOFF (chn->reconnect_delay); +} + + +static void +master_connect (struct GNUNET_PSYC_Master *mst) +{ + struct GNUNET_PSYC_Channel *chn = &mst->chn; + + struct GNUNET_MQ_MessageHandler handlers[] = { + GNUNET_MQ_hd_fixed_size (master_start_ack, + GNUNET_MESSAGE_TYPE_PSYC_MASTER_START_ACK, + struct GNUNET_PSYC_CountersResultMessage, + mst), + GNUNET_MQ_hd_var_size (master_join_request, + GNUNET_MESSAGE_TYPE_PSYC_JOIN_REQUEST, + struct GNUNET_PSYC_JoinRequestMessage, + mst), + GNUNET_MQ_hd_fixed_size (channel_part_ack, + GNUNET_MESSAGE_TYPE_PSYC_PART_ACK, + struct GNUNET_MessageHeader, + chn), + GNUNET_MQ_hd_var_size (channel_message, + GNUNET_MESSAGE_TYPE_PSYC_MESSAGE, + struct GNUNET_PSYC_MessageHeader, + chn), + GNUNET_MQ_hd_fixed_size (channel_message_ack, + GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_ACK, + struct GNUNET_MessageHeader, + chn), + GNUNET_MQ_hd_var_size (channel_history_result, + GNUNET_MESSAGE_TYPE_PSYC_HISTORY_RESULT, + struct GNUNET_OperationResultMessage, + chn), + GNUNET_MQ_hd_var_size (channel_state_result, + GNUNET_MESSAGE_TYPE_PSYC_STATE_RESULT, + struct GNUNET_OperationResultMessage, + chn), + GNUNET_MQ_hd_var_size (channel_result, + GNUNET_MESSAGE_TYPE_PSYC_RESULT_CODE, + struct GNUNET_OperationResultMessage, + chn), + GNUNET_MQ_handler_end () + }; + + chn->mq = GNUNET_CLIENT_connect (chn->cfg, + "psyc", + handlers, + &master_disconnected, + mst); + GNUNET_assert (NULL != chn->mq); + chn->tmit = GNUNET_PSYC_transmit_create (chn->mq); + + GNUNET_MQ_send_copy (chn->mq, chn->connect_env); +} + + +/** + * Start a PSYC master channel. + * + * Will start a multicast group identified by the given ECC key. Messages + * received from group members will be given to the respective handler methods. + * If a new member wants to join a group, the "join" method handler will be + * invoked; the join handler must then generate a "join" message to approve the + * joining of the new member. The channel can also change group membership + * without explicit requests. Note that PSYC doesn't itself "understand" join + * or part messages, the respective methods must call other PSYC functions to + * inform PSYC about the meaning of the respective events. + * + * @param cfg Configuration to use (to connect to PSYC service). + * @param channel_key ECC key that will be used to sign messages for this + * PSYC session. The public key is used to identify the PSYC channel. + * Note that end-users will usually not use the private key directly, but + * rather look it up in GNS for places managed by other users, or select + * a file with the private key(s) when setting up their own channels + * FIXME: we'll likely want to use NOT the p521 curve here, but a cheaper + * one in the future. + * @param policy Channel policy specifying join and history restrictions. + * Used to automate join decisions. + * @param message_cb Function to invoke on message parts received from slaves. + * @param join_request_cb Function to invoke when a slave wants to join. + * @param master_start_cb Function to invoke after the channel master started. + * @param cls Closure for @a method and @a join_cb. + * + * @return Handle for the channel master, NULL on error. + */ +struct GNUNET_PSYC_Master * +GNUNET_PSYC_master_start (const struct GNUNET_CONFIGURATION_Handle *cfg, + const struct GNUNET_CRYPTO_EddsaPrivateKey *channel_key, + enum GNUNET_PSYC_Policy policy, + GNUNET_PSYC_MasterStartCallback start_cb, + GNUNET_PSYC_JoinRequestCallback join_request_cb, + GNUNET_PSYC_MessageCallback message_cb, + GNUNET_PSYC_MessagePartCallback message_part_cb, + void *cls) +{ + struct GNUNET_PSYC_Master *mst = GNUNET_new (struct GNUNET_PSYC_Master); + struct GNUNET_PSYC_Channel *chn = &mst->chn; + struct MasterStartRequest *req; + + chn->connect_env = GNUNET_MQ_msg (req, + GNUNET_MESSAGE_TYPE_PSYC_MASTER_START); + req->channel_key = *channel_key; + req->policy = policy; + + chn->cfg = cfg; + chn->is_master = GNUNET_YES; + chn->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS; + + chn->op = GNUNET_OP_create (); + chn->recv = GNUNET_PSYC_receive_create (message_cb, message_part_cb, cls); + + mst->start_cb = start_cb; + mst->join_req_cb = join_request_cb; + mst->cb_cls = cls; + + master_connect (mst); + return mst; +} + + +/** + * Stop a PSYC master channel. + * + * @param master PSYC channel master to stop. + * @param keep_active FIXME + */ +void +GNUNET_PSYC_master_stop (struct GNUNET_PSYC_Master *mst, + int keep_active, + GNUNET_ContinuationCallback stop_cb, + void *stop_cls) +{ + struct GNUNET_PSYC_Channel *chn = &mst->chn; + struct GNUNET_MQ_Envelope *env; + + chn->is_disconnecting = GNUNET_YES; + chn->disconnect_cb = stop_cb; + chn->disconnect_cls = stop_cls; + env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_PSYC_PART_REQUEST); + GNUNET_MQ_send (chn->mq, env); +} + + +/** + * Function to call with the decision made for a join request. + * + * Must be called once and only once in response to an invocation of the + * #GNUNET_PSYC_JoinCallback. + * + * @param jh Join request handle. + * @param is_admitted #GNUNET_YES if the join is approved, + * #GNUNET_NO if it is disapproved, + * #GNUNET_SYSERR if we cannot answer the request. + * @param relay_count Number of relays given. + * @param relays Array of suggested peers that might be useful relays to use + * when joining the multicast group (essentially a list of peers that + * are already part of the multicast group and might thus be willing + * to help with routing). If empty, only this local peer (which must + * be the multicast origin) is a good candidate for building the + * multicast tree. Note that it is unnecessary to specify our own + * peer identity in this array. + * @param join_resp Application-dependent join response message. + * + * @return #GNUNET_OK on success, + * #GNUNET_SYSERR if the message is too large. + */ +int +GNUNET_PSYC_join_decision (struct GNUNET_PSYC_JoinHandle *jh, + int is_admitted, + uint32_t relay_count, + const struct GNUNET_PeerIdentity *relays, + const struct GNUNET_PSYC_Message *join_resp) +{ + struct GNUNET_PSYC_Channel *chn = &jh->mst->chn; + struct GNUNET_PSYC_JoinDecisionMessage *dcsn; + uint16_t join_resp_size + = (NULL != join_resp) ? ntohs (join_resp->header.size) : 0; + uint16_t relay_size = relay_count * sizeof (*relays); + + if (GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD + < sizeof (*dcsn) + relay_size + join_resp_size) + return GNUNET_SYSERR; + + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (dcsn, relay_size + join_resp_size, + GNUNET_MESSAGE_TYPE_PSYC_JOIN_DECISION); + dcsn->is_admitted = htonl (is_admitted); + dcsn->slave_pub_key = jh->slave_pub_key; + + if (0 < join_resp_size) + GNUNET_memcpy (&dcsn[1], join_resp, join_resp_size); + + GNUNET_MQ_send (chn->mq, env); + GNUNET_free (jh); + return GNUNET_OK; +} + + +/** + * Send a message to call a method to all members in the PSYC channel. + * + * @param master Handle to the PSYC channel. + * @param method_name Which method should be invoked. + * @param notify_mod Function to call to obtain modifiers. + * @param notify_data Function to call to obtain fragments of the data. + * @param notify_cls Closure for @a notify_mod and @a notify_data. + * @param flags Flags for the message being transmitted. + * + * @return Transmission handle, NULL on error (i.e. more than one request queued). + */ +struct GNUNET_PSYC_MasterTransmitHandle * +GNUNET_PSYC_master_transmit (struct GNUNET_PSYC_Master *mst, + const char *method_name, + GNUNET_PSYC_TransmitNotifyModifier notify_mod, + GNUNET_PSYC_TransmitNotifyData notify_data, + void *notify_cls, + enum GNUNET_PSYC_MasterTransmitFlags flags) +{ + if (GNUNET_OK + == GNUNET_PSYC_transmit_message (mst->chn.tmit, method_name, NULL, + notify_mod, notify_data, notify_cls, + flags)) + return (struct GNUNET_PSYC_MasterTransmitHandle *) mst->chn.tmit; + else + return NULL; +} + + +/** + * Resume transmission to the channel. + * + * @param tmit Handle of the request that is being resumed. + */ +void +GNUNET_PSYC_master_transmit_resume (struct GNUNET_PSYC_MasterTransmitHandle *tmit) +{ + GNUNET_PSYC_transmit_resume ((struct GNUNET_PSYC_TransmitHandle *) tmit); +} + + +/** + * Abort transmission request to the channel. + * + * @param tmit Handle of the request that is being aborted. + */ +void +GNUNET_PSYC_master_transmit_cancel (struct GNUNET_PSYC_MasterTransmitHandle *tmit) +{ + GNUNET_PSYC_transmit_cancel ((struct GNUNET_PSYC_TransmitHandle *) tmit); +} + + +/** + * Convert a channel @a master to a @e channel handle to access the @e channel + * APIs. + * + * @param master Channel master handle. + * + * @return Channel handle, valid for as long as @a master is valid. + */ +struct GNUNET_PSYC_Channel * +GNUNET_PSYC_master_get_channel (struct GNUNET_PSYC_Master *master) +{ + return &master->chn; +} + + +/*** SLAVE ***/ + + +static void +slave_connect (struct GNUNET_PSYC_Slave *slv); + + +static void +slave_reconnect (void *cls) +{ + slave_connect (cls); +} + + +/** + * Slave client disconnected from service. + * + * Reconnect after backoff period. + */ +static void +slave_disconnected (void *cls, + enum GNUNET_MQ_Error error) +{ + struct GNUNET_PSYC_Slave *slv = cls; + struct GNUNET_PSYC_Channel *chn = &slv->chn; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Slave client disconnected (%d), re-connecting\n", + (int) error); + if (NULL != chn->tmit) + { + GNUNET_PSYC_transmit_destroy (chn->tmit); + chn->tmit = NULL; + } + if (NULL != chn->mq) + { + GNUNET_MQ_destroy (chn->mq); + chn->mq = NULL; + } + chn->reconnect_task = GNUNET_SCHEDULER_add_delayed (chn->reconnect_delay, + &slave_reconnect, + slv); + chn->reconnect_delay = GNUNET_TIME_STD_BACKOFF (chn->reconnect_delay); +} + + +static void +slave_connect (struct GNUNET_PSYC_Slave *slv) +{ + struct GNUNET_PSYC_Channel *chn = &slv->chn; + + struct GNUNET_MQ_MessageHandler handlers[] = { + GNUNET_MQ_hd_fixed_size (slave_join_ack, + GNUNET_MESSAGE_TYPE_PSYC_SLAVE_JOIN_ACK, + struct GNUNET_PSYC_CountersResultMessage, + slv), + GNUNET_MQ_hd_var_size (slave_join_decision, + GNUNET_MESSAGE_TYPE_PSYC_JOIN_DECISION, + struct GNUNET_PSYC_JoinDecisionMessage, + slv), + GNUNET_MQ_hd_fixed_size (channel_part_ack, + GNUNET_MESSAGE_TYPE_PSYC_PART_ACK, + struct GNUNET_MessageHeader, + chn), + GNUNET_MQ_hd_var_size (channel_message, + GNUNET_MESSAGE_TYPE_PSYC_MESSAGE, + struct GNUNET_PSYC_MessageHeader, + chn), + GNUNET_MQ_hd_fixed_size (channel_message_ack, + GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_ACK, + struct GNUNET_MessageHeader, + chn), + GNUNET_MQ_hd_var_size (channel_history_result, + GNUNET_MESSAGE_TYPE_PSYC_HISTORY_RESULT, + struct GNUNET_OperationResultMessage, + chn), + GNUNET_MQ_hd_var_size (channel_state_result, + GNUNET_MESSAGE_TYPE_PSYC_STATE_RESULT, + struct GNUNET_OperationResultMessage, + chn), + GNUNET_MQ_hd_var_size (channel_result, + GNUNET_MESSAGE_TYPE_PSYC_RESULT_CODE, + struct GNUNET_OperationResultMessage, + chn), + GNUNET_MQ_handler_end () + }; + + chn->mq = GNUNET_CLIENT_connect (chn->cfg, + "psyc", + handlers, + &slave_disconnected, + slv); + if (NULL == chn->mq) + { + chn->reconnect_task = GNUNET_SCHEDULER_add_delayed (chn->reconnect_delay, + &slave_reconnect, + slv); + chn->reconnect_delay = GNUNET_TIME_STD_BACKOFF (chn->reconnect_delay); + return; + } + chn->tmit = GNUNET_PSYC_transmit_create (chn->mq); + + GNUNET_MQ_send_copy (chn->mq, chn->connect_env); +} + + +/** + * Join a PSYC channel. + * + * The entity joining is always the local peer. The user must immediately use + * the GNUNET_PSYC_slave_transmit() functions to transmit a @e join_msg to the + * channel; if the join request succeeds, the channel state (and @e recent + * method calls) will be replayed to the joining member. There is no explicit + * notification on failure (as the channel may simply take days to approve, + * and disapproval is simply being ignored). + * + * @param cfg + * Configuration to use. + * @param channel_key ECC public key that identifies the channel we wish to join. + * @param slave_key ECC private-public key pair that identifies the slave, and + * used by multicast to sign the join request and subsequent unicast + * requests sent to the master. + * @param origin Peer identity of the origin. + * @param relay_count Number of peers in the @a relays array. + * @param relays Peer identities of members of the multicast group, which serve + * as relays and used to join the group at. + * @param message_cb Function to invoke on message parts received from the + * channel, typically at least contains method handlers for @e join and + * @e part. + * @param slave_connect_cb Function invoked once we have connected to the + * PSYC service. + * @param join_decision_cb Function invoked once we have received a join + * decision. + * @param cls Closure for @a message_cb and @a slave_joined_cb. + * @param method_name Method name for the join request. + * @param env Environment containing transient variables for the request, or NULL. + * @param data Payload for the join message. + * @param data_size Number of bytes in @a data. + * + * @return Handle for the slave, NULL on error. + */ +struct GNUNET_PSYC_Slave * +GNUNET_PSYC_slave_join (const struct GNUNET_CONFIGURATION_Handle *cfg, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_pub_key, + const struct GNUNET_CRYPTO_EcdsaPrivateKey *slave_key, + enum GNUNET_PSYC_SlaveJoinFlags flags, + const struct GNUNET_PeerIdentity *origin, + uint32_t relay_count, + const struct GNUNET_PeerIdentity *relays, + GNUNET_PSYC_MessageCallback message_cb, + GNUNET_PSYC_MessagePartCallback message_part_cb, + GNUNET_PSYC_SlaveConnectCallback connect_cb, + GNUNET_PSYC_JoinDecisionCallback join_decision_cb, + void *cls, + const struct GNUNET_PSYC_Message *join_msg) +{ + struct GNUNET_PSYC_Slave *slv = GNUNET_malloc (sizeof (*slv)); + struct GNUNET_PSYC_Channel *chn = &slv->chn; + uint16_t relay_size = relay_count * sizeof (*relays); + uint16_t join_msg_size; + if (NULL == join_msg) + join_msg_size = 0; + else + join_msg_size = ntohs (join_msg->header.size); + + struct SlaveJoinRequest *req; + chn->connect_env = GNUNET_MQ_msg_extra (req, relay_size + join_msg_size, + GNUNET_MESSAGE_TYPE_PSYC_SLAVE_JOIN); + req->channel_pub_key = *channel_pub_key; + req->slave_key = *slave_key; + req->origin = *origin; + req->relay_count = htonl (relay_count); + req->flags = htonl (flags); + + if (0 < relay_size) + GNUNET_memcpy (&req[1], relays, relay_size); + + if (NULL != join_msg) + GNUNET_memcpy ((char *) &req[1] + relay_size, join_msg, join_msg_size); + + chn->cfg = cfg; + chn->is_master = GNUNET_NO; + chn->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS; + + chn->op = GNUNET_OP_create (); + chn->recv = GNUNET_PSYC_receive_create (message_cb, message_part_cb, cls); + + slv->connect_cb = connect_cb; + slv->join_dcsn_cb = join_decision_cb; + slv->cb_cls = cls; + + slave_connect (slv); + return slv; +} + + +/** + * Part a PSYC channel. + * + * Will terminate the connection to the PSYC service. Polite clients should + * first explicitly send a part request (via GNUNET_PSYC_slave_transmit()). + * + * @param slave Slave handle. + */ +void +GNUNET_PSYC_slave_part (struct GNUNET_PSYC_Slave *slv, + int keep_active, + GNUNET_ContinuationCallback part_cb, + void *part_cls) +{ + struct GNUNET_PSYC_Channel *chn = &slv->chn; + struct GNUNET_MQ_Envelope *env; + + chn->is_disconnecting = GNUNET_YES; + chn->disconnect_cb = part_cb; + chn->disconnect_cls = part_cls; + env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_PSYC_PART_REQUEST); + GNUNET_MQ_send (chn->mq, env); +} + + +/** + * Request a message to be sent to the channel master. + * + * @param slave Slave handle. + * @param method_name Which (PSYC) method should be invoked (on host). + * @param notify_mod Function to call to obtain modifiers. + * @param notify_data Function to call to obtain fragments of the data. + * @param notify_cls Closure for @a notify. + * @param flags Flags for the message being transmitted. + * + * @return Transmission handle, NULL on error (i.e. more than one request + * queued). + */ +struct GNUNET_PSYC_SlaveTransmitHandle * +GNUNET_PSYC_slave_transmit (struct GNUNET_PSYC_Slave *slv, + const char *method_name, + GNUNET_PSYC_TransmitNotifyModifier notify_mod, + GNUNET_PSYC_TransmitNotifyData notify_data, + void *notify_cls, + enum GNUNET_PSYC_SlaveTransmitFlags flags) + +{ + if (GNUNET_OK + == GNUNET_PSYC_transmit_message (slv->chn.tmit, method_name, NULL, + notify_mod, notify_data, notify_cls, + flags)) + return (struct GNUNET_PSYC_SlaveTransmitHandle *) slv->chn.tmit; + else + return NULL; +} + + +/** + * Resume transmission to the master. + * + * @param tmit Handle of the request that is being resumed. + */ +void +GNUNET_PSYC_slave_transmit_resume (struct GNUNET_PSYC_SlaveTransmitHandle *tmit) +{ + GNUNET_PSYC_transmit_resume ((struct GNUNET_PSYC_TransmitHandle *) tmit); +} + + +/** + * Abort transmission request to master. + * + * @param tmit Handle of the request that is being aborted. + */ +void +GNUNET_PSYC_slave_transmit_cancel (struct GNUNET_PSYC_SlaveTransmitHandle *tmit) +{ + GNUNET_PSYC_transmit_cancel ((struct GNUNET_PSYC_TransmitHandle *) tmit); +} + + +/** + * Convert @a slave to a @e channel handle to access the @e channel APIs. + * + * @param slv Slave handle. + * + * @return Channel handle, valid for as long as @a slave is valid. + */ +struct GNUNET_PSYC_Channel * +GNUNET_PSYC_slave_get_channel (struct GNUNET_PSYC_Slave *slv) +{ + return &slv->chn; +} + + +/** + * Add a slave to the channel's membership list. + * + * Note that this will NOT generate any PSYC traffic, it will merely update the + * local database to modify how we react to membership test queries. + * The channel master still needs to explicitly transmit a @e join message to + * notify other channel members and they then also must still call this function + * in their respective methods handling the @e join message. This way, how @e + * join and @e part operations are exactly implemented is still up to the + * application; for example, there might be a @e part_all method to kick out + * everyone. + * + * Note that channel slaves are explicitly trusted to execute such methods + * correctly; not doing so correctly will result in either denying other slaves + * access or offering access to channel data to non-members. + * + * @param chn + * Channel handle. + * @param slave_pub_key + * Identity of channel slave to add. + * @param announced_at + * ID of the message that announced the membership change. + * @param effective_since + * Addition of slave is in effect since this message ID. + * @param result_cb + * Function to call with the result of the operation. + * The @e result_code argument is #GNUNET_OK on success, or + * #GNUNET_SYSERR on error. In case of an error, the @e data argument + * can contain an optional error message. + * @param cls + * Closure for @a result_cb. + */ +void +GNUNET_PSYC_channel_slave_add (struct GNUNET_PSYC_Channel *chn, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_pub_key, + uint64_t announced_at, + uint64_t effective_since, + GNUNET_ResultCallback result_cb, + void *cls) +{ + struct ChannelMembershipStoreRequest *req; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (req, GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_MEMBERSHIP_STORE); + req->slave_pub_key = *slave_pub_key; + req->announced_at = GNUNET_htonll (announced_at); + req->effective_since = GNUNET_htonll (effective_since); + req->did_join = GNUNET_YES; + req->op_id = GNUNET_htonll (GNUNET_OP_add (chn->op, result_cb, cls, NULL)); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "GNUNET_PSYC_channel_slave_add, OP ID: %" PRIu64 "\n", + GNUNET_ntohll (req->op_id)); + GNUNET_MQ_send (chn->mq, env); +} + + +/** + * Remove a slave from the channel's membership list. + * + * Note that this will NOT generate any PSYC traffic, it will merely update the + * local database to modify how we react to membership test queries. + * The channel master still needs to explicitly transmit a @e part message to + * notify other channel members and they then also must still call this function + * in their respective methods handling the @e part message. This way, how + * @e join and @e part operations are exactly implemented is still up to the + * application; for example, there might be a @e part_all message to kick out + * everyone. + * + * Note that channel members are explicitly trusted to perform these + * operations correctly; not doing so correctly will result in either + * denying members access or offering access to channel data to + * non-members. + * + * @param chn + * Channel handle. + * @param slave_pub_key + * Identity of channel slave to remove. + * @param announced_at + * ID of the message that announced the membership change. + * @param result_cb + * Function to call with the result of the operation. + * The @e result_code argument is #GNUNET_OK on success, or + * #GNUNET_SYSERR on error. In case of an error, the @e data argument + * can contain an optional error message. + * @param cls + * Closure for @a result_cb. + */ +void +GNUNET_PSYC_channel_slave_remove (struct GNUNET_PSYC_Channel *chn, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_pub_key, + uint64_t announced_at, + GNUNET_ResultCallback result_cb, + void *cls) +{ + struct ChannelMembershipStoreRequest *req; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (req, GNUNET_MESSAGE_TYPE_PSYC_CHANNEL_MEMBERSHIP_STORE); + req->slave_pub_key = *slave_pub_key; + req->announced_at = GNUNET_htonll (announced_at); + req->did_join = GNUNET_NO; + req->op_id = GNUNET_htonll (GNUNET_OP_add (chn->op, result_cb, cls, NULL)); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "GNUNET_PSYC_channel_slave_remove, OP ID: %" PRIu64 "\n", + GNUNET_ntohll (req->op_id)); + GNUNET_MQ_send (chn->mq, env); +} + + +static struct GNUNET_PSYC_HistoryRequest * +channel_history_replay (struct GNUNET_PSYC_Channel *chn, + uint64_t start_message_id, + uint64_t end_message_id, + uint64_t message_limit, + const char *method_prefix, + uint32_t flags, + GNUNET_PSYC_MessageCallback message_cb, + GNUNET_PSYC_MessagePartCallback message_part_cb, + GNUNET_ResultCallback result_cb, + void *cls) +{ + struct GNUNET_PSYC_HistoryRequestMessage *req; + struct GNUNET_PSYC_HistoryRequest *hist = GNUNET_malloc (sizeof (*hist)); + hist->chn = chn; + hist->recv = GNUNET_PSYC_receive_create (message_cb, message_part_cb, cls); + hist->result_cb = result_cb; + hist->cls = cls; + hist->op_id = GNUNET_OP_add (chn->op, op_recv_history_result, hist, NULL); + + GNUNET_assert (NULL != method_prefix); + uint16_t method_size = strnlen (method_prefix, + GNUNET_MAX_MESSAGE_SIZE + - sizeof (*req)) + 1; + GNUNET_assert ('\0' == method_prefix[method_size - 1]); + + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (req, method_size, + GNUNET_MESSAGE_TYPE_PSYC_HISTORY_REPLAY); + req->start_message_id = GNUNET_htonll (start_message_id); + req->end_message_id = GNUNET_htonll (end_message_id); + req->message_limit = GNUNET_htonll (message_limit); + req->flags = htonl (flags); + req->op_id = GNUNET_htonll (hist->op_id); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "channel_history_replay, OP ID: %" PRIu64 "\n", + GNUNET_ntohll (req->op_id)); + GNUNET_memcpy (&req[1], method_prefix, method_size); + + GNUNET_MQ_send (chn->mq, env); + return hist; +} + + +/** + * Request to replay a part of the message history of the channel. + * + * Historic messages (but NOT the state at the time) will be replayed and given + * to the normal method handlers with a #GNUNET_PSYC_MESSAGE_HISTORIC flag set. + * + * Messages are retrieved from the local PSYCstore if available, + * otherwise requested from the network. + * + * @param channel + * Which channel should be replayed? + * @param start_message_id + * Earliest interesting point in history. + * @param end_message_id + * Last (inclusive) interesting point in history. + * @param method_prefix + * Retrieve only messages with a matching method prefix. + * @param flags + * OR'ed enum GNUNET_PSYC_HistoryReplayFlags + * @param result_cb + * Function to call when the requested history has been fully replayed. + * @param cls + * Closure for the callbacks. + * + * @return Handle to cancel history replay operation. + */ +struct GNUNET_PSYC_HistoryRequest * +GNUNET_PSYC_channel_history_replay (struct GNUNET_PSYC_Channel *chn, + uint64_t start_message_id, + uint64_t end_message_id, + const char *method_prefix, + uint32_t flags, + GNUNET_PSYC_MessageCallback message_cb, + GNUNET_PSYC_MessagePartCallback message_part_cb, + GNUNET_ResultCallback result_cb, + void *cls) +{ + return channel_history_replay (chn, start_message_id, end_message_id, 0, + method_prefix, flags, + message_cb, message_part_cb, result_cb, cls); +} + + +/** + * Request to replay the latest messages from the message history of the channel. + * + * Historic messages (but NOT the state at the time) will be replayed (given to + * the normal method handlers) if available and if access is permitted. + * + * @param channel + * Which channel should be replayed? + * @param message_limit + * Maximum number of messages to replay. + * @param method_prefix + * Retrieve only messages with a matching method prefix. + * Use NULL or "" to retrieve all. + * @param flags + * OR'ed enum GNUNET_PSYC_HistoryReplayFlags + * @param result_cb + * Function to call when the requested history has been fully replayed. + * @param cls + * Closure for the callbacks. + * + * @return Handle to cancel history replay operation. + */ +struct GNUNET_PSYC_HistoryRequest * +GNUNET_PSYC_channel_history_replay_latest (struct GNUNET_PSYC_Channel *chn, + uint64_t message_limit, + const char *method_prefix, + uint32_t flags, + GNUNET_PSYC_MessageCallback message_cb, + GNUNET_PSYC_MessagePartCallback message_part_cb, + GNUNET_ResultCallback result_cb, + void *cls) +{ + return channel_history_replay (chn, 0, 0, message_limit, method_prefix, flags, + message_cb, message_part_cb, result_cb, cls); +} + + +void +GNUNET_PSYC_channel_history_replay_cancel (struct GNUNET_PSYC_Channel *channel, + struct GNUNET_PSYC_HistoryRequest *hist) +{ + GNUNET_PSYC_receive_destroy (hist->recv); + GNUNET_OP_remove (hist->chn->op, hist->op_id); + GNUNET_free (hist); +} + + +/** + * Retrieve the best matching channel state variable. + * + * If the requested variable name is not present in the state, the nearest + * less-specific name is matched; for example, requesting "_a_b" will match "_a" + * if "_a_b" does not exist. + * + * @param channel + * Channel handle. + * @param full_name + * Full name of the requested variable. + * The actual variable returned might have a shorter name. + * @param var_cb + * Function called once when a matching state variable is found. + * Not called if there's no matching state variable. + * @param result_cb + * Function called after the operation finished. + * (i.e. all state variables have been returned via @a state_cb) + * @param cls + * Closure for the callbacks. + */ +static struct GNUNET_PSYC_StateRequest * +channel_state_get (struct GNUNET_PSYC_Channel *chn, + uint16_t type, const char *name, + GNUNET_PSYC_StateVarCallback var_cb, + GNUNET_ResultCallback result_cb, void *cls) +{ + struct StateRequest *req; + struct GNUNET_PSYC_StateRequest *sr = GNUNET_malloc (sizeof (*sr)); + sr->chn = chn; + sr->var_cb = var_cb; + sr->result_cb = result_cb; + sr->cls = cls; + sr->op_id = GNUNET_OP_add (chn->op, op_recv_state_result, sr, NULL); + + GNUNET_assert (NULL != name); + size_t name_size = strnlen (name, GNUNET_MAX_MESSAGE_SIZE + - sizeof (*req)) + 1; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (req, name_size, type); + req->op_id = GNUNET_htonll (sr->op_id); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "channel_state_get, OP ID: %" PRIu64 "\n", + GNUNET_ntohll (req->op_id)); + + GNUNET_memcpy (&req[1], name, name_size); + + GNUNET_MQ_send (chn->mq, env); + return sr; +} + + +/** + * Retrieve the best matching channel state variable. + * + * If the requested variable name is not present in the state, the nearest + * less-specific name is matched; for example, requesting "_a_b" will match "_a" + * if "_a_b" does not exist. + * + * @param channel + * Channel handle. + * @param full_name + * Full name of the requested variable. + * The actual variable returned might have a shorter name. + * @param var_cb + * Function called once when a matching state variable is found. + * Not called if there's no matching state variable. + * @param result_cb + * Function called after the operation finished. + * (i.e. all state variables have been returned via @a state_cb) + * @param cls + * Closure for the callbacks. + */ +struct GNUNET_PSYC_StateRequest * +GNUNET_PSYC_channel_state_get (struct GNUNET_PSYC_Channel *chn, + const char *full_name, + GNUNET_PSYC_StateVarCallback var_cb, + GNUNET_ResultCallback result_cb, + void *cls) +{ + return channel_state_get (chn, GNUNET_MESSAGE_TYPE_PSYC_STATE_GET, + full_name, var_cb, result_cb, cls); + +} + + +/** + * Return all channel state variables whose name matches a given prefix. + * + * A name matches if it starts with the given @a name_prefix, thus requesting + * the empty prefix ("") will match all values; requesting "_a_b" will also + * return values stored under "_a_b_c". + * + * The @a state_cb is invoked on all matching state variables asynchronously, as + * the state is stored in and retrieved from the PSYCstore, + * + * @param channel + * Channel handle. + * @param name_prefix + * Prefix of the state variable name to match. + * @param var_cb + * Function called once when a matching state variable is found. + * Not called if there's no matching state variable. + * @param result_cb + * Function called after the operation finished. + * (i.e. all state variables have been returned via @a state_cb) + * @param cls + * Closure for the callbacks. + */ +struct GNUNET_PSYC_StateRequest * +GNUNET_PSYC_channel_state_get_prefix (struct GNUNET_PSYC_Channel *chn, + const char *name_prefix, + GNUNET_PSYC_StateVarCallback var_cb, + GNUNET_ResultCallback result_cb, + void *cls) +{ + return channel_state_get (chn, GNUNET_MESSAGE_TYPE_PSYC_STATE_GET_PREFIX, + name_prefix, var_cb, result_cb, cls); +} + + +/** + * Cancel a state request operation. + * + * @param sr + * Handle for the operation to cancel. + */ +void +GNUNET_PSYC_channel_state_get_cancel (struct GNUNET_PSYC_StateRequest *sr) +{ + GNUNET_OP_remove (sr->chn->op, sr->op_id); + GNUNET_free (sr); +} + +/* end of psyc_api.c */ diff --git a/src/psyc/psyc_test_lib.h b/src/psyc/psyc_test_lib.h new file mode 100644 index 0000000..0ad9910 --- /dev/null +++ b/src/psyc/psyc_test_lib.h @@ -0,0 +1,67 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file psyc/test_psyc_api_join.c + * @brief library for writing psyc tests + * @author xrs + */ + +#define MAX_TESTBED_OPS 32 + +struct pctx +{ + int idx; + + struct GNUNET_TESTBED_Peer *testbed_peer; + + const struct GNUNET_PeerIdentity *peer_id; + + const struct GNUNET_PeerIdentity *peer_id_master; + + /** + * Used to simulate egos (not peerid) + */ + const struct GNUNET_CRYPTO_EcdsaPrivateKey *id_key; + + const struct GNUNET_CRYPTO_EcdsaPublicKey *id_pub_key; + + /** + * Used to store either GNUNET_PSYC_Master or GNUNET_PSYC_Slave handle + */ + void *psyc; + + struct GNUNET_PSYC_Channel *channel; + + const struct GNUNET_CRYPTO_EddsaPrivateKey *channel_key; + + struct GNUNET_CRYPTO_EddsaPublicKey *channel_pub_key; + + int test_ok; +}; + +static struct GNUNET_SCHEDULER_Task *timeout_task_id; + +static int result = GNUNET_SYSERR; + +static struct GNUNET_TESTBED_Operation *op[MAX_TESTBED_OPS]; + +static int op_cnt = 0; + diff --git a/src/psyc/test_psyc.c b/src/psyc/test_psyc.c new file mode 100644 index 0000000..b6e27bb --- /dev/null +++ b/src/psyc/test_psyc.c @@ -0,0 +1,1018 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file psyc/test_psyc.c + * @brief Tests for the PSYC API. + * @author Gabor X Toth + * @author Christian Grothoff + */ + +#include + +#include "platform.h" +#include "gnunet_crypto_lib.h" +#include "gnunet_common.h" +#include "gnunet_util_lib.h" +#include "gnunet_testing_lib.h" +#include "gnunet_psyc_util_lib.h" +#include "gnunet_psyc_service.h" + +#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30) + +/** + * Return value from 'main'. + */ +static int res; + +static const struct GNUNET_CONFIGURATION_Handle *cfg; + +static struct GNUNET_PeerIdentity this_peer; + +/** + * Handle for task for timeout termination. + */ +static struct GNUNET_SCHEDULER_Task * end_badly_task; + +static struct GNUNET_PSYC_Master *mst; +static struct GNUNET_PSYC_Slave *slv; + +static struct GNUNET_PSYC_Channel *mst_chn, *slv_chn; + +static struct GNUNET_CRYPTO_EddsaPrivateKey *channel_key; +static struct GNUNET_CRYPTO_EcdsaPrivateKey *slave_key; + +static struct GNUNET_CRYPTO_EddsaPublicKey channel_pub_key; +static struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key; + +struct TransmitClosure +{ + struct GNUNET_PSYC_MasterTransmitHandle *mst_tmit; + struct GNUNET_PSYC_SlaveTransmitHandle *slv_tmit; + struct GNUNET_PSYC_Environment *env; + struct GNUNET_PSYC_Modifier *mod; + char *data[16]; + const char *mod_value; + size_t mod_value_size; + uint8_t data_delay[16]; + uint8_t data_count; + uint8_t paused; + uint8_t n; +}; + +static struct TransmitClosure *tmit; + +static uint8_t join_req_count, end_count; + +enum +{ + TEST_NONE = 0, + TEST_MASTER_START = 1, + TEST_SLAVE_JOIN_REJECT = 2, + TEST_SLAVE_JOIN_ACCEPT = 3, + TEST_SLAVE_ADD = 4, + TEST_SLAVE_REMOVE = 5, + TEST_SLAVE_TRANSMIT = 6, + TEST_MASTER_TRANSMIT = 7, + TEST_MASTER_HISTORY_REPLAY_LATEST = 8, + TEST_SLAVE_HISTORY_REPLAY_LATEST = 9, + TEST_MASTER_HISTORY_REPLAY = 10, + TEST_SLAVE_HISTORY_REPLAY = 11, + TEST_MASTER_STATE_GET = 12, + TEST_SLAVE_STATE_GET = 13, + TEST_MASTER_STATE_GET_PREFIX = 14, + TEST_SLAVE_STATE_GET_PREFIX = 15, +} test; + + +static void +master_transmit (); + +static void +master_history_replay_latest (); + + +static void +master_stopped (void *cls) +{ + if (NULL != tmit) + { + GNUNET_PSYC_env_destroy (tmit->env); + GNUNET_free (tmit); + tmit = NULL; + } + GNUNET_SCHEDULER_shutdown (); +} + + +static void +slave_parted (void *cls) +{ + if (NULL != mst) + { + GNUNET_PSYC_master_stop (mst, GNUNET_NO, &master_stopped, NULL); + mst = NULL; + } + else + master_stopped (NULL); +} + + +/** + * Clean up all resources used. + */ +static void +cleanup () +{ + if (NULL != slv) + { + GNUNET_PSYC_slave_part (slv, GNUNET_NO, &slave_parted, NULL); + slv = NULL; + } + else + slave_parted (NULL); +} + + +/** + * Terminate the test case (failure). + * + * @param cls NULL + */ +static void +end_badly (void *cls) +{ + res = 1; + cleanup (); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Test FAILED.\n"); +} + + +/** + * Terminate the test case (success). + * + * @param cls NULL + */ +static void +end_normally (void *cls) +{ + res = 0; + cleanup (); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "Test PASSED.\n"); +} + + +/** + * Finish the test case (successfully). + */ +static void +end () +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Ending tests.\n"); + + if (end_badly_task != NULL) + { + GNUNET_SCHEDULER_cancel (end_badly_task); + end_badly_task = NULL; + } + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MILLISECONDS, + &end_normally, NULL); +} + + +static void +master_message_cb (void *cls, const struct GNUNET_PSYC_MessageHeader *msg) +{ + GNUNET_assert (NULL != msg); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: Master got PSYC message fragment of size %u " + "belonging to message ID %" PRIu64 " with flags %x\n", + test, ntohs (msg->header.size), + GNUNET_ntohll (msg->message_id), ntohl (msg->flags)); + // FIXME +} + + +static void +master_message_part_cb (void *cls, const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg) +{ + GNUNET_assert (NULL != msg && NULL != pmsg); + + uint64_t message_id = GNUNET_ntohll (msg->message_id); + uint32_t flags = ntohl (msg->flags); + + uint16_t type = ntohs (pmsg->type); + uint16_t size = ntohs (pmsg->size); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: Master got message part of type %u and size %u " + "belonging to message ID %" PRIu64 " with flags %x\n", + test, type, size, message_id, flags); + + switch (test) + { + case TEST_SLAVE_TRANSMIT: + if (GNUNET_PSYC_MESSAGE_REQUEST != flags) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Test #%d: Unexpected request flags: %x" PRIu32 "\n", + test, flags); + GNUNET_assert (0); + return; + } + // FIXME: check rest of message + + if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END == type) + master_transmit (); + break; + + case TEST_MASTER_TRANSMIT: + if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END == type && 2 == ++end_count) + master_history_replay_latest (); + break; + + case TEST_MASTER_HISTORY_REPLAY: + case TEST_MASTER_HISTORY_REPLAY_LATEST: + if (GNUNET_PSYC_MESSAGE_HISTORIC != flags) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Test #%d: Unexpected flags for historic message: %x" PRIu32 "\n", + test, flags); + GNUNET_assert (0); + return; + } + break; + + default: + GNUNET_assert (0); + } +} + + +static void +slave_message_cb (void *cls, const struct GNUNET_PSYC_MessageHeader *msg) +{ + GNUNET_assert (NULL != msg); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: Slave got PSYC message fragment of size %u " + "belonging to message ID %" PRIu64 " with flags %x\n", + test, ntohs (msg->header.size), + GNUNET_ntohll (msg->message_id), ntohl (msg->flags)); + // FIXME +} + + +static void +slave_message_part_cb (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg) +{ + GNUNET_assert (NULL != msg && NULL != pmsg); + + uint64_t message_id = GNUNET_ntohll (msg->message_id); + uint32_t flags = ntohl (msg->flags); + + uint16_t type = ntohs (pmsg->type); + uint16_t size = ntohs (pmsg->size); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: Slave got message part of type %u and size %u " + "belonging to message ID %" PRIu64 " with flags %x\n", + test, type, size, message_id, flags); + + switch (test) + { + case TEST_MASTER_TRANSMIT: + if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END == type && 2 == ++end_count) + master_history_replay_latest (); + break; + + case TEST_SLAVE_HISTORY_REPLAY: + case TEST_SLAVE_HISTORY_REPLAY_LATEST: + if (GNUNET_PSYC_MESSAGE_HISTORIC != flags) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Test #%d: Unexpected flags for historic message: %x" PRIu32 "\n", + test, flags); + GNUNET_assert (0); + return; + } + break; + + default: + GNUNET_assert (0); + } +} + + +static void +state_get_var (void *cls, const struct GNUNET_MessageHeader *mod, + const char *name, const void *value, + uint32_t value_size, uint32_t full_value_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Got state var: %s\n%.*s\n", + name, + (int) value_size, + (const char *) value); +} + + +/*** Slave state_get_prefix() ***/ + +static void +slave_state_get_prefix_result (void *cls, int64_t result, + const void *err_msg, uint16_t err_msg_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: slave_state_get_prefix:\t%" PRId64 " (%.*s)\n", + test, result, + (int) err_msg_size, + (const char *) err_msg); + // FIXME: GNUNET_assert (2 == result); + end (); +} + + +static void +slave_state_get_prefix () +{ + test = TEST_SLAVE_STATE_GET_PREFIX; + GNUNET_PSYC_channel_state_get_prefix (slv_chn, "_foo", state_get_var, + slave_state_get_prefix_result, NULL); +} + + +/*** Master state_get_prefix() ***/ + + +static void +master_state_get_prefix_result (void *cls, int64_t result, + const void *err_msg, uint16_t err_msg_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: master_state_get_prefix:\t%" PRId64 " (%s)\n", + test, result, (char *) err_msg); + // FIXME: GNUNET_assert (2 == result); + slave_state_get_prefix (); +} + + +static void +master_state_get_prefix () +{ + test = TEST_MASTER_STATE_GET_PREFIX; + GNUNET_PSYC_channel_state_get_prefix (mst_chn, "_foo", state_get_var, + master_state_get_prefix_result, NULL); +} + + +/*** Slave state_get() ***/ + + +static void +slave_state_get_result (void *cls, int64_t result, + const void *err_msg, uint16_t err_msg_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: slave_state_get:\t%" PRId64 " (%.*s)\n", + test, result, err_msg_size, (char *) err_msg); + // FIXME: GNUNET_assert (2 == result); + master_state_get_prefix (); +} + + +static void +slave_state_get () +{ + test = TEST_SLAVE_STATE_GET; + GNUNET_PSYC_channel_state_get (slv_chn, "_foo_bar_baz", state_get_var, + slave_state_get_result, NULL); +} + + +/*** Master state_get() ***/ + + +static void +master_state_get_result (void *cls, int64_t result, + const void *err_msg, uint16_t err_msg_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: master_state_get:\t%" PRId64 " (%.*s)\n", + test, result, err_msg_size, (char *) err_msg); + // FIXME: GNUNET_assert (1 == result); + slave_state_get (); +} + + +static void +master_state_get () +{ + test = TEST_MASTER_STATE_GET; + GNUNET_PSYC_channel_state_get (mst_chn, "_foo_bar_baz", state_get_var, + master_state_get_result, NULL); +} + + +/*** Slave history_replay() ***/ + +static void +slave_history_replay_result (void *cls, int64_t result, + const void *err_msg, uint16_t err_msg_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: slave_history_replay:\t%" PRId64 " (%.*s)\n", + test, result, + (int) err_msg_size, + (const char *) err_msg); + GNUNET_assert (9 == result); + + master_state_get (); +} + + +static void +slave_history_replay () +{ + test = TEST_SLAVE_HISTORY_REPLAY; + GNUNET_PSYC_channel_history_replay (slv_chn, 1, 1, "", + GNUNET_PSYC_HISTORY_REPLAY_LOCAL, + slave_message_cb, + slave_message_part_cb, + slave_history_replay_result, NULL); +} + + +/*** Master history_replay() ***/ + + +static void +master_history_replay_result (void *cls, int64_t result, + const void *err_msg, uint16_t err_msg_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: master_history_replay:\t%" PRId64 " (%.*s)\n", + test, result, + (int) err_msg_size, + (const char *) err_msg); + GNUNET_assert (9 == result); + + slave_history_replay (); +} + + +static void +master_history_replay () +{ + test = TEST_MASTER_HISTORY_REPLAY; + GNUNET_PSYC_channel_history_replay (mst_chn, 1, 1, "", + GNUNET_PSYC_HISTORY_REPLAY_LOCAL, + master_message_cb, + master_message_part_cb, + master_history_replay_result, NULL); +} + + +/*** Slave history_replay_latest() ***/ + + +static void +slave_history_replay_latest_result (void *cls, int64_t result, + const void *err_msg, uint16_t err_msg_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: slave_history_replay_latest:\t%" PRId64 " (%.*s)\n", + test, result, + (int) err_msg_size, + (const char *) err_msg); + GNUNET_assert (9 == result); + + master_history_replay (); +} + + +static void +slave_history_replay_latest () +{ + test = TEST_SLAVE_HISTORY_REPLAY_LATEST; + GNUNET_PSYC_channel_history_replay_latest (slv_chn, 1, "", + GNUNET_PSYC_HISTORY_REPLAY_LOCAL, + &slave_message_cb, + &slave_message_part_cb, + &slave_history_replay_latest_result, + NULL); +} + + +/*** Master history_replay_latest() ***/ + + +static void +master_history_replay_latest_result (void *cls, int64_t result, + const void *err_msg, uint16_t err_msg_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: master_history_replay_latest:\t%" PRId64 " (%.*s)\n", + test, result, err_msg_size, (char *) err_msg); + GNUNET_assert (9 == result); + + slave_history_replay_latest (); +} + + +static void +master_history_replay_latest () +{ + test = TEST_MASTER_HISTORY_REPLAY_LATEST; + GNUNET_PSYC_channel_history_replay_latest (mst_chn, 1, "", + GNUNET_PSYC_HISTORY_REPLAY_LOCAL, + &master_message_cb, + &master_message_part_cb, + &master_history_replay_latest_result, + NULL); +} + + +static void +transmit_resume (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Test #%d: Transmission resumed.\n", test); + struct TransmitClosure *tmit = cls; + if (NULL != tmit->mst_tmit) + GNUNET_PSYC_master_transmit_resume (tmit->mst_tmit); + else + GNUNET_PSYC_slave_transmit_resume (tmit->slv_tmit); +} + + +static int +tmit_notify_data (void *cls, uint16_t *data_size, void *data) +{ + struct TransmitClosure *tmit = cls; + if (0 == tmit->data_count) + { + *data_size = 0; + return GNUNET_YES; + } + + uint16_t size = strlen (tmit->data[tmit->n]); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Test #%d: Transmit notify data: %u bytes available, " + "processing fragment %u/%u (size %u).\n", + test, *data_size, tmit->n + 1, tmit->data_count, size); + if (*data_size < size) + { + *data_size = 0; + GNUNET_assert (0); + return GNUNET_SYSERR; + } + + if (GNUNET_YES != tmit->paused && 0 < tmit->data_delay[tmit->n]) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Test #%d: Transmission paused.\n", test); + tmit->paused = GNUNET_YES; + GNUNET_SCHEDULER_add_delayed ( + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, + tmit->data_delay[tmit->n]), + &transmit_resume, tmit); + *data_size = 0; + return GNUNET_NO; + } + tmit->paused = GNUNET_NO; + + *data_size = size; + GNUNET_memcpy (data, tmit->data[tmit->n], size); + + return ++tmit->n < tmit->data_count ? GNUNET_NO : GNUNET_YES; +} + + +static int +tmit_notify_mod (void *cls, uint16_t *data_size, void *data, uint8_t *oper, + uint32_t *full_value_size) +{ + struct TransmitClosure *tmit = cls; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Test #%d: Transmit notify modifier: %u bytes available, " + "%u modifiers left to process.\n", + test, *data_size, (unsigned int) GNUNET_PSYC_env_get_count (tmit->env)); + + uint16_t name_size = 0; + size_t value_size = 0; + const char *value = NULL; + + if (NULL != oper && NULL != tmit->mod) + { /* New modifier */ + tmit->mod = tmit->mod->next; + if (NULL == tmit->mod) + { /* No more modifiers, continue with data */ + *data_size = 0; + return GNUNET_YES; + } + + GNUNET_assert (tmit->mod->value_size < UINT32_MAX); + *full_value_size = tmit->mod->value_size; + *oper = tmit->mod->oper; + name_size = strlen (tmit->mod->name); + + if (name_size + 1 + tmit->mod->value_size <= *data_size) + { + *data_size = name_size + 1 + tmit->mod->value_size; + } + else + { + tmit->mod_value_size = tmit->mod->value_size; + value_size = *data_size - name_size - 1; + tmit->mod_value_size -= value_size; + tmit->mod_value = tmit->mod->value + value_size; + } + + GNUNET_memcpy (data, tmit->mod->name, name_size); + ((char *)data)[name_size] = '\0'; + GNUNET_memcpy ((char *)data + name_size + 1, tmit->mod->value, value_size); + } + else if (NULL != tmit->mod_value && 0 < tmit->mod_value_size) + { /* Modifier continuation */ + value = tmit->mod_value; + if (tmit->mod_value_size <= *data_size) + { + value_size = tmit->mod_value_size; + tmit->mod_value = NULL; + } + else + { + value_size = *data_size; + tmit->mod_value += value_size; + } + tmit->mod_value_size -= value_size; + + if (*data_size < value_size) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "value larger than buffer: %u < %zu\n", + *data_size, value_size); + *data_size = 0; + return GNUNET_NO; + } + + *data_size = value_size; + GNUNET_memcpy (data, value, value_size); + } + + return GNUNET_NO; +} + + +static void +slave_join (); + + +static void +slave_transmit () +{ + test = TEST_SLAVE_TRANSMIT; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: Slave sending request to master.\n", test); + + tmit = GNUNET_new (struct TransmitClosure); + tmit->env = GNUNET_PSYC_env_create (); + GNUNET_PSYC_env_add (tmit->env, GNUNET_PSYC_OP_ASSIGN, + "_abc", "abc def", 7); + GNUNET_PSYC_env_add (tmit->env, GNUNET_PSYC_OP_ASSIGN, + "_abc_def", "abc def ghi", 11); + tmit->mod = GNUNET_PSYC_env_head (tmit->env); + tmit->n = 0; + tmit->data[0] = "slave test"; + tmit->data_count = 1; + tmit->slv_tmit + = GNUNET_PSYC_slave_transmit (slv, "_request_test", &tmit_notify_mod, + &tmit_notify_data, tmit, + GNUNET_PSYC_SLAVE_TRANSMIT_NONE); +} + + +static void +slave_remove_cb (void *cls, int64_t result, + const void *err_msg, uint16_t err_msg_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: slave_remove:\t%" PRId64 " (%.*s)\n", + test, result, err_msg_size, (char *) err_msg); + + slave_transmit (); +} + + +static void +slave_remove () +{ + test = TEST_SLAVE_REMOVE; + struct GNUNET_PSYC_Channel *chn = GNUNET_PSYC_master_get_channel (mst); + GNUNET_PSYC_channel_slave_remove (chn, &slave_pub_key, 2, + &slave_remove_cb, chn); +} + + +static void +slave_add_cb (void *cls, int64_t result, + const void *err_msg, uint16_t err_msg_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: slave_add:\t%" PRId64 " (%.*s)\n", + test, result, err_msg_size, (char *) err_msg); + slave_remove (); +} + + +static void +slave_add () +{ + test = TEST_SLAVE_ADD; + struct GNUNET_PSYC_Channel *chn = GNUNET_PSYC_master_get_channel (mst); + GNUNET_PSYC_channel_slave_add (chn, &slave_pub_key, 2, 2, &slave_add_cb, chn); +} + + +static void +schedule_second_slave_join (void *cls) +{ + slave_join (TEST_SLAVE_JOIN_ACCEPT); +} + + +static void +first_slave_parted (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "First slave parted.\n"); + GNUNET_SCHEDULER_add_now (&schedule_second_slave_join, NULL); +} + + +static void +schedule_first_slave_part (void *cls) +{ + GNUNET_PSYC_slave_part (slv, GNUNET_NO, &first_slave_parted, NULL); +} + + +static void +join_decision_cb (void *cls, + const struct GNUNET_PSYC_JoinDecisionMessage *dcsn, + int is_admitted, + const struct GNUNET_PSYC_Message *join_msg) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: Slave got join decision: %d\n", test, is_admitted); + + switch (test) + { + case TEST_SLAVE_JOIN_REJECT: + GNUNET_assert (0 == is_admitted); + GNUNET_assert (1 == join_req_count); + GNUNET_SCHEDULER_add_now (&schedule_first_slave_part, NULL); + break; + + case TEST_SLAVE_JOIN_ACCEPT: + GNUNET_assert (1 == is_admitted); + GNUNET_assert (2 == join_req_count); + slave_add (); + break; + + default: + GNUNET_break (0); + } +} + + +static void +join_request_cb (void *cls, + const struct GNUNET_PSYC_JoinRequestMessage *req, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + const struct GNUNET_PSYC_Message *join_msg, + struct GNUNET_PSYC_JoinHandle *jh) +{ + struct GNUNET_HashCode slave_key_hash; + GNUNET_CRYPTO_hash (slave_key, sizeof (*slave_key), &slave_key_hash); + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: Got join request #%u from %s.\n", + test, join_req_count, GNUNET_h2s (&slave_key_hash)); + + /* Reject first request */ + int is_admitted = (0 < join_req_count++) ? GNUNET_YES : GNUNET_NO; + GNUNET_PSYC_join_decision (jh, is_admitted, 0, NULL, NULL); +} + + +static void +slave_connect_cb (void *cls, int result, uint64_t max_message_id) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: Slave connected: %d, max_message_id: %" PRIu64 "\n", + test, result, max_message_id); + GNUNET_assert (TEST_SLAVE_JOIN_REJECT == test || TEST_SLAVE_JOIN_ACCEPT == test); + GNUNET_assert (GNUNET_OK == result || GNUNET_NO == result); +} + + +static void +slave_join (int t) +{ + test = t; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: Joining slave.\n", t); + + struct GNUNET_PeerIdentity origin = this_peer; + struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create (); + GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_ASSIGN, + "_foo", "bar baz", 7); + GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_ASSIGN, + "_foo_bar", "foo bar baz", 11); + struct GNUNET_PSYC_Message * + join_msg = GNUNET_PSYC_message_create ("_request_join", env, "some data", 9); + + slv = GNUNET_PSYC_slave_join (cfg, + &channel_pub_key, + slave_key, + GNUNET_PSYC_SLAVE_JOIN_NONE, + &origin, + 0, + NULL, + &slave_message_cb, + &slave_message_part_cb, + &slave_connect_cb, + &join_decision_cb, + NULL, + join_msg); + GNUNET_free (join_msg); + slv_chn = GNUNET_PSYC_slave_get_channel (slv); + GNUNET_PSYC_env_destroy (env); +} + + +static void +master_transmit () +{ + test = TEST_MASTER_TRANSMIT; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: Master sending message to all.\n", test); + end_count = 0; + + uint32_t i, j; + + char *name_max = "_test_max"; + uint8_t name_max_size = sizeof ("_test_max"); + char *val_max = GNUNET_malloc (GNUNET_PSYC_MODIFIER_MAX_PAYLOAD); + for (i = 0; i < GNUNET_PSYC_MODIFIER_MAX_PAYLOAD; i++) + val_max[i] = (0 == i % 10000) ? '0' + i / 10000 : '.'; + + char *name_cont = "_test_cont"; + uint8_t name_cont_size = sizeof ("_test_cont"); + char *val_cont = GNUNET_malloc (GNUNET_PSYC_MODIFIER_MAX_PAYLOAD + + GNUNET_PSYC_MOD_CONT_MAX_PAYLOAD); + for (i = 0; i < GNUNET_PSYC_MODIFIER_MAX_PAYLOAD - name_cont_size; i++) + val_cont[i] = (0 == i % 10000) ? '0' + i / 10000 : ':'; + for (j = 0; j < GNUNET_PSYC_MOD_CONT_MAX_PAYLOAD; j++, i++) + val_cont[i] = (0 == j % 10000) ? '0' + j / 10000 : '!'; + + tmit = GNUNET_new (struct TransmitClosure); + tmit->env = GNUNET_PSYC_env_create (); + GNUNET_PSYC_env_add (tmit->env, GNUNET_PSYC_OP_ASSIGN, + "_foo", "bar baz", 7); + GNUNET_PSYC_env_add (tmit->env, GNUNET_PSYC_OP_ASSIGN, + name_max, val_max, + GNUNET_PSYC_MODIFIER_MAX_PAYLOAD + - name_max_size); + GNUNET_PSYC_env_add (tmit->env, GNUNET_PSYC_OP_ASSIGN, + "_foo_bar", "foo bar baz", 11); + GNUNET_PSYC_env_add (tmit->env, GNUNET_PSYC_OP_ASSIGN, + name_cont, val_cont, + GNUNET_PSYC_MODIFIER_MAX_PAYLOAD - name_cont_size + + GNUNET_PSYC_MOD_CONT_MAX_PAYLOAD); + tmit->mod = GNUNET_PSYC_env_head (tmit->env); + tmit->data[0] = "foo"; + tmit->data[1] = GNUNET_malloc (GNUNET_PSYC_DATA_MAX_PAYLOAD + 1); + for (i = 0; i < GNUNET_PSYC_DATA_MAX_PAYLOAD; i++) + tmit->data[1][i] = (0 == i % 10000) ? '0' + i / 10000 : '_'; + tmit->data[2] = "foo bar"; + tmit->data[3] = "foo bar baz"; + tmit->data_delay[1] = 3; + tmit->data_count = 4; + tmit->mst_tmit + = GNUNET_PSYC_master_transmit (mst, "_notice_test", &tmit_notify_mod, + &tmit_notify_data, tmit, + GNUNET_PSYC_MASTER_TRANSMIT_INC_GROUP_GEN); +} + + +static void +master_start_cb (void *cls, int result, uint64_t max_message_id) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Test #%d: Master started: %d, max_message_id: %" PRIu64 "\n", + test, result, max_message_id); + GNUNET_assert (TEST_MASTER_START == test); + GNUNET_assert (GNUNET_OK == result || GNUNET_NO == result); + slave_join (TEST_SLAVE_JOIN_REJECT); +} + + +static void +master_start () +{ + test = TEST_MASTER_START; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Test #%d: Starting master.\n", test); + mst = GNUNET_PSYC_master_start (cfg, channel_key, GNUNET_PSYC_CHANNEL_PRIVATE, + &master_start_cb, &join_request_cb, + &master_message_cb, &master_message_part_cb, + NULL); + mst_chn = GNUNET_PSYC_master_get_channel (mst); +} + + +static void +schedule_master_start (void *cls) +{ + master_start (); +} + + +/** + * Main function of the test, run from scheduler. + * + * @param cls NULL + * @param cfg configuration we use (also to connect to PSYC service) + * @param peer handle to access more of the peer (not used) + */ +static void +#if DEBUG_TEST_PSYC +run (void *cls, char *const *args, const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *c) +#else +run (void *cls, + const struct GNUNET_CONFIGURATION_Handle *c, + struct GNUNET_TESTING_Peer *peer) +#endif +{ + cfg = c; + end_badly_task = GNUNET_SCHEDULER_add_delayed (TIMEOUT, &end_badly, NULL); + + GNUNET_CRYPTO_get_peer_identity (cfg, &this_peer); + + channel_key = GNUNET_CRYPTO_eddsa_key_create (); + slave_key = GNUNET_CRYPTO_ecdsa_key_create (); + + GNUNET_CRYPTO_eddsa_key_get_public (channel_key, &channel_pub_key); + GNUNET_CRYPTO_ecdsa_key_get_public (slave_key, &slave_pub_key); + +#if DEBUG_TEST_PSYC + master_start (); +#else + /* Allow some time for the services to initialize. */ + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, + &schedule_master_start, NULL); +#endif +} + + +int +main (int argc, char *argv[]) +{ + res = 1; +#if DEBUG_TEST_PSYC + const struct GNUNET_GETOPT_CommandLineOption opts[] = { + GNUNET_GETOPT_OPTION_END + }; + if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv, "test-psyc", + "test-psyc [options]", + opts, &run, NULL)) + return 1; +#else + if (0 != GNUNET_TESTING_peer_run ("test-psyc", "test_psyc.conf", &run, NULL)) + return 1; +#endif + return res; +} + +/* end of test_psyc.c */ diff --git a/src/psyc/test_psyc2.c b/src/psyc/test_psyc2.c new file mode 100644 index 0000000..c6e7237 --- /dev/null +++ b/src/psyc/test_psyc2.c @@ -0,0 +1,284 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file psyc/test_psyc2.c + * @brief Testbed test for the PSYC API. + * @author xrs + */ + +#include "platform.h" +#include "gnunet_crypto_lib.h" +#include "gnunet_common.h" +#include "gnunet_util_lib.h" +#include "gnunet_testbed_service.h" +#include "gnunet_psyc_util_lib.h" +#include "gnunet_psyc_service.h" + +#define PEERS_REQUESTED 2 + +static int result; + +static struct GNUNET_SCHEDULER_Task *timeout_tid; +static struct pctx **pctx; + +static struct GNUNET_CRYPTO_EddsaPrivateKey *channel_key; +static struct GNUNET_CRYPTO_EddsaPublicKey channel_pub_key; + +static struct GNUNET_CRYPTO_EcdsaPrivateKey *slave_key; +static struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key; + +/** + * Task To perform tests + */ +static struct GNUNET_SCHEDULER_Task *test_task; + +/** + * Peer id couter + */ +static unsigned int pids; + +struct pctx +{ + int idx; + struct GNUNET_TESTBED_Peer *peer; + const struct GNUNET_PeerIdentity *id; + + struct GNUNET_TESTBED_Operation *op; + + /** + * psyc service handle + */ + void *psyc; + struct GNUNET_PSYC_Master *mst; + struct GNUNET_PSYC_Slave *slv; + + /** + * result for test on peer + */ + int test_ok; +}; + +static void +shutdown_task (void *cls) +{ + if (NULL != pctx) + { + if (NULL != pctx[0]->mst) + GNUNET_PSYC_master_stop (pctx[0]->mst, GNUNET_NO, NULL, NULL); + + for (int i=0; i < PEERS_REQUESTED; i++) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Operation done.\n"); + GNUNET_TESTBED_operation_done (pctx[i]->op); + GNUNET_free_non_null (pctx[i]); + } + GNUNET_free (pctx); + } + + if (NULL != timeout_tid) + GNUNET_SCHEDULER_cancel (timeout_tid); +} + +static void +timeout_task (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Timeout!\n"); + result = GNUNET_SYSERR; + GNUNET_SCHEDULER_shutdown (); +} + +static void +start_test (void *cls) +{ +} + +static void +pinfo_cb (void *cls, + struct GNUNET_TESTBED_Operation *operation, + const struct GNUNET_TESTBED_PeerInformation *pinfo, + const char *emsg) +{ + struct pctx *pc = (struct pctx*) cls; + + pc->id = pinfo->result.id; + + pids++; + if (pids < (PEERS_REQUESTED - 1)) + return; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got all IDs, starting test\n"); + test_task = GNUNET_SCHEDULER_add_now (&start_test, NULL); +} + +static void +mst_start_cb () +{ +} + +static void +join_request_cb () +{ +} + +static void +mst_message_cb () +{ +} + +static void +mst_message_part_cb () +{ +} + +static void +slv_message_cb () +{ +} + +static void +slv_message_part_cb () +{ +} + +static void +slv_connect_cb () +{ +} + +static void +join_decision_cb () +{ +} + +static void * +psyc_ca (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct GNUNET_PSYC_Message *join_msg = NULL; + struct pctx *pc = (struct pctx *) cls; + + if (0 == pc->idx) + { + pc->mst = GNUNET_PSYC_master_start (cfg, channel_key, + GNUNET_PSYC_CHANNEL_PRIVATE, + &mst_start_cb, &join_request_cb, + &mst_message_cb, &mst_message_part_cb, + NULL); + return pc->mst; + } + + pc->slv = GNUNET_PSYC_slave_join (cfg, &channel_pub_key, slave_key, + GNUNET_PSYC_SLAVE_JOIN_NONE, + &pid, 0, NULL, &slv_message_cb, + &slv_message_part_cb, + &slv_connect_cb, &join_decision_cb, + NULL, join_msg); + return pc->slv; +} + +static void +psyc_da (void *cls, + void *op_result) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Disconnected from service.\n"); +} + +static void +service_connect (void *cls, + struct GNUNET_TESTBED_Operation *op, + void *ca_result, + const char *emsg) +{ + struct pctx *pc = (struct pctx *) cls; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, + "Connected to service\n"); + + GNUNET_assert (NULL != ca_result); + + // FIXME: we need a simple service handle to connect to the service, then + // get peer information and AFTER that make PSYC ops. Compare to CADET. + pc->psyc = ca_result; + + GNUNET_TESTBED_peer_get_information (pc->peer, + GNUNET_TESTBED_PIT_IDENTITY, + pinfo_cb, pc); +} + +static void +testbed_master (void *cls, + struct GNUNET_TESTBED_RunHandle *h, + unsigned int num_peers, + struct GNUNET_TESTBED_Peer **p, + unsigned int links_succeeded, + unsigned int links_failed) +{ + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Connected to testbed_master()\n"); + + // Create ctx for peers + pctx = GNUNET_new_array (PEERS_REQUESTED, struct pctx*); + for (int i = 0; iidx = i; + pctx[i]->peer = p[i]; + pctx[i]->id = NULL; + pctx[i]->mst = NULL; + pctx[i]->op = NULL; + pctx[i]->test_ok = GNUNET_NO; + } + + channel_key = GNUNET_CRYPTO_eddsa_key_create (); + slave_key = GNUNET_CRYPTO_ecdsa_key_create (); + + GNUNET_CRYPTO_eddsa_key_get_public (channel_key, &channel_pub_key); + GNUNET_CRYPTO_ecdsa_key_get_public (slave_key, &slave_pub_key); + + pctx[0]->op = + GNUNET_TESTBED_service_connect (NULL, p[0], "psyc", service_connect, + pctx[0], psyc_ca, psyc_da, pctx[0]); + + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL); + + timeout_tid = + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 5), + &timeout_task, NULL); +} + +int +main (int argc, char *argv[]) +{ + int ret; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "test\n"); + + result = GNUNET_SYSERR; + + ret = GNUNET_TESTBED_test_run ("test-psyc2", "test_psyc.conf", + PEERS_REQUESTED, 0LL, NULL, NULL, + testbed_master, NULL); + + if ((GNUNET_OK != ret) || (GNUNET_OK != result)) + return 1; + + return 0; +} + +/* end of test-psyc2.c */ diff --git a/src/psyc/test_psyc_api_join.c b/src/psyc/test_psyc_api_join.c new file mode 100644 index 0000000..419fa11 --- /dev/null +++ b/src/psyc/test_psyc_api_join.c @@ -0,0 +1,282 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file psyc/test_psyc_api_join.c + * @brief Testbed test for the PSYC API. + * @author xrs + */ + +/** + * Lessons Learned: + * - define topology in config + * - psyc slave join needs part to end (same with master) + * - GNUNET_SCHEDULER_add_delayed return value will outdate at call time + * - main can not contain GNUNET_log() + */ + +#include "platform.h" +#include "gnunet_crypto_lib.h" +#include "gnunet_common.h" +#include "gnunet_util_lib.h" +#include "gnunet_testbed_service.h" +#include "gnunet_psyc_util_lib.h" +#include "gnunet_psyc_service.h" +#include "psyc_test_lib.h" + +static struct pctx PEERS[2]; + +static int pids; + + +static void +shutdown_task (void *cls) +{ + if (NULL != timeout_task_id) { + GNUNET_SCHEDULER_cancel (timeout_task_id); + timeout_task_id = NULL; + } + + for (int i=0;i<2;i++) { + GNUNET_free (PEERS[i].channel_pub_key); + + if (NULL != PEERS[i].psyc) + { + if (0 == i) + GNUNET_PSYC_master_stop (PEERS[i].psyc, GNUNET_NO, NULL, NULL); + else + GNUNET_PSYC_slave_part (PEERS[i].psyc, GNUNET_NO, NULL, NULL); + } + } + + for (int i=0;iidx) { + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Connecting to PSYC as master ...\n"); + + peer->psyc = (struct GNUNET_PSYC_Master *) + GNUNET_PSYC_master_start (cfg, + peer->channel_key, + GNUNET_PSYC_CHANNEL_PRIVATE, + NULL, + join_request_cb, + NULL, + NULL, + cls); + return peer->psyc; + } + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Connecting to PSYC as slave ...\n"); + + struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create (); + GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_ASSIGN, "_foo", "bar baz", 7); + GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_ASSIGN, "_foo_bar", "foo bar baz", 11); + + struct GNUNET_PSYC_Message * + join_msg = GNUNET_PSYC_message_create ("_request_join", env, "some data", 40); + + peer->psyc = (struct GNUNET_PSYC_Slave *) + GNUNET_PSYC_slave_join (cfg, + peer->channel_pub_key, + peer->id_key, + GNUNET_PSYC_SLAVE_JOIN_NONE, + peer->peer_id_master, + 0, + NULL, + NULL, + NULL, + NULL, + join_decision_cb, + cls, + join_msg); + + GNUNET_free (join_msg); + peer->channel = GNUNET_PSYC_slave_get_channel (peer->psyc); + GNUNET_PSYC_env_destroy (env); + + return peer->psyc; +} + +static void +service_connect (void *cls, + struct GNUNET_TESTBED_Operation *op, + void *ca_result, + const char *emsg) +{ + GNUNET_assert (NULL != ca_result); + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Connected to the service\n"); +} + +static void +connect_to_services (void *cls) +{ + for (int i = 0; i < 2; i++) + { + PEERS[i].peer_id_master = PEERS[0].peer_id; + + op[op_cnt++] = + GNUNET_TESTBED_service_connect (NULL, PEERS[i].testbed_peer, "psyc", + &service_connect, &PEERS[i], &psyc_ca, + &psyc_da, &PEERS[i]); + } +} + +static void +pinfo_cb (void *cls, + struct GNUNET_TESTBED_Operation *operation, + const struct GNUNET_TESTBED_PeerInformation *pinfo, + const char *emsg) +{ + struct pctx *peer = (struct pctx*) cls; + + peer->peer_id = pinfo->result.id; + + pids++; + if (pids < 2) + return; + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Got all IDs, starting test\n"); + + GNUNET_SCHEDULER_add_now (&connect_to_services, NULL); +} + +static void +testbed_master (void *cls, + struct GNUNET_TESTBED_RunHandle *h, + unsigned int num_peers, + struct GNUNET_TESTBED_Peer **p, + unsigned int links_succeeded, + unsigned int links_failed) +{ + struct GNUNET_CRYPTO_EddsaPrivateKey *channel_key = NULL; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, "Connected to testbed_master\n"); + + // Set up shutdown logic + GNUNET_SCHEDULER_add_shutdown (&shutdown_task, NULL); + timeout_task_id = + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_relative_multiply(GNUNET_TIME_UNIT_SECONDS, 15), + &timeout_task, NULL); + GNUNET_assert (NULL != timeout_task_id); + + // Set up channel key + channel_key = GNUNET_CRYPTO_eddsa_key_create (); + GNUNET_assert (NULL != channel_key); + + // Set up information contexts for peers + for (int i=0 ; i < 2 ; i++) + { + PEERS[i].idx = i; + PEERS[i].testbed_peer = p[i]; + + // Create "egos" + PEERS[i].id_key = GNUNET_CRYPTO_ecdsa_key_create (); + + // Set up channel keys shared by master and slave + PEERS[i].channel_key = channel_key; + + PEERS[i].channel_pub_key = + GNUNET_malloc (sizeof (struct GNUNET_CRYPTO_EddsaPublicKey)); + // Get public key + GNUNET_CRYPTO_eddsa_key_get_public (PEERS[i].channel_key, + PEERS[i].channel_pub_key); + // Get peerinfo + op[op_cnt++] = + GNUNET_TESTBED_peer_get_information (p[i], + GNUNET_TESTBED_PIT_IDENTITY, + pinfo_cb, &PEERS[i]); + } +} + +int +main (int argc, char *argv[]) +{ + int ret; + + ret = GNUNET_TESTBED_test_run ("test_psyc_api_join", "test_psyc.conf", + 2, 0LL, NULL, NULL, + &testbed_master, NULL); + + if ( (GNUNET_OK != ret) || (GNUNET_OK != result) ) + return 1; + + return 0; +} + +/* end of test_psyc_api_join.c */ diff --git a/src/psycstore/.gitignore b/src/psycstore/.gitignore new file mode 100644 index 0000000..5ec7832 --- /dev/null +++ b/src/psycstore/.gitignore @@ -0,0 +1,5 @@ +gnunet-service-psycstore +test_plugin_psycstore_mysql +test_plugin_psycstore_sqlite +test_plugin_psycstore_postgres +test_psycstore diff --git a/src/psycstore/Makefile.am b/src/psycstore/Makefile.am new file mode 100644 index 0000000..557bb42 --- /dev/null +++ b/src/psycstore/Makefile.am @@ -0,0 +1,155 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +plugindir = $(libdir)/gnunet + +pkgcfgdir= $(pkgdatadir)/config.d/ + +libexecdir= $(pkglibdir)/libexec/ + +pkgcfg_DATA = \ + psycstore.conf + + +if MINGW + WINFLAGS = -Wl,--no-undefined -Wl,--export-all-symbols +endif + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +if HAVE_MYSQL +MYSQL_PLUGIN = libgnunet_plugin_psycstore_mysql.la +if HAVE_TESTING +MYSQL_TESTS = test_plugin_psycstore_mysql +endif +endif + +if HAVE_POSTGRESQL +POSTGRES_PLUGIN = libgnunet_plugin_psycstore_postgres.la +if HAVE_TESTING +POSTGRES_TESTS = test_plugin_psycstore_postgres +endif +endif + +if HAVE_SQLITE +SQLITE_PLUGIN = libgnunet_plugin_psycstore_sqlite.la +if HAVE_TESTING +SQLITE_TESTS = test_plugin_psycstore_sqlite +endif +endif + +lib_LTLIBRARIES = libgnunetpsycstore.la + +libgnunetpsycstore_la_SOURCES = \ + psycstore_api.c \ + psycstore.h +libgnunetpsycstore_la_LIBADD = \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(GN_LIBINTL) $(XLIB) +libgnunetpsycstore_la_LDFLAGS = \ + $(GN_LIB_LDFLAGS) $(WINFLAGS) \ + -version-info 0:0:0 + +bin_PROGRAMS = + +libexec_PROGRAMS = \ + gnunet-service-psycstore + +gnunet_service_psycstore_SOURCES = \ + gnunet-service-psycstore.c +gnunet_service_psycstore_LDADD = \ + $(top_builddir)/src/statistics/libgnunetstatistics.la \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(top_builddir)/src/psycutil/libgnunetpsycutil.la \ + $(GN_LIBINTL) + +plugin_LTLIBRARIES = \ + $(SQLITE_PLUGIN) \ + $(MYSQL_PLUGIN) \ + $(POSTGRES_PLUGIN) + + +libgnunet_plugin_psycstore_mysql_la_SOURCES = \ + plugin_psycstore_mysql.c +libgnunet_plugin_psycstore_mysql_la_LIBADD = \ + libgnunetpsycstore.la \ + $(top_builddir)/src/my/libgnunetmy.la \ + $(top_builddir)/src/mysql/libgnunetmysql.la \ + $(top_builddir)/src/statistics/libgnunetstatistics.la \ + $(top_builddir)/src/util/libgnunetutil.la $(XLIBS) \ + $(LTLIBINTL) +libgnunet_plugin_psycstore_mysql_la_LDFLAGS = \ + $(GN_PLUGIN_LDFLAGS) + +libgnunet_plugin_psycstore_postgres_la_SOURCES = \ + plugin_psycstore_postgres.c +libgnunet_plugin_psycstore_postgres_la_LIBADD = \ + libgnunetpsycstore.la \ + $(top_builddir)/src/pq/libgnunetpq.la \ + $(top_builddir)/src/statistics/libgnunetstatistics.la \ + $(top_builddir)/src/util/libgnunetutil.la $(XLIBS) -lpq \ + $(LTLIBINTL) +libgnunet_plugin_psycstore_postgres_la_LDFLAGS = \ + $(GN_PLUGIN_LDFLAGS) $(POSTGRESQL_LDFLAGS) +libgnunet_plugin_psycstore_postgres_la_CPPFLAGS = \ + $(POSTGRESQL_CPPFLAGS) $(AM_CPPFLAGS) + + +libgnunet_plugin_psycstore_sqlite_la_SOURCES = \ + plugin_psycstore_sqlite.c +libgnunet_plugin_psycstore_sqlite_la_LIBADD = \ + libgnunetpsycstore.la \ + $(top_builddir)/src/statistics/libgnunetstatistics.la \ + $(top_builddir)/src/util/libgnunetutil.la $(XLIBS) -lsqlite3 \ + $(LTLIBINTL) +libgnunet_plugin_psycstore_sqlite_la_LDFLAGS = \ + $(GN_PLUGIN_LDFLAGS) + + +if HAVE_SQLITE +if HAVE_TESTING +check_PROGRAMS = \ + $(SQLITE_TESTS) \ + $(MYSQL_TESTS) \ + $(POSTGRES_TESTS) \ + test_psycstore +endif +endif + +if ENABLE_TEST_RUN +AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@};export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME; +TESTS = $(check_PROGRAMS) +endif + +test_psycstore_SOURCES = \ + test_psycstore.c +test_psycstore_LDADD = \ + libgnunetpsycstore.la \ + $(top_builddir)/src/testing/libgnunettesting.la \ + $(top_builddir)/src/util/libgnunetutil.la + +EXTRA_DIST = \ + test_psycstore.conf + + +test_plugin_psycstore_sqlite_SOURCES = \ + test_plugin_psycstore.c +test_plugin_psycstore_sqlite_LDADD = \ + $(top_builddir)/src/testing/libgnunettesting.la \ + $(top_builddir)/src/util/libgnunetutil.la + +test_plugin_psycstore_mysql_SOURCES = \ + test_plugin_psycstore.c +test_plugin_psycstore_mysql_LDADD = \ + $(top_builddir)/src/testing/libgnunettesting.la \ + $(top_builddir)/src/util/libgnunetutil.la + +test_plugin_psycstore_postgres_SOURCES = \ + test_plugin_psycstore.c +test_plugin_psycstore_postgres_LDADD = \ + $(top_builddir)/src/testing/libgnunettesting.la \ + $(top_builddir)/src/util/libgnunetutil.la + diff --git a/src/psycstore/gnunet-service-psycstore.c b/src/psycstore/gnunet-service-psycstore.c new file mode 100644 index 0000000..9aebd3e --- /dev/null +++ b/src/psycstore/gnunet-service-psycstore.c @@ -0,0 +1,1049 @@ +/** + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file psycstore/gnunet-service-psycstore.c + * @brief PSYCstore service + * @author Gabor X Toth + * @author Christian Grothoff + */ + +#include + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_constants.h" +#include "gnunet_protocols.h" +#include "gnunet_statistics_service.h" +#include "gnunet_psyc_util_lib.h" +#include "gnunet_psycstore_service.h" +#include "gnunet_psycstore_plugin.h" +#include "psycstore.h" + + +/** + * Handle to our current configuration. + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Service handle. + */ +static struct GNUNET_SERVICE_Handle *service; + +/** + * Handle to the statistics service. + */ +static struct GNUNET_STATISTICS_Handle *stats; + +/** + * Database handle + */ +static struct GNUNET_PSYCSTORE_PluginFunctions *db; + +/** + * Name of the database plugin + */ +static char *db_lib_name; + + +/** + * Task run during shutdown. + * + * @param cls unused + */ +static void +shutdown_task (void *cls) +{ + if (NULL != stats) + { + GNUNET_STATISTICS_destroy (stats, GNUNET_NO); + stats = NULL; + } + GNUNET_break (NULL == GNUNET_PLUGIN_unload (db_lib_name, db)); + GNUNET_free (db_lib_name); + db_lib_name = NULL; +} + + +/** + * Send a result code back to the client. + * + * @param client + * Client that should receive the result code. + * @param result_code + * Code to transmit. + * @param op_id + * Operation ID in network byte order. + * @param err_msg + * Error message to include (or NULL for none). + */ +static void +send_result_code (struct GNUNET_SERVICE_Client *client, + uint64_t op_id, + int64_t result_code, + const char *err_msg) +{ + struct OperationResult *res; + size_t err_size = 0; + + if (NULL != err_msg) + err_size = strnlen (err_msg, + GNUNET_MAX_MESSAGE_SIZE - sizeof (*res) - 1) + 1; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (res, err_size, + GNUNET_MESSAGE_TYPE_PSYCSTORE_RESULT_CODE); + res->result_code = GNUNET_htonll (result_code - INT64_MIN); + res->op_id = op_id; + if (0 < err_size) + { + GNUNET_memcpy (&res[1], err_msg, err_size); + ((char *) &res[1])[err_size - 1] = '\0'; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending result to client: %" PRId64 " (%s)\n", + result_code, err_msg); + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env); +} + + +enum +{ + MEMBERSHIP_TEST_NOT_NEEDED = 0, + MEMBERSHIP_TEST_NEEDED = 1, + MEMBERSHIP_TEST_DONE = 2, +} MessageMembershipTest; + + +struct SendClosure +{ + struct GNUNET_SERVICE_Client *client; + + /** + * Channel's public key. + */ + struct GNUNET_CRYPTO_EddsaPublicKey channel_key; + + /** + * Slave's public key. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey slave_key; + + /** + * Operation ID. + */ + uint64_t op_id; + + /** + * Membership test result. + */ + int membership_test_result; + + /** + * Do membership test with @a slave_key before returning fragment? + * @see enum MessageMembershipTest + */ + uint8_t membership_test; +}; + + +static int +send_fragment (void *cls, struct GNUNET_MULTICAST_MessageHeader *msg, + enum GNUNET_PSYCSTORE_MessageFlags flags) +{ + struct SendClosure *sc = cls; + struct FragmentResult *res; + + if (MEMBERSHIP_TEST_NEEDED == sc->membership_test) + { + sc->membership_test = MEMBERSHIP_TEST_DONE; + sc->membership_test_result + = db->membership_test (db->cls, &sc->channel_key, &sc->slave_key, + GNUNET_ntohll (msg->message_id)); + switch (sc->membership_test_result) + { + case GNUNET_YES: + break; + + case GNUNET_NO: + case GNUNET_SYSERR: + return GNUNET_NO; + } + } + + size_t msg_size = ntohs (msg->header.size); + + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (res, msg_size, + GNUNET_MESSAGE_TYPE_PSYCSTORE_RESULT_FRAGMENT); + res->op_id = sc->op_id; + res->psycstore_flags = htonl (flags); + GNUNET_memcpy (&res[1], msg, msg_size); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending fragment %llu to client\n", + (unsigned long long) GNUNET_ntohll (msg->fragment_id)); + GNUNET_free (msg); + + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (sc->client), env); + return GNUNET_YES; +} + + +static int +send_state_var (void *cls, const char *name, + const void *value, uint32_t value_size) +{ + struct SendClosure *sc = cls; + struct StateResult *res; + size_t name_size = strlen (name) + 1; + + /** @todo FIXME: split up value into 64k chunks */ + + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (res, name_size + value_size, + GNUNET_MESSAGE_TYPE_PSYCSTORE_RESULT_STATE); + res->op_id = sc->op_id; + res->name_size = htons (name_size); + GNUNET_memcpy (&res[1], name, name_size); + GNUNET_memcpy ((char *) &res[1] + name_size, value, value_size); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending state variable %s to client\n", name); + + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (sc->client), env); + return GNUNET_OK; +} + + +static void +handle_client_membership_store (void *cls, + const struct MembershipStoreRequest *req) +{ + struct GNUNET_SERVICE_Client *client = cls; + + int ret = db->membership_store (db->cls, &req->channel_key, &req->slave_key, + req->did_join, + GNUNET_ntohll (req->announced_at), + GNUNET_ntohll (req->effective_since), + GNUNET_ntohll (req->group_generation)); + + if (ret != GNUNET_OK) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to store membership information!\n")); + + send_result_code (client, req->op_id, ret, NULL); + GNUNET_SERVICE_client_continue (client); +} + + +static void +handle_client_membership_test (void *cls, + const struct MembershipTestRequest *req) +{ + struct GNUNET_SERVICE_Client *client = cls; + + int ret = db->membership_test (db->cls, &req->channel_key, &req->slave_key, + GNUNET_ntohll (req->message_id)); + switch (ret) + { + case GNUNET_YES: + case GNUNET_NO: + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to test membership!\n")); + } + + send_result_code (client, req->op_id, ret, NULL); + GNUNET_SERVICE_client_continue (client); +} + + +static int +check_client_fragment_store (void *cls, + const struct FragmentStoreRequest *req) +{ + return GNUNET_OK; +} + + +static void +handle_client_fragment_store (void *cls, + const struct FragmentStoreRequest *req) +{ + struct GNUNET_SERVICE_Client *client = cls; + + const struct GNUNET_MessageHeader * + msg = GNUNET_MQ_extract_nested_mh (req); + if (NULL == msg + || ntohs (msg->size) < sizeof (struct GNUNET_MULTICAST_MessageHeader)) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Dropping invalid fragment\n")); + GNUNET_SERVICE_client_drop (client); + return; + } + + int ret = db->fragment_store (db->cls, &req->channel_key, + (const struct GNUNET_MULTICAST_MessageHeader *) + msg, ntohl (req->psycstore_flags)); + + if (ret != GNUNET_OK) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to store fragment\n")); + + send_result_code (client, req->op_id, ret, NULL); + GNUNET_SERVICE_client_continue (client); +} + + +static void +handle_client_fragment_get (void *cls, + const struct FragmentGetRequest *req) +{ + struct GNUNET_SERVICE_Client *client = cls; + + struct SendClosure + sc = { .op_id = req->op_id, + .client = client, + .channel_key = req->channel_key, + .slave_key = req->slave_key, + .membership_test = req->do_membership_test }; + + int64_t ret; + uint64_t ret_frags = 0; + uint64_t first_fragment_id = GNUNET_ntohll (req->first_fragment_id); + uint64_t last_fragment_id = GNUNET_ntohll (req->last_fragment_id); + uint64_t limit = GNUNET_ntohll (req->fragment_limit); + + if (0 == limit) + ret = db->fragment_get (db->cls, &req->channel_key, + first_fragment_id, last_fragment_id, + &ret_frags, send_fragment, &sc); + else + ret = db->fragment_get_latest (db->cls, &req->channel_key, limit, + &ret_frags, send_fragment, &sc); + + switch (ret) + { + case GNUNET_YES: + case GNUNET_NO: + if (MEMBERSHIP_TEST_DONE == sc.membership_test) + { + switch (sc.membership_test_result) + { + case GNUNET_YES: + break; + + case GNUNET_NO: + ret = GNUNET_PSYCSTORE_MEMBERSHIP_TEST_FAILED; + break; + + case GNUNET_SYSERR: + ret = GNUNET_SYSERR; + break; + } + } + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to get fragment!\n")); + } + send_result_code (client, req->op_id, (ret < 0) ? ret : ret_frags, NULL); + GNUNET_SERVICE_client_continue (client); +} + + +static int +check_client_message_get (void *cls, + const struct MessageGetRequest *req) +{ + return GNUNET_OK; +} + + +static void +handle_client_message_get (void *cls, + const struct MessageGetRequest *req) +{ + struct GNUNET_SERVICE_Client *client = cls; + + uint16_t size = ntohs (req->header.size); + const char *method_prefix = (const char *) &req[1]; + + if (size < sizeof (*req) + 1 + || '\0' != method_prefix[size - sizeof (*req) - 1]) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Message get: invalid method prefix. size: %u < %u?\n", + size, + (unsigned int) (sizeof (*req) + 1)); + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + struct SendClosure + sc = { .op_id = req->op_id, + .client = client, + .channel_key = req->channel_key, + .slave_key = req->slave_key, + .membership_test = req->do_membership_test }; + + int64_t ret; + uint64_t ret_frags = 0; + uint64_t first_message_id = GNUNET_ntohll (req->first_message_id); + uint64_t last_message_id = GNUNET_ntohll (req->last_message_id); + uint64_t msg_limit = GNUNET_ntohll (req->message_limit); + uint64_t frag_limit = GNUNET_ntohll (req->fragment_limit); + + /** @todo method_prefix */ + if (0 == msg_limit) + ret = db->message_get (db->cls, &req->channel_key, + first_message_id, last_message_id, frag_limit, + &ret_frags, send_fragment, &sc); + else + ret = db->message_get_latest (db->cls, &req->channel_key, msg_limit, + &ret_frags, send_fragment, &sc); + + switch (ret) + { + case GNUNET_YES: + case GNUNET_NO: + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to get message!\n")); + } + + send_result_code (client, req->op_id, (ret < 0) ? ret : ret_frags, NULL); + GNUNET_SERVICE_client_continue (client); +} + + +static void +handle_client_message_get_fragment (void *cls, + const struct MessageGetFragmentRequest *req) +{ + struct GNUNET_SERVICE_Client *client = cls; + + struct SendClosure + sc = { .op_id = req->op_id, .client = client, + .channel_key = req->channel_key, .slave_key = req->slave_key, + .membership_test = req->do_membership_test }; + + int ret = db->message_get_fragment (db->cls, &req->channel_key, + GNUNET_ntohll (req->message_id), + GNUNET_ntohll (req->fragment_offset), + &send_fragment, &sc); + switch (ret) + { + case GNUNET_YES: + case GNUNET_NO: + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to get message fragment!\n")); + } + + send_result_code (client, req->op_id, ret, NULL); + GNUNET_SERVICE_client_continue (client); +} + + +static void +handle_client_counters_get (void *cls, + const struct OperationRequest *req) +{ + struct GNUNET_SERVICE_Client *client = cls; + + struct CountersResult *res; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (res, GNUNET_MESSAGE_TYPE_PSYCSTORE_RESULT_COUNTERS); + + int ret = db->counters_message_get (db->cls, &req->channel_key, + &res->max_fragment_id, &res->max_message_id, + &res->max_group_generation); + switch (ret) + { + case GNUNET_OK: + ret = db->counters_state_get (db->cls, &req->channel_key, + &res->max_state_message_id); + case GNUNET_NO: + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to get master counters!\n")); + } + + res->result_code = htonl (ret); + res->op_id = req->op_id; + res->max_fragment_id = GNUNET_htonll (res->max_fragment_id); + res->max_message_id = GNUNET_htonll (res->max_message_id); + res->max_group_generation = GNUNET_htonll (res->max_group_generation); + res->max_state_message_id = GNUNET_htonll (res->max_state_message_id); + + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env); + GNUNET_SERVICE_client_continue (client); +} + + +struct StateModifyClosure +{ + const struct GNUNET_CRYPTO_EddsaPublicKey channel_key; + struct GNUNET_PSYC_ReceiveHandle *recv; + enum GNUNET_PSYC_MessageState msg_state; + char mod_oper; + char *mod_name; + char *mod_value; + uint32_t mod_value_size; + uint32_t mod_value_remaining; +}; + + +static void +recv_state_message_part (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg) +{ + struct StateModifyClosure *scls = cls; + uint16_t psize; + + if (NULL == msg) + { // FIXME: error on unknown message + return; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "recv_state_message_part() message_id: %" PRIu64 + ", fragment_offset: %" PRIu64 ", flags: %u\n", + GNUNET_ntohll (msg->message_id), + GNUNET_ntohll (msg->fragment_offset), + ntohl (msg->flags)); + + if (NULL == pmsg) + { + scls->msg_state = GNUNET_PSYC_MESSAGE_STATE_ERROR; + return; + } + + switch (ntohs (pmsg->type)) + { + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD: + { + scls->msg_state = GNUNET_PSYC_MESSAGE_STATE_METHOD; + break; + } + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER: + { + struct GNUNET_PSYC_MessageModifier * + pmod = (struct GNUNET_PSYC_MessageModifier *) pmsg; + psize = ntohs (pmod->header.size); + uint16_t name_size = ntohs (pmod->name_size); + uint32_t value_size = ntohl (pmod->value_size); + + const char *name = (const char *) &pmod[1]; + const void *value = name + name_size; + + if (GNUNET_PSYC_OP_SET != pmod->oper) + { // Apply non-transient operation. + if (psize == sizeof (*pmod) + name_size + value_size) + { + db->state_modify_op (db->cls, &scls->channel_key, + pmod->oper, name, value, value_size); + } + else + { + scls->mod_oper = pmod->oper; + scls->mod_name = GNUNET_malloc (name_size); + GNUNET_memcpy (scls->mod_name, name, name_size); + + scls->mod_value_size = value_size; + scls->mod_value = GNUNET_malloc (scls->mod_value_size); + scls->mod_value_remaining + = scls->mod_value_size - (psize - sizeof (*pmod) - name_size); + GNUNET_memcpy (scls->mod_value, value, value_size - scls->mod_value_remaining); + } + } + scls->msg_state = GNUNET_PSYC_MESSAGE_STATE_MODIFIER; + break; + } + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT: + if (GNUNET_PSYC_OP_SET != scls->mod_oper) + { + if (scls->mod_value_remaining == 0) + { + GNUNET_break_op (0); + scls->msg_state = GNUNET_PSYC_MESSAGE_STATE_ERROR; + } + psize = ntohs (pmsg->size); + GNUNET_memcpy (scls->mod_value + (scls->mod_value_size - scls->mod_value_remaining), + &pmsg[1], psize - sizeof (*pmsg)); + scls->mod_value_remaining -= psize - sizeof (*pmsg); + if (0 == scls->mod_value_remaining) + { + db->state_modify_op (db->cls, &scls->channel_key, + scls->mod_oper, scls->mod_name, + scls->mod_value, scls->mod_value_size); + GNUNET_free (scls->mod_name); + GNUNET_free (scls->mod_value); + scls->mod_oper = 0; + scls->mod_name = NULL; + scls->mod_value = NULL; + scls->mod_value_size = 0; + } + } + scls->msg_state = GNUNET_PSYC_MESSAGE_STATE_MOD_CONT; + break; + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_DATA: + scls->msg_state = GNUNET_PSYC_MESSAGE_STATE_DATA; + break; + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END: + scls->msg_state = GNUNET_PSYC_MESSAGE_STATE_END; + break; + + default: + scls->msg_state = GNUNET_PSYC_MESSAGE_STATE_ERROR; + } +} + + +static int +recv_state_fragment (void *cls, struct GNUNET_MULTICAST_MessageHeader *msg, + enum GNUNET_PSYCSTORE_MessageFlags flags) +{ + struct StateModifyClosure *scls = cls; + + if (NULL == scls->recv) + { + scls->recv = GNUNET_PSYC_receive_create (NULL, recv_state_message_part, + scls); + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "recv_state_fragment: %" PRIu64 "\n", GNUNET_ntohll (msg->fragment_id)); + + struct GNUNET_PSYC_MessageHeader * + pmsg = GNUNET_PSYC_message_header_create (msg, flags); + GNUNET_PSYC_receive_message (scls->recv, pmsg); + GNUNET_free (pmsg); + + return GNUNET_YES; +} + + +static void +handle_client_state_modify (void *cls, + const struct StateModifyRequest *req) +{ + struct GNUNET_SERVICE_Client *client = cls; + + uint64_t message_id = GNUNET_ntohll (req->message_id); + uint64_t state_delta = GNUNET_ntohll (req->state_delta); + uint64_t ret_frags = 0; + struct StateModifyClosure + scls = { .channel_key = req->channel_key }; + + int ret = db->state_modify_begin (db->cls, &req->channel_key, + message_id, state_delta); + + if (GNUNET_OK != ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + _("Failed to begin modifying state: %d\n"), ret); + } + else + { + ret = db->message_get (db->cls, &req->channel_key, + message_id, message_id, 0, + &ret_frags, recv_state_fragment, &scls); + if (GNUNET_OK != ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to modify state: %d\n"), ret); + GNUNET_break (0); + } + else + { + if (GNUNET_OK != db->state_modify_end (db->cls, &req->channel_key, message_id)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to end modifying state!\n")); + GNUNET_break (0); + } + } + if (NULL != scls.recv) + { + GNUNET_PSYC_receive_destroy (scls.recv); + } + } + + send_result_code (client, req->op_id, ret, NULL); + GNUNET_SERVICE_client_continue (client); +} + + +static int +check_client_state_sync (void *cls, + const struct StateSyncRequest *req) +{ + return GNUNET_OK; +} + + +/** @todo FIXME: stop processing further state sync messages after an error */ +static void +handle_client_state_sync (void *cls, + const struct StateSyncRequest *req) +{ + struct GNUNET_SERVICE_Client *client = cls; + + int ret = GNUNET_SYSERR; + const char *name = (const char *) &req[1]; + uint16_t name_size = ntohs (req->name_size); + + if (name_size <= 2 || '\0' != name[name_size - 1]) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Tried to set invalid state variable name!\n")); + GNUNET_break_op (0); + } + else + { + ret = GNUNET_OK; + + if (req->flags & STATE_OP_FIRST) + { + ret = db->state_sync_begin (db->cls, &req->channel_key); + } + if (ret != GNUNET_OK) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to begin synchronizing state!\n")); + } + else + { + ret = db->state_sync_assign (db->cls, &req->channel_key, name, + name + ntohs (req->name_size), + ntohs (req->header.size) - sizeof (*req) + - ntohs (req->name_size)); + } + + if (GNUNET_OK == ret && req->flags & STATE_OP_LAST) + { + ret = db->state_sync_end (db->cls, &req->channel_key, + GNUNET_ntohll (req->max_state_message_id), + GNUNET_ntohll (req->state_hash_message_id)); + if (ret != GNUNET_OK) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to end synchronizing state!\n")); + } + } + send_result_code (client, req->op_id, ret, NULL); + GNUNET_SERVICE_client_continue (client); +} + + +static void +handle_client_state_reset (void *cls, + const struct OperationRequest *req) +{ + struct GNUNET_SERVICE_Client *client = cls; + + int ret = db->state_reset (db->cls, &req->channel_key); + + if (ret != GNUNET_OK) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to reset state!\n")); + + send_result_code (client, req->op_id, ret, NULL); + GNUNET_SERVICE_client_continue (client); +} + + +static void +handle_client_state_hash_update (void *cls, + const struct StateHashUpdateRequest *req) +{ + struct GNUNET_SERVICE_Client *client = cls; + + int ret = db->state_reset (db->cls, &req->channel_key); + if (ret != GNUNET_OK) + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to reset state!\n")); + + send_result_code (client, req->op_id, ret, NULL); + GNUNET_SERVICE_client_continue (client); +} + + +static int +check_client_state_get (void *cls, + const struct OperationRequest *req) +{ + return GNUNET_OK; +} + + +static void +handle_client_state_get (void *cls, + const struct OperationRequest *req) +{ + struct GNUNET_SERVICE_Client *client = cls; + + struct SendClosure sc = { .op_id = req->op_id, .client = client }; + int64_t ret = GNUNET_SYSERR; + const char *name = (const char *) &req[1]; + uint16_t name_size = ntohs (req->header.size) - sizeof (*req); + + if (name_size <= 2 || '\0' != name[name_size - 1]) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Tried to get invalid state variable name!\n")); + GNUNET_break (0); + } + else + { + ret = db->state_get (db->cls, &req->channel_key, name, + &send_state_var, &sc); + if (GNUNET_NO == ret && name_size >= 5) /* min: _a_b\0 */ + { + char *p, *n = GNUNET_malloc (name_size); + GNUNET_memcpy (n, name, name_size); + while (&n[1] < (p = strrchr (n, '_')) && GNUNET_NO == ret) + { + *p = '\0'; + ret = db->state_get (db->cls, &req->channel_key, n, + &send_state_var, &sc); + } + GNUNET_free (n); + } + } + switch (ret) + { + case GNUNET_OK: + case GNUNET_NO: + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to get state variable!\n")); + } + + send_result_code (client, req->op_id, ret, NULL); + GNUNET_SERVICE_client_continue (client); +} + + +static int +check_client_state_get_prefix (void *cls, + const struct OperationRequest *req) +{ + return GNUNET_OK; +} + + +static void +handle_client_state_get_prefix (void *cls, + const struct OperationRequest *req) +{ + struct GNUNET_SERVICE_Client *client = cls; + + struct SendClosure sc = { .op_id = req->op_id, .client = client }; + int64_t ret = GNUNET_SYSERR; + const char *name = (const char *) &req[1]; + uint16_t name_size = ntohs (req->header.size) - sizeof (*req); + + if (name_size <= 1 || '\0' != name[name_size - 1]) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Tried to get invalid state variable name!\n")); + GNUNET_break (0); + } + else + { + ret = db->state_get_prefix (db->cls, &req->channel_key, name, + &send_state_var, &sc); + } + switch (ret) + { + case GNUNET_OK: + case GNUNET_NO: + break; + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("Failed to get state variable!\n")); + } + + send_result_code (client, req->op_id, ret, NULL); + GNUNET_SERVICE_client_continue (client); +} + + +/** + * A new client connected. + * + * @param cls NULL + * @param client client to add + * @param mq message queue for @a client + * @return @a client + */ +static void * +client_notify_connect (void *cls, + struct GNUNET_SERVICE_Client *client, + struct GNUNET_MQ_Handle *mq) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Client connected: %p\n", client); + + return client; +} + + +/** + * Called whenever a client is disconnected. + * Frees our resources associated with that client. + * + * @param cls closure + * @param client identification of the client + * @param app_ctx must match @a client + */ +static void +client_notify_disconnect (void *cls, + struct GNUNET_SERVICE_Client *client, + void *app_ctx) +{ +} + + +/** + * Initialize the PSYCstore service. + * + * @param cls Closure. + * @param server The initialized server. + * @param c Configuration to use. + */ +static void +run (void *cls, + const struct GNUNET_CONFIGURATION_Handle *c, + struct GNUNET_SERVICE_Handle *svc) +{ + cfg = c; + service = svc; + + /* Loading database plugin */ + char *database; + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_string (cfg, "psycstore", "database", + &database)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "psycstore", + "database"); + } + else + { + GNUNET_asprintf (&db_lib_name, + "libgnunet_plugin_psycstore_%s", + database); + db = GNUNET_PLUGIN_load (db_lib_name, (void *) cfg); + GNUNET_free (database); + } + if (NULL == db) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Could not load database backend `%s'\n", + db_lib_name); + GNUNET_SCHEDULER_add_now (&shutdown_task, NULL); + return; + } + + stats = GNUNET_STATISTICS_create ("psycstore", cfg); + GNUNET_SCHEDULER_add_shutdown (shutdown_task, + NULL); +} + +/** + * Define "main" method using service macro. + */ +GNUNET_SERVICE_MAIN +("psycstore", + GNUNET_SERVICE_OPTION_NONE, + run, + client_notify_connect, + client_notify_disconnect, + NULL, + GNUNET_MQ_hd_fixed_size (client_membership_store, + GNUNET_MESSAGE_TYPE_PSYCSTORE_MEMBERSHIP_STORE, + struct MembershipStoreRequest, + NULL), + GNUNET_MQ_hd_fixed_size (client_membership_test, + GNUNET_MESSAGE_TYPE_PSYCSTORE_MEMBERSHIP_TEST, + struct MembershipTestRequest, + NULL), + GNUNET_MQ_hd_var_size (client_fragment_store, + GNUNET_MESSAGE_TYPE_PSYCSTORE_FRAGMENT_STORE, + struct FragmentStoreRequest, + NULL), + GNUNET_MQ_hd_fixed_size (client_fragment_get, + GNUNET_MESSAGE_TYPE_PSYCSTORE_FRAGMENT_GET, + struct FragmentGetRequest, + NULL), + GNUNET_MQ_hd_var_size (client_message_get, + GNUNET_MESSAGE_TYPE_PSYCSTORE_MESSAGE_GET, + struct MessageGetRequest, + NULL), + GNUNET_MQ_hd_fixed_size (client_message_get_fragment, + GNUNET_MESSAGE_TYPE_PSYCSTORE_MESSAGE_GET_FRAGMENT, + struct MessageGetFragmentRequest, + NULL), + GNUNET_MQ_hd_fixed_size (client_counters_get, + GNUNET_MESSAGE_TYPE_PSYCSTORE_COUNTERS_GET, + struct OperationRequest, + NULL), + GNUNET_MQ_hd_fixed_size (client_state_modify, + GNUNET_MESSAGE_TYPE_PSYCSTORE_STATE_MODIFY, + struct StateModifyRequest, + NULL), + GNUNET_MQ_hd_var_size (client_state_sync, + GNUNET_MESSAGE_TYPE_PSYCSTORE_STATE_SYNC, + struct StateSyncRequest, + NULL), + GNUNET_MQ_hd_fixed_size (client_state_reset, + GNUNET_MESSAGE_TYPE_PSYCSTORE_STATE_RESET, + struct OperationRequest, + NULL), + GNUNET_MQ_hd_fixed_size (client_state_hash_update, + GNUNET_MESSAGE_TYPE_PSYCSTORE_STATE_HASH_UPDATE, + struct StateHashUpdateRequest, + NULL), + GNUNET_MQ_hd_var_size (client_state_get, + GNUNET_MESSAGE_TYPE_PSYCSTORE_STATE_GET, + struct OperationRequest, + NULL), + GNUNET_MQ_hd_var_size (client_state_get_prefix, + GNUNET_MESSAGE_TYPE_PSYCSTORE_STATE_GET_PREFIX, + struct OperationRequest, + NULL)); + +/* end of gnunet-service-psycstore.c */ diff --git a/src/psycstore/plugin_psycstore_mysql.c b/src/psycstore/plugin_psycstore_mysql.c new file mode 100644 index 0000000..c36b6f7 --- /dev/null +++ b/src/psycstore/plugin_psycstore_mysql.c @@ -0,0 +1,1960 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file psycstore/plugin_psycstore_mysql.c + * @brief mysql-based psycstore backend + * @author Gabor X Toth + * @author Christian Grothoff + * @author Christophe Genevey + */ + +#include "platform.h" +#include "gnunet_psycstore_plugin.h" +#include "gnunet_psycstore_service.h" +#include "gnunet_multicast_service.h" +#include "gnunet_crypto_lib.h" +#include "gnunet_psyc_util_lib.h" +#include "psycstore.h" +#include "gnunet_my_lib.h" +#include "gnunet_mysql_lib.h" +#include + +/** + * After how many ms "busy" should a DB operation fail for good? A + * low value makes sure that we are more responsive to requests + * (especially PUTs). A high value guarantees a higher success rate + * (SELECTs in iterate can take several seconds despite LIMIT=1). + * + * The default value of 1s should ensure that users do not experience + * huge latencies while at the same time allowing operations to + * succeed with reasonable probability. + */ +#define BUSY_TIMEOUT_MS 1000 + +#define DEBUG_PSYCSTORE GNUNET_EXTRA_LOGGING + +/** + * Log an error message at log-level 'level' that indicates + * a failure of the command 'cmd' on file 'filename' + * with the message given by strerror(errno). + */ +#define LOG_MYSQL(db, level, cmd, stmt) \ + do { \ + GNUNET_log_from (level, "psycstore-mysql", \ + _("`%s' failed at %s:%d with error: %s\n"), \ + cmd, __FILE__, __LINE__, \ + mysql_stmt_error (GNUNET_MYSQL_statement_get_stmt(stmt))); \ + } while (0) + +#define LOG(kind,...) GNUNET_log_from (kind, "psycstore-mysql", __VA_ARGS__) + +enum Transactions { + TRANSACTION_NONE = 0, + TRANSACTION_STATE_MODIFY, + TRANSACTION_STATE_SYNC, +}; + +/** + * Context for all functions in this plugin. + */ +struct Plugin +{ + + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * MySQL context. + */ + struct GNUNET_MYSQL_Context *mc; + + /** + * Current transaction. + */ + enum Transactions transaction; + + /** + * Precompiled SQL for channel_key_store() + */ + struct GNUNET_MYSQL_StatementHandle *insert_channel_key; + + /** + * Precompiled SQL for slave_key_store() + */ + struct GNUNET_MYSQL_StatementHandle *insert_slave_key; + + /** + * Precompiled SQL for membership_store() + */ + struct GNUNET_MYSQL_StatementHandle *insert_membership; + + /** + * Precompiled SQL for membership_test() + */ + struct GNUNET_MYSQL_StatementHandle *select_membership; + + /** + * Precompiled SQL for fragment_store() + */ + struct GNUNET_MYSQL_StatementHandle *insert_fragment; + + /** + * Precompiled SQL for message_add_flags() + */ + struct GNUNET_MYSQL_StatementHandle *update_message_flags; + + /** + * Precompiled SQL for fragment_get() + */ + struct GNUNET_MYSQL_StatementHandle *select_fragments; + + /** + * Precompiled SQL for fragment_get() + */ + struct GNUNET_MYSQL_StatementHandle *select_latest_fragments; + + /** + * Precompiled SQL for message_get() + */ + struct GNUNET_MYSQL_StatementHandle *select_messages; + + /** + * Precompiled SQL for message_get() + */ + struct GNUNET_MYSQL_StatementHandle *select_latest_messages; + + /** + * Precompiled SQL for message_get_fragment() + */ + struct GNUNET_MYSQL_StatementHandle *select_message_fragment; + + /** + * Precompiled SQL for counters_get_message() + */ + struct GNUNET_MYSQL_StatementHandle *select_counters_message; + + /** + * Precompiled SQL for counters_get_state() + */ + struct GNUNET_MYSQL_StatementHandle *select_counters_state; + + /** + * Precompiled SQL for state_modify_end() + */ + struct GNUNET_MYSQL_StatementHandle *update_state_hash_message_id; + + /** + * Precompiled SQL for state_sync_end() + */ + struct GNUNET_MYSQL_StatementHandle *update_max_state_message_id; + + /** + * Precompiled SQL for state_modify_op() + */ + struct GNUNET_MYSQL_StatementHandle *insert_state_current; + + /** + * Precompiled SQL for state_modify_end() + */ + struct GNUNET_MYSQL_StatementHandle *delete_state_empty; + + /** + * Precompiled SQL for state_set_signed() + */ + struct GNUNET_MYSQL_StatementHandle *update_state_signed; + + /** + * Precompiled SQL for state_sync() + */ + struct GNUNET_MYSQL_StatementHandle *insert_state_sync; + + /** + * Precompiled SQL for state_sync() + */ + struct GNUNET_MYSQL_StatementHandle *delete_state; + + /** + * Precompiled SQL for state_sync() + */ + struct GNUNET_MYSQL_StatementHandle *insert_state_from_sync; + + /** + * Precompiled SQL for state_sync() + */ + struct GNUNET_MYSQL_StatementHandle *delete_state_sync; + + /** + * Precompiled SQL for state_get_signed() + */ + struct GNUNET_MYSQL_StatementHandle *select_state_signed; + + /** + * Precompiled SQL for state_get() + */ + struct GNUNET_MYSQL_StatementHandle *select_state_one; + + /** + * Precompiled SQL for state_get_prefix() + */ + struct GNUNET_MYSQL_StatementHandle *select_state_prefix; + +}; + +#if DEBUG_PSYCSTORE + +static void +mysql_trace (void *cls, const char *sql) +{ + LOG(GNUNET_ERROR_TYPE_DEBUG, "MYSQL query:\n%s\n", sql); +} + +#endif + + +/** + * @brief Prepare a SQL statement + * + * @param dbh handle to the database + * @param sql SQL statement, UTF-8 encoded + * @param stmt set to the prepared statement + * @return #GNUNET_OK on success, #GNUNET_SYSERR on failure + */ +static int +mysql_prepare (struct GNUNET_MYSQL_Context *mc, + const char *sql, + struct GNUNET_MYSQL_StatementHandle **stmt) +{ + *stmt = GNUNET_MYSQL_statement_prepare (mc, + sql); + + if (NULL == *stmt) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Error preparing SQL query: %s\n %s\n"), + mysql_stmt_error (GNUNET_MYSQL_statement_get_stmt (*stmt)), + sql); + return GNUNET_SYSERR; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Prepared `%s' / %p\n", + sql, + stmt); + return GNUNET_OK; +} + + +/** + * Initialize the database connections and associated + * data structures (create tables and indices + * as needed as well). + * + * @param plugin the plugin context (state for this module) + * @return #GNUNET_OK on success + */ +static int +database_setup (struct Plugin *plugin) +{ + /* Open database and precompile statements */ + plugin->mc = GNUNET_MYSQL_context_create (plugin->cfg, + "psycstore-mysql"); + + if (NULL == plugin->mc) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Unable to initialize Mysql.\n")); + return GNUNET_SYSERR; + } + +#define STMT_RUN(sql) \ + if (GNUNET_OK != \ + GNUNET_MYSQL_statement_run (plugin->mc, \ + sql)) \ + { \ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, \ + _("Failed to run SQL statement `%s'\n"), \ + sql); \ + return GNUNET_SYSERR; \ + } + + /* Create tables */ + STMT_RUN ("CREATE TABLE IF NOT EXISTS channels (\n" + " id BIGINT UNSIGNED AUTO_INCREMENT,\n" + " pub_key BLOB(32),\n" + " max_state_message_id BIGINT UNSIGNED,\n" + " state_hash_message_id BIGINT UNSIGNED,\n" + " PRIMARY KEY(id),\n" + " UNIQUE KEY(pub_key(32))\n" + ");"); + + STMT_RUN ("CREATE TABLE IF NOT EXISTS slaves (\n" + " id BIGINT UNSIGNED AUTO_INCREMENT,\n" + " pub_key BLOB(32),\n" + " PRIMARY KEY(id),\n" + " UNIQUE KEY(pub_key(32))\n" + ");"); + + STMT_RUN ("CREATE TABLE IF NOT EXISTS membership (\n" + " channel_id BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n" + " slave_id BIGINT UNSIGNED NOT NULL REFERENCES slaves(id),\n" + " did_join TINYINT NOT NULL,\n" + " announced_at BIGINT UNSIGNED NOT NULL,\n" + " effective_since BIGINT UNSIGNED NOT NULL,\n" + " group_generation BIGINT UNSIGNED NOT NULL\n" + ");"); + +/*** FIX because IF NOT EXISTS doesn't work ***/ + GNUNET_MYSQL_statement_run (plugin->mc, + "CREATE INDEX idx_membership_channel_id_slave_id " + "ON membership (channel_id, slave_id);"); + + /** @todo messages table: add method_name column */ + STMT_RUN ("CREATE TABLE IF NOT EXISTS messages (\n" + " channel_id BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n" + " hop_counter BIGINT UNSIGNED NOT NULL,\n" + " signature BLOB,\n" + " purpose BLOB,\n" + " fragment_id BIGINT UNSIGNED NOT NULL,\n" + " fragment_offset BIGINT UNSIGNED NOT NULL,\n" + " message_id BIGINT UNSIGNED NOT NULL,\n" + " group_generation BIGINT UNSIGNED NOT NULL,\n" + " multicast_flags BIGINT UNSIGNED NOT NULL,\n" + " psycstore_flags BIGINT UNSIGNED NOT NULL,\n" + " data BLOB,\n" + " PRIMARY KEY (channel_id, fragment_id),\n" + " UNIQUE KEY(channel_id, message_id, fragment_offset)\n" + ");"); + + STMT_RUN ("CREATE TABLE IF NOT EXISTS state (\n" + " channel_id BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n" + " name TEXT NOT NULL,\n" + " value_current BLOB,\n" + " value_signed BLOB\n" + //" PRIMARY KEY (channel_id, name(255))\n" + ");"); + + STMT_RUN ("CREATE TABLE IF NOT EXISTS state_sync (\n" + " channel_id BIGINT UNSIGNED NOT NULL REFERENCES channels(id),\n" + " name TEXT NOT NULL,\n" + " value BLOB\n" + //" PRIMARY KEY (channel_id, name(255))\n" + ");"); +#undef STMT_RUN + + /* Prepare statements */ +#define PREP(stmt,handle) \ + if (GNUNET_OK != mysql_prepare (plugin->mc, stmt, handle)) \ + { \ + GNUNET_break (0); \ + return GNUNET_SYSERR; \ + } + PREP ("INSERT IGNORE INTO channels (pub_key) VALUES (?);", + &plugin->insert_channel_key); + PREP ("INSERT IGNORE INTO slaves (pub_key) VALUES (?);", + &plugin->insert_slave_key); + PREP ("INSERT INTO membership\n" + " (channel_id, slave_id, did_join, announced_at,\n" + " effective_since, group_generation)\n" + "VALUES ((SELECT id FROM channels WHERE pub_key = ?),\n" + " (SELECT id FROM slaves WHERE pub_key = ?),\n" + " ?, ?, ?, ?);", + &plugin->insert_membership); + PREP ("SELECT did_join FROM membership\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND slave_id = (SELECT id FROM slaves WHERE pub_key = ?)\n" + " AND effective_since <= ? AND did_join = 1\n" + "ORDER BY announced_at DESC LIMIT 1;", + &plugin->select_membership); + + PREP ("INSERT IGNORE INTO messages\n" + " (channel_id, hop_counter, signature, purpose,\n" + " fragment_id, fragment_offset, message_id,\n" + " group_generation, multicast_flags, psycstore_flags, data)\n" + "VALUES ((SELECT id FROM channels WHERE pub_key = ?),\n" + " ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);", + &plugin->insert_fragment); + + PREP ("UPDATE messages\n" + "SET psycstore_flags = psycstore_flags | ?\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND message_id = ? AND fragment_offset = 0;", + &plugin->update_message_flags); + + PREP ("SELECT hop_counter, signature, purpose, fragment_id,\n" + " fragment_offset, message_id, group_generation,\n" + " multicast_flags, psycstore_flags, data\n" + "FROM messages\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND ? <= fragment_id AND fragment_id <= ? LIMIT 1;", + &plugin->select_fragments); + + /** @todo select_messages: add method_prefix filter */ + PREP ("SELECT hop_counter, signature, purpose, fragment_id,\n" + " fragment_offset, message_id, group_generation,\n" + " multicast_flags, psycstore_flags, data\n" + "FROM messages\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND ? <= message_id AND message_id <= ?\n" + "LIMIT ?;", + &plugin->select_messages); + + PREP ("SELECT * FROM\n" + "(SELECT hop_counter, signature, purpose, fragment_id,\n" + " fragment_offset, message_id, group_generation,\n" + " multicast_flags, psycstore_flags, data\n" + " FROM messages\n" + " WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " ORDER BY fragment_id DESC\n" + " LIMIT ?)\n" + "ORDER BY fragment_id;", + &plugin->select_latest_fragments); + + /** @todo select_latest_messages: add method_prefix filter */ + PREP ("SELECT hop_counter, signature, purpose, fragment_id,\n" + " fragment_offset, message_id, group_generation,\n" + " multicast_flags, psycstore_flags, data\n" + "FROM messages\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND message_id IN\n" + " (SELECT message_id\n" + " FROM messages\n" + " WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " GROUP BY message_id\n" + " ORDER BY message_id\n" + " DESC LIMIT ?)\n" + "ORDER BY fragment_id;", + &plugin->select_latest_messages); + + PREP ("SELECT hop_counter, signature, purpose, fragment_id,\n" + " fragment_offset, message_id, group_generation,\n" + " multicast_flags, psycstore_flags, data\n" + "FROM messages\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND message_id = ? AND fragment_offset = ?;", + &plugin->select_message_fragment); + + PREP ("SELECT fragment_id, message_id, group_generation\n" + "FROM messages\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + "ORDER BY fragment_id DESC LIMIT 1;", + &plugin->select_counters_message); + + PREP ("SELECT max_state_message_id\n" + "FROM channels\n" + "WHERE pub_key = ? AND max_state_message_id IS NOT NULL;", + &plugin->select_counters_state); + + PREP ("UPDATE channels\n" + "SET max_state_message_id = ?\n" + "WHERE pub_key = ?;", + &plugin->update_max_state_message_id); + + PREP ("UPDATE channels\n" + "SET state_hash_message_id = ?\n" + "WHERE pub_key = ?;", + &plugin->update_state_hash_message_id); + + PREP ("REPLACE INTO state\n" + " (channel_id, name, value_current, value_signed)\n" + "SELECT new.channel_id, new.name, new.value_current, old.value_signed\n" + "FROM (SELECT (SELECT id FROM channels WHERE pub_key = ?) AS channel_id,\n" + " (SELECT ?) AS name,\n" + " (SELECT ?) AS value_current\n" + " ) AS new\n" + "LEFT JOIN (SELECT channel_id, name, value_signed\n" + " FROM state) AS old\n" + "ON new.channel_id = old.channel_id AND new.name = old.name;", + &plugin->insert_state_current); + + PREP ("DELETE FROM state\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND (value_current IS NULL OR length(value_current) = 0)\n" + " AND (value_signed IS NULL OR length(value_signed) = 0);", + &plugin->delete_state_empty); + + PREP ("UPDATE state\n" + "SET value_signed = value_current\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?);", + &plugin->update_state_signed); + + PREP ("DELETE FROM state\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?);", + &plugin->delete_state); + + PREP ("INSERT INTO state_sync (channel_id, name, value)\n" + "VALUES ((SELECT id FROM channels WHERE pub_key = ?), ?, ?);", + &plugin->insert_state_sync); + + PREP ("INSERT INTO state\n" + " (channel_id, name, value_current, value_signed)\n" + "SELECT channel_id, name, value, value\n" + "FROM state_sync\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?);", + &plugin->insert_state_from_sync); + + PREP ("DELETE FROM state_sync\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?);", + &plugin->delete_state_sync); + + PREP ("SELECT value_current\n" + "FROM state\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND name = ?;", + &plugin->select_state_one); + + PREP ("SELECT name, value_current\n" + "FROM state\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND (name = ? OR substr(name, 1, ?) = ?);", + &plugin->select_state_prefix); + + PREP ("SELECT name, value_signed\n" + "FROM state\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)" + " AND value_signed IS NOT NULL;", + &plugin->select_state_signed); +#undef PREP + + return GNUNET_OK; +} + + +/** + * Shutdown database connection and associate data + * structures. + * @param plugin the plugin context (state for this module) + */ +static void +database_shutdown (struct Plugin *plugin) +{ + GNUNET_MYSQL_context_destroy (plugin->mc); +} + + +/** + * Execute a prepared statement with a @a channel_key argument. + * + * @param plugin Plugin handle. + * @param stmt Statement to execute. + * @param channel_key Public key of the channel. + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +exec_channel (struct Plugin *plugin, struct GNUNET_MYSQL_StatementHandle *stmt, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key) +{ + struct GNUNET_MY_QueryParam params[] = { + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_end + }; + + if (GNUNET_OK != GNUNET_MY_exec_prepared (plugin->mc, stmt, params)) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql exec_channel", stmt); + } + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/** + * Begin a transaction. + */ +static int +transaction_begin (struct Plugin *plugin, enum Transactions transaction) +{ + if (GNUNET_OK != GNUNET_MYSQL_statement_run (plugin->mc, "BEGIN")) + { + LOG(GNUNET_ERROR_TYPE_ERROR, "transaction_begin failed"); + return GNUNET_SYSERR; + } + + plugin->transaction = transaction; + return GNUNET_OK; +} + + +/** + * Commit current transaction. + */ +static int +transaction_commit (struct Plugin *plugin) +{ + if (GNUNET_OK != GNUNET_MYSQL_statement_run (plugin->mc, "COMMIT")) + { + LOG(GNUNET_ERROR_TYPE_ERROR, "transaction_commit failed"); + return GNUNET_SYSERR; + } + + plugin->transaction = TRANSACTION_NONE; + return GNUNET_OK; +} + + +/** + * Roll back current transaction. + */ +static int +transaction_rollback (struct Plugin *plugin) +{ + if (GNUNET_OK != GNUNET_MYSQL_statement_run (plugin->mc, "ROLLBACK")) + { + LOG(GNUNET_ERROR_TYPE_ERROR, "transaction_rollback failed"); + return GNUNET_SYSERR; + } + + plugin->transaction = TRANSACTION_NONE; + return GNUNET_OK; +} + + +static int +channel_key_store (struct Plugin *plugin, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key) +{ + struct GNUNET_MYSQL_StatementHandle *stmt = plugin->insert_channel_key; + + struct GNUNET_MY_QueryParam params[] = { + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_end + }; + + if (GNUNET_OK != GNUNET_MY_exec_prepared (plugin->mc, stmt, params)) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql exec_prepared", stmt); + return GNUNET_SYSERR; + } + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +static int +slave_key_store (struct Plugin *plugin, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key) +{ + struct GNUNET_MYSQL_StatementHandle *stmt = plugin->insert_slave_key; + + struct GNUNET_MY_QueryParam params[] = { + GNUNET_MY_query_param_auto_from_type (slave_key), + GNUNET_MY_query_param_end + }; + + if (GNUNET_OK != GNUNET_MY_exec_prepared (plugin->mc, stmt, params)) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql exec_prepared", stmt); + return GNUNET_SYSERR; + } + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/** + * Store join/leave events for a PSYC channel in order to be able to answer + * membership test queries later. + * + * @see GNUNET_PSYCSTORE_membership_store() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +mysql_membership_store (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + int did_join, + uint64_t announced_at, + uint64_t effective_since, + uint64_t group_generation) +{ + struct Plugin *plugin = cls; + + uint32_t idid_join = (uint32_t)did_join; + + struct GNUNET_MYSQL_StatementHandle *stmt = plugin->insert_membership; + + GNUNET_assert (TRANSACTION_NONE == plugin->transaction); + + if (announced_at > INT64_MAX || + effective_since > INT64_MAX || + group_generation > INT64_MAX) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != channel_key_store (plugin, channel_key) + || GNUNET_OK != slave_key_store (plugin, slave_key)) + return GNUNET_SYSERR; + + struct GNUNET_MY_QueryParam params[] = { + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_auto_from_type (slave_key), + GNUNET_MY_query_param_uint32 (&idid_join), + GNUNET_MY_query_param_uint64 (&announced_at), + GNUNET_MY_query_param_uint64 (&effective_since), + GNUNET_MY_query_param_uint64 (&group_generation), + GNUNET_MY_query_param_end + }; + + if (GNUNET_OK != GNUNET_MY_exec_prepared (plugin->mc, stmt, params)) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql exec_prepared", stmt); + return GNUNET_SYSERR; + } + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + +/** + * Test if a member was admitted to the channel at the given message ID. + * + * @see GNUNET_PSYCSTORE_membership_test() + * + * @return #GNUNET_YES if the member was admitted, #GNUNET_NO if not, + * #GNUNET_SYSERR if there was en error. + */ +static int +membership_test (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + uint64_t message_id) +{ + struct Plugin *plugin = cls; + + struct GNUNET_MYSQL_StatementHandle *stmt = plugin->select_membership; + + uint32_t did_join = 0; + + int ret = GNUNET_SYSERR; + + struct GNUNET_MY_QueryParam params_select[] = { + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_auto_from_type (slave_key), + GNUNET_MY_query_param_uint64 (&message_id), + GNUNET_MY_query_param_end + }; + + if (GNUNET_OK != GNUNET_MY_exec_prepared (plugin->mc, stmt, params_select)) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql execute prepared", stmt); + return GNUNET_SYSERR; + } + + struct GNUNET_MY_ResultSpec results_select[] = { + GNUNET_MY_result_spec_uint32 (&did_join), + GNUNET_MY_result_spec_end + }; + + switch (GNUNET_MY_extract_result (stmt, results_select)) + { + case GNUNET_NO: + ret = GNUNET_NO; + break; + case GNUNET_OK: + ret = GNUNET_YES; + break; + default: + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql extract_result", stmt); + return GNUNET_SYSERR; + } + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return ret; +} + +/** + * Store a message fragment sent to a channel. + * + * @see GNUNET_PSYCSTORE_fragment_store() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +fragment_store (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_MULTICAST_MessageHeader *msg, + uint32_t psycstore_flags) +{ + struct Plugin *plugin = cls; + + struct GNUNET_MYSQL_StatementHandle *stmt = plugin->insert_fragment; + + GNUNET_assert (TRANSACTION_NONE == plugin->transaction); + + uint64_t fragment_id = GNUNET_ntohll (msg->fragment_id); + + uint64_t fragment_offset = GNUNET_ntohll (msg->fragment_offset); + uint64_t message_id = GNUNET_ntohll (msg->message_id); + uint64_t group_generation = GNUNET_ntohll (msg->group_generation); + + uint64_t hop_counter = ntohl(msg->hop_counter); + uint64_t flags = ntohl(msg->flags); + + if (fragment_id > INT64_MAX || fragment_offset > INT64_MAX || + message_id > INT64_MAX || group_generation > INT64_MAX) + { + LOG(GNUNET_ERROR_TYPE_ERROR, + "Tried to store fragment with a field > INT64_MAX: " + "%lu, %lu, %lu, %lu\n", fragment_id, fragment_offset, + message_id, group_generation); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != channel_key_store (plugin, channel_key)) + return GNUNET_SYSERR; + + struct GNUNET_MY_QueryParam params_insert[] = { + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_uint64 (&hop_counter), + GNUNET_MY_query_param_auto_from_type (&msg->signature), + GNUNET_MY_query_param_auto_from_type (&msg->purpose), + GNUNET_MY_query_param_uint64 (&fragment_id), + GNUNET_MY_query_param_uint64 (&fragment_offset), + GNUNET_MY_query_param_uint64 (&message_id), + GNUNET_MY_query_param_uint64 (&group_generation), + GNUNET_MY_query_param_uint64 (&flags), + GNUNET_MY_query_param_uint32 (&psycstore_flags), + GNUNET_MY_query_param_fixed_size (&msg[1], ntohs (msg->header.size) + - sizeof (*msg)), + GNUNET_MY_query_param_end + }; + + if (GNUNET_OK != GNUNET_MY_exec_prepared (plugin->mc, stmt, params_insert)) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql execute prepared", stmt); + return GNUNET_SYSERR; + } + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + +/** + * Set additional flags for a given message. + * + * They are OR'd with any existing flags set. + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +message_add_flags (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id, + uint32_t psycstore_flags) +{ + struct Plugin *plugin = cls; + struct GNUNET_MYSQL_StatementHandle *stmt = plugin->update_message_flags; + + int sql_ret; + int ret = GNUNET_SYSERR; + + struct GNUNET_MY_QueryParam params_update[] = { + GNUNET_MY_query_param_uint32 (&psycstore_flags), + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_uint64 (&message_id), + GNUNET_MY_query_param_end + }; + + sql_ret = GNUNET_MY_exec_prepared (plugin->mc, stmt, params_update); + switch (sql_ret) + { + case GNUNET_OK: + ret = GNUNET_OK; + break; + + default: + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql execute prepared", stmt); + } + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return ret; +} + + +static int +fragment_row (struct GNUNET_MYSQL_StatementHandle *stmt, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls, + uint64_t *returned_fragments) +{ + + uint32_t hop_counter; + void *signature = NULL; + void *purpose = NULL; + size_t signature_size; + size_t purpose_size; + uint64_t fragment_id; + uint64_t fragment_offset; + uint64_t message_id; + uint64_t group_generation; + uint64_t flags; + void *buf; + size_t buf_size; + int ret = GNUNET_SYSERR; + int sql_ret; + struct GNUNET_MULTICAST_MessageHeader *mp; + uint64_t msg_flags; + struct GNUNET_MY_ResultSpec results[] = { + GNUNET_MY_result_spec_uint32 (&hop_counter), + GNUNET_MY_result_spec_variable_size (&signature, &signature_size), + GNUNET_MY_result_spec_variable_size (&purpose, &purpose_size), + GNUNET_MY_result_spec_uint64 (&fragment_id), + GNUNET_MY_result_spec_uint64 (&fragment_offset), + GNUNET_MY_result_spec_uint64 (&message_id), + GNUNET_MY_result_spec_uint64 (&group_generation), + GNUNET_MY_result_spec_uint64 (&msg_flags), + GNUNET_MY_result_spec_uint64 (&flags), + GNUNET_MY_result_spec_variable_size (&buf, + &buf_size), + GNUNET_MY_result_spec_end + }; + + do + { + sql_ret = GNUNET_MY_extract_result (stmt, results); + switch (sql_ret) + { + case GNUNET_NO: + if (ret != GNUNET_YES) + ret = GNUNET_NO; + break; + + case GNUNET_YES: + mp = GNUNET_malloc (sizeof (*mp) + buf_size); + + mp->header.size = htons (sizeof (*mp) + buf_size); + mp->header.type = htons (GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE); + mp->hop_counter = htonl (hop_counter); + GNUNET_memcpy (&mp->signature, + signature, + signature_size); + GNUNET_memcpy (&mp->purpose, + purpose, + purpose_size); + mp->fragment_id = GNUNET_htonll (fragment_id); + mp->fragment_offset = GNUNET_htonll (fragment_offset); + mp->message_id = GNUNET_htonll (message_id); + mp->group_generation = GNUNET_htonll (group_generation); + mp->flags = htonl(msg_flags); + + GNUNET_memcpy (&mp[1], + buf, + buf_size); + ret = cb (cb_cls, mp, (enum GNUNET_PSYCSTORE_MessageFlags) flags); + if (NULL != returned_fragments) + (*returned_fragments)++; + GNUNET_MY_cleanup_result (results); + break; + + default: + LOG_MYSQL (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql extract_result", stmt); + } + } + while (GNUNET_YES == sql_ret); + + // for debugging + if (GNUNET_NO == ret) + GNUNET_log (GNUNET_ERROR_TYPE_WARNING | GNUNET_ERROR_TYPE_BULK, + "Empty result set\n"); + + return ret; +} + + +static int +fragment_select (struct Plugin *plugin, + struct GNUNET_MYSQL_StatementHandle *stmt, + struct GNUNET_MY_QueryParam *params, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + int ret = GNUNET_SYSERR; + int sql_ret; + + sql_ret = GNUNET_MY_exec_prepared (plugin->mc, stmt, params); + switch (sql_ret) + { + case GNUNET_NO: + if (ret != GNUNET_YES) + ret = GNUNET_NO; + break; + + case GNUNET_YES: + ret = fragment_row (stmt, cb, cb_cls, returned_fragments); + break; + + default: + LOG_MYSQL (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql exec_prepared", stmt); + } + return ret; +} + + +/** + * Retrieve a message fragment range by fragment ID. + * + * @see GNUNET_PSYCSTORE_fragment_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +fragment_get (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t first_fragment_id, + uint64_t last_fragment_id, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + struct GNUNET_MYSQL_StatementHandle *stmt = plugin->select_fragments; + int ret = GNUNET_SYSERR; + struct GNUNET_MY_QueryParam params_select[] = { + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_uint64 (&first_fragment_id), + GNUNET_MY_query_param_uint64 (&last_fragment_id), + GNUNET_MY_query_param_end + }; + + *returned_fragments = 0; + ret = fragment_select (plugin, stmt, params_select, returned_fragments, cb, cb_cls); + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return ret; +} + + +/** + * Retrieve a message fragment range by fragment ID. + * + * @see GNUNET_PSYCSTORE_fragment_get_latest() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +fragment_get_latest (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t fragment_limit, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + + struct GNUNET_MYSQL_StatementHandle *stmt = plugin->select_latest_fragments; + + int ret = GNUNET_SYSERR; + *returned_fragments = 0; + + struct GNUNET_MY_QueryParam params_select[] = { + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_uint64 (&fragment_limit), + GNUNET_MY_query_param_end + }; + + ret = fragment_select (plugin, stmt, params_select, returned_fragments, cb, cb_cls); + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return ret; +} + + +/** + * Retrieve all fragments of a message ID range. + * + * @see GNUNET_PSYCSTORE_message_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +message_get (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t first_message_id, + uint64_t last_message_id, + uint64_t fragment_limit, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + struct GNUNET_MYSQL_StatementHandle *stmt = plugin->select_messages; + int ret; + + if (0 == fragment_limit) + fragment_limit = UINT64_MAX; + + struct GNUNET_MY_QueryParam params_select[] = { + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_uint64 (&first_message_id), + GNUNET_MY_query_param_uint64 (&last_message_id), + GNUNET_MY_query_param_uint64 (&fragment_limit), + GNUNET_MY_query_param_end + }; + + *returned_fragments = 0; + ret = fragment_select (plugin, stmt, params_select, returned_fragments, cb, cb_cls); + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return ret; +} + + +/** + * Retrieve all fragments of the latest messages. + * + * @see GNUNET_PSYCSTORE_message_get_latest() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +message_get_latest (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_limit, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + + struct GNUNET_MYSQL_StatementHandle *stmt = plugin->select_latest_messages; + + int ret = GNUNET_SYSERR; + *returned_fragments = 0; + + struct GNUNET_MY_QueryParam params_select[] = { + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_uint64 (&message_limit), + GNUNET_MY_query_param_end + }; + + ret = fragment_select (plugin, stmt, params_select, returned_fragments, cb, cb_cls); + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return ret; +} + + +/** + * Retrieve a fragment of message specified by its message ID and fragment + * offset. + * + * @see GNUNET_PSYCSTORE_message_get_fragment() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +message_get_fragment (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id, + uint64_t fragment_offset, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + struct GNUNET_MYSQL_StatementHandle *stmt = plugin->select_message_fragment; + int sql_ret; + int ret = GNUNET_SYSERR; + + struct GNUNET_MY_QueryParam params_select[] = { + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_uint64 (&message_id), + GNUNET_MY_query_param_uint64 (&fragment_offset), + GNUNET_MY_query_param_end + }; + + sql_ret = GNUNET_MY_exec_prepared (plugin->mc, stmt, params_select); + switch (sql_ret) + { + case GNUNET_NO: + ret = GNUNET_NO; + break; + + case GNUNET_OK: + ret = fragment_row (stmt, cb, cb_cls, NULL); + break; + + default: + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql execute prepared", stmt); + } + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return ret; +} + +/** + * Retrieve the max. values of message counters for a channel. + * + * @see GNUNET_PSYCSTORE_counters_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +counters_message_get (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t *max_fragment_id, + uint64_t *max_message_id, + uint64_t *max_group_generation) +{ + struct Plugin *plugin = cls; + + struct GNUNET_MYSQL_StatementHandle *stmt = plugin->select_counters_message; + + int ret = GNUNET_SYSERR; + + struct GNUNET_MY_QueryParam params_select[] = { + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_end + }; + + if (GNUNET_OK != GNUNET_MY_exec_prepared (plugin->mc, stmt, params_select)) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql execute prepared", stmt); + return GNUNET_SYSERR; + } + + struct GNUNET_MY_ResultSpec results_select[] = { + GNUNET_MY_result_spec_uint64 (max_fragment_id), + GNUNET_MY_result_spec_uint64 (max_message_id), + GNUNET_MY_result_spec_uint64 (max_group_generation), + GNUNET_MY_result_spec_end + }; + + ret = GNUNET_MY_extract_result (stmt, results_select); + + if (GNUNET_OK != ret) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql extract_result", stmt); + return GNUNET_SYSERR; + } + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return ret; +} + +/** + * Retrieve the max. values of state counters for a channel. + * + * @see GNUNET_PSYCSTORE_counters_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +counters_state_get (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t *max_state_message_id) +{ + struct Plugin *plugin = cls; + + struct GNUNET_MYSQL_StatementHandle *stmt = plugin->select_counters_state; + + int ret = GNUNET_SYSERR; + + struct GNUNET_MY_QueryParam params_select[] = { + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_end + }; + + if (GNUNET_OK != GNUNET_MY_exec_prepared (plugin->mc, stmt, params_select)) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql execute prepared", stmt); + return GNUNET_SYSERR; + } + + struct GNUNET_MY_ResultSpec results_select[] = { + GNUNET_MY_result_spec_uint64 (max_state_message_id), + GNUNET_MY_result_spec_end + }; + + ret = GNUNET_MY_extract_result (stmt, results_select); + + if (GNUNET_OK != ret) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql extract_result", stmt); + return GNUNET_SYSERR; + } + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return ret; +} + + +/** + * Assign a value to a state variable. + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_assign (struct Plugin *plugin, struct GNUNET_MYSQL_StatementHandle *stmt, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, const void *value, size_t value_size) +{ + int ret = GNUNET_SYSERR; + + struct GNUNET_MY_QueryParam params[] = { + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_string (name), + GNUNET_MY_query_param_fixed_size(value, value_size), + GNUNET_MY_query_param_end + }; + + ret = GNUNET_MY_exec_prepared (plugin->mc, stmt, params); + if (GNUNET_OK != ret) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql exec_prepared", stmt); + return GNUNET_SYSERR; + } + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return ret; +} + + +static int +update_message_id (struct Plugin *plugin, struct GNUNET_MYSQL_StatementHandle *stmt, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id) +{ + struct GNUNET_MY_QueryParam params[] = { + GNUNET_MY_query_param_uint64 (&message_id), + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_end + }; + + if (GNUNET_OK != GNUNET_MY_exec_prepared (plugin->mc, + stmt, + params)) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql execute prepared", stmt); + return GNUNET_SYSERR; + } + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/** + * Begin modifying current state. + */ +static int +state_modify_begin (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id, uint64_t state_delta) +{ + struct Plugin *plugin = cls; + + if (state_delta > 0) + { + /** + * We can only apply state modifiers in the current message if modifiers in + * the previous stateful message (message_id - state_delta) were already + * applied. + */ + + uint64_t max_state_message_id = 0; + int ret = counters_state_get (plugin, channel_key, &max_state_message_id); + switch (ret) + { + case GNUNET_OK: + case GNUNET_NO: // no state yet + ret = GNUNET_OK; + break; + default: + return ret; + } + + if (max_state_message_id < message_id - state_delta) + return GNUNET_NO; /* some stateful messages not yet applied */ + else if (message_id - state_delta < max_state_message_id) + return GNUNET_NO; /* changes already applied */ + } + + if (TRANSACTION_NONE != plugin->transaction) + { + /** @todo FIXME: wait for other transaction to finish */ + return GNUNET_SYSERR; + } + return transaction_begin (plugin, TRANSACTION_STATE_MODIFY); +} + + +/** + * Set the current value of state variable. + * + * @see GNUNET_PSYCSTORE_state_modify() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_modify_op (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + enum GNUNET_PSYC_Operator op, + const char *name, const void *value, size_t value_size) +{ + struct Plugin *plugin = cls; + GNUNET_assert (TRANSACTION_STATE_MODIFY == plugin->transaction); + + switch (op) + { + case GNUNET_PSYC_OP_ASSIGN: + return state_assign (plugin, plugin->insert_state_current, + channel_key, name, value, value_size); + + default: /** @todo implement more state operations */ + GNUNET_break (0); + return GNUNET_SYSERR; + } +} + + +/** + * End modifying current state. + */ +static int +state_modify_end (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id) +{ + struct Plugin *plugin = cls; + GNUNET_assert (TRANSACTION_STATE_MODIFY == plugin->transaction); + + return + GNUNET_OK == exec_channel (plugin, plugin->delete_state_empty, channel_key) + && GNUNET_OK == update_message_id (plugin, + plugin->update_max_state_message_id, + channel_key, message_id) + && GNUNET_OK == transaction_commit (plugin) + ? GNUNET_OK : GNUNET_SYSERR; +} + + +/** + * Begin state synchronization. + */ +static int +state_sync_begin (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key) +{ + struct Plugin *plugin = cls; + return exec_channel (plugin, plugin->delete_state_sync, channel_key); +} + + +/** + * Assign current value of a state variable. + * + * @see GNUNET_PSYCSTORE_state_modify() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_sync_assign (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, const void *value, size_t value_size) +{ + struct Plugin *plugin = cls; + return state_assign (cls, plugin->insert_state_sync, + channel_key, name, value, value_size); +} + + +/** + * End modifying current state. + */ +static int +state_sync_end (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t max_state_message_id, + uint64_t state_hash_message_id) +{ + struct Plugin *plugin = cls; + int ret = GNUNET_SYSERR; + + if (TRANSACTION_NONE != plugin->transaction) + { + /** @todo FIXME: wait for other transaction to finish */ + return GNUNET_SYSERR; + } + + GNUNET_OK == transaction_begin (plugin, TRANSACTION_STATE_SYNC) + && GNUNET_OK == exec_channel (plugin, plugin->delete_state, channel_key) + && GNUNET_OK == exec_channel (plugin, plugin->insert_state_from_sync, + channel_key) + && GNUNET_OK == exec_channel (plugin, plugin->delete_state_sync, + channel_key) + && GNUNET_OK == update_message_id (plugin, + plugin->update_state_hash_message_id, + channel_key, state_hash_message_id) + && GNUNET_OK == update_message_id (plugin, + plugin->update_max_state_message_id, + channel_key, max_state_message_id) + && GNUNET_OK == transaction_commit (plugin) + ? ret = GNUNET_OK + : transaction_rollback (plugin); + return ret; +} + + +/** + * Delete the whole state. + * + * @see GNUNET_PSYCSTORE_state_reset() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_reset (void *cls, const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key) +{ + struct Plugin *plugin = cls; + return exec_channel (plugin, plugin->delete_state, channel_key); +} + + +/** + * Update signed values of state variables in the state store. + * + * @see GNUNET_PSYCSTORE_state_hash_update() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_update_signed (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key) +{ + struct Plugin *plugin = cls; + return exec_channel (plugin, plugin->update_state_signed, channel_key); +} + + +/** + * Retrieve a state variable by name. + * + * @see GNUNET_PSYCSTORE_state_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_get (void *cls, const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, GNUNET_PSYCSTORE_StateCallback cb, void *cb_cls) +{ + struct Plugin *plugin = cls; + int ret = GNUNET_SYSERR; + int sql_ret ; + + struct GNUNET_MYSQL_StatementHandle *stmt = plugin->select_state_one; + + struct GNUNET_MY_QueryParam params_select[] = { + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_string (name), + GNUNET_MY_query_param_end + }; + + void *value_current = NULL; + size_t value_size = 0; + + struct GNUNET_MY_ResultSpec results[] = { + GNUNET_MY_result_spec_variable_size (&value_current, &value_size), + GNUNET_MY_result_spec_end + }; + + if (GNUNET_OK != GNUNET_MY_exec_prepared (plugin->mc, stmt, params_select)) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql exec_prepared", stmt); + } + else + { + sql_ret = GNUNET_MY_extract_result (stmt, results); + switch (sql_ret) + { + case GNUNET_NO: + ret = GNUNET_NO; + break; + + case GNUNET_YES: + ret = cb (cb_cls, name, value_current, value_size); + break; + + default: + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql extract_result", stmt); + } + } + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return ret; +} + + +/** + * Retrieve all state variables for a channel with the given prefix. + * + * @see GNUNET_PSYCSTORE_state_get_prefix() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_get_prefix (void *cls, const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, GNUNET_PSYCSTORE_StateCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + int ret = GNUNET_SYSERR; + + struct GNUNET_MYSQL_StatementHandle *stmt = plugin->select_state_prefix; + + uint32_t name_len = (uint32_t) strlen (name); + + struct GNUNET_MY_QueryParam params_select[] = { + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_string (name), + GNUNET_MY_query_param_uint32 (&name_len), + GNUNET_MY_query_param_string (name), + GNUNET_MY_query_param_end + }; + + char *name2 = ""; + void *value_current = NULL; + size_t value_size = 0; + + struct GNUNET_MY_ResultSpec results[] = { + GNUNET_MY_result_spec_string (&name2), + GNUNET_MY_result_spec_variable_size (&value_current, &value_size), + GNUNET_MY_result_spec_end + };; + + int sql_ret; + + if (GNUNET_OK != GNUNET_MY_exec_prepared (plugin->mc, stmt, params_select)) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql exec_prepared", stmt); + return GNUNET_SYSERR; + } + + do + { + sql_ret = GNUNET_MY_extract_result (stmt, results); + switch (sql_ret) + { + case GNUNET_NO: + if (ret != GNUNET_YES) + ret = GNUNET_NO; + break; + + case GNUNET_YES: + ret = cb (cb_cls, (const char *) name2, value_current, value_size); + + if (ret != GNUNET_YES) + sql_ret = GNUNET_NO; + break; + + default: + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql extract_result", stmt); + } + } + while (sql_ret == GNUNET_YES); + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return ret; +} + + +/** + * Retrieve all signed state variables for a channel. + * + * @see GNUNET_PSYCSTORE_state_get_signed() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_get_signed (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + GNUNET_PSYCSTORE_StateCallback cb, void *cb_cls) +{ + struct Plugin *plugin = cls; + int ret = GNUNET_SYSERR; + + struct GNUNET_MYSQL_StatementHandle *stmt = plugin->select_state_signed; + + struct GNUNET_MY_QueryParam params_select[] = { + GNUNET_MY_query_param_auto_from_type (channel_key), + GNUNET_MY_query_param_end + }; + + int sql_ret; + + char *name = ""; + void *value_signed = NULL; + size_t value_size = 0; + + struct GNUNET_MY_ResultSpec results[] = { + GNUNET_MY_result_spec_string (&name), + GNUNET_MY_result_spec_variable_size (&value_signed, &value_size), + GNUNET_MY_result_spec_end + }; + + if (GNUNET_OK != GNUNET_MY_exec_prepared (plugin->mc, stmt, params_select)) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql exec_prepared", stmt); + return GNUNET_SYSERR; + } + + do + { + sql_ret = GNUNET_MY_extract_result (stmt, results); + switch (sql_ret) + { + case GNUNET_NO: + if (ret != GNUNET_YES) + ret = GNUNET_NO; + break; + + case GNUNET_YES: + ret = cb (cb_cls, (const char *) name, value_signed, value_size); + + if (ret != GNUNET_YES) + sql_ret = GNUNET_NO; + break; + + default: + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql extract_result", stmt); + } + } + while (sql_ret == GNUNET_YES); + + if (0 != mysql_stmt_reset (GNUNET_MYSQL_statement_get_stmt (stmt))) + { + LOG_MYSQL(plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "mysql_stmt_reset", stmt); + return GNUNET_SYSERR; + } + + return ret; +} + + +/** + * Entry point for the plugin. + * + * @param cls The struct GNUNET_CONFIGURATION_Handle. + * @return NULL on error, otherwise the plugin context + */ +void * +libgnunet_plugin_psycstore_mysql_init (void *cls) +{ + static struct Plugin plugin; + const struct GNUNET_CONFIGURATION_Handle *cfg = cls; + struct GNUNET_PSYCSTORE_PluginFunctions *api; + + if (NULL != plugin.cfg) + return NULL; /* can only initialize once! */ + memset (&plugin, 0, sizeof (struct Plugin)); + plugin.cfg = cfg; + if (GNUNET_OK != database_setup (&plugin)) + { + database_shutdown (&plugin); + return NULL; + } + api = GNUNET_new (struct GNUNET_PSYCSTORE_PluginFunctions); + api->cls = &plugin; + api->membership_store = &mysql_membership_store; + api->membership_test = &membership_test; + api->fragment_store = &fragment_store; + api->message_add_flags = &message_add_flags; + api->fragment_get = &fragment_get; + api->fragment_get_latest = &fragment_get_latest; + api->message_get = &message_get; + api->message_get_latest = &message_get_latest; + api->message_get_fragment = &message_get_fragment; + api->counters_message_get = &counters_message_get; + api->counters_state_get = &counters_state_get; + api->state_modify_begin = &state_modify_begin; + api->state_modify_op = &state_modify_op; + api->state_modify_end = &state_modify_end; + api->state_sync_begin = &state_sync_begin; + api->state_sync_assign = &state_sync_assign; + api->state_sync_end = &state_sync_end; + api->state_reset = &state_reset; + api->state_update_signed = &state_update_signed; + api->state_get = &state_get; + api->state_get_prefix = &state_get_prefix; + api->state_get_signed = &state_get_signed; + + LOG (GNUNET_ERROR_TYPE_INFO, _("Mysql database running\n")); + return api; +} + + +/** + * Exit point from the plugin. + * + * @param cls The plugin context (as returned by "init") + * @return Always NULL + */ +void * +libgnunet_plugin_psycstore_mysql_done (void *cls) +{ + struct GNUNET_PSYCSTORE_PluginFunctions *api = cls; + struct Plugin *plugin = api->cls; + + database_shutdown (plugin); + plugin->cfg = NULL; + GNUNET_free (api); + LOG (GNUNET_ERROR_TYPE_DEBUG, "Mysql plugin is finished\n"); + return NULL; +} + +/* end of plugin_psycstore_mysql.c */ diff --git a/src/psycstore/plugin_psycstore_postgres.c b/src/psycstore/plugin_psycstore_postgres.c new file mode 100644 index 0000000..33c9960 --- /dev/null +++ b/src/psycstore/plugin_psycstore_postgres.c @@ -0,0 +1,1530 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2016 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file psycstore/plugin_psycstore_postgres.c + * @brief PostgresQL-based psycstore backend + * @author Daniel Golle + * @author Gabor X Toth + * @author Christian Grothoff + * @author Christophe Genevey + * @author Jeffrey Burdges + */ + +#include "platform.h" +#include "gnunet_psycstore_plugin.h" +#include "gnunet_psycstore_service.h" +#include "gnunet_multicast_service.h" +#include "gnunet_crypto_lib.h" +#include "gnunet_psyc_util_lib.h" +#include "psycstore.h" +#include "gnunet_pq_lib.h" + +/** + * After how many ms "busy" should a DB operation fail for good? A + * low value makes sure that we are more responsive to requests + * (especially PUTs). A high value guarantees a higher success rate + * (SELECTs in iterate can take several seconds despite LIMIT=1). + * + * The default value of 1s should ensure that users do not experience + * huge latencies while at the same time allowing operations to + * succeed with reasonable probability. + */ +#define BUSY_TIMEOUT_MS 1000 + +#define DEBUG_PSYCSTORE GNUNET_EXTRA_LOGGING + +#define LOG(kind,...) GNUNET_log_from (kind, "psycstore-postgres", __VA_ARGS__) + +enum Transactions { + TRANSACTION_NONE = 0, + TRANSACTION_STATE_MODIFY, + TRANSACTION_STATE_SYNC, +}; + +/** + * Context for all functions in this plugin. + */ +struct Plugin +{ + + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Native Postgres database handle. + */ + PGconn *dbh; + + enum Transactions transaction; + + void *cls; +}; + + +/** + * Initialize the database connections and associated + * data structures (create tables and indices + * as needed as well). + * + * @param plugin the plugin context (state for this module) + * @return #GNUNET_OK on success + */ +static int +database_setup (struct Plugin *plugin) +{ + struct GNUNET_PQ_ExecuteStatement es[] = { + GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS channels (\n" + " id SERIAL,\n" + " pub_key BYTEA NOT NULL CHECK (LENGTH(pub_key)=32),\n" + " max_state_message_id BIGINT,\n" + " state_hash_message_id BIGINT,\n" + " PRIMARY KEY(id)\n" + ")" + "WITH OIDS"), + GNUNET_PQ_make_execute ("CREATE UNIQUE INDEX IF NOT EXISTS channel_pub_key_idx \n" + " ON channels (pub_key)"), + GNUNET_PQ_make_execute ("CREATE OR REPLACE FUNCTION get_chan_id(BYTEA) RETURNS INTEGER AS \n" + " 'SELECT id FROM channels WHERE pub_key=$1;' LANGUAGE SQL STABLE \n" + "RETURNS NULL ON NULL INPUT"), + GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS slaves (\n" + " id SERIAL,\n" + " pub_key BYTEA NOT NULL CHECK (LENGTH(pub_key)=32),\n" + " PRIMARY KEY(id)\n" + ")" + "WITH OIDS"), + GNUNET_PQ_make_execute ("CREATE UNIQUE INDEX IF NOT EXISTS slaves_pub_key_idx \n" + " ON slaves (pub_key)"), + GNUNET_PQ_make_execute ("CREATE OR REPLACE FUNCTION get_slave_id(BYTEA) RETURNS INTEGER AS \n" + " 'SELECT id FROM slaves WHERE pub_key=$1;' LANGUAGE SQL STABLE \n" + "RETURNS NULL ON NULL INPUT"), + GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS membership (\n" + " channel_id BIGINT NOT NULL REFERENCES channels(id),\n" + " slave_id BIGINT NOT NULL REFERENCES slaves(id),\n" + " did_join INT NOT NULL,\n" + " announced_at BIGINT NOT NULL,\n" + " effective_since BIGINT NOT NULL,\n" + " group_generation BIGINT NOT NULL\n" + ")" + "WITH OIDS"), + GNUNET_PQ_make_execute ("CREATE INDEX IF NOT EXISTS idx_membership_channel_id_slave_id " + "ON membership (channel_id, slave_id)"), + /** @todo messages table: add method_name column */ + GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS messages (\n" + " channel_id BIGINT NOT NULL REFERENCES channels(id),\n" + " hop_counter INT NOT NULL,\n" + " signature BYTEA CHECK (LENGTH(signature)=64),\n" + " purpose BYTEA CHECK (LENGTH(purpose)=8),\n" + " fragment_id BIGINT NOT NULL,\n" + " fragment_offset BIGINT NOT NULL,\n" + " message_id BIGINT NOT NULL,\n" + " group_generation BIGINT NOT NULL,\n" + " multicast_flags INT NOT NULL,\n" + " psycstore_flags INT NOT NULL,\n" + " data BYTEA,\n" + " PRIMARY KEY (channel_id, fragment_id),\n" + " UNIQUE (channel_id, message_id, fragment_offset)\n" + ")" + "WITH OIDS"), + GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS state (\n" + " channel_id BIGINT NOT NULL REFERENCES channels(id),\n" + " name TEXT NOT NULL,\n" + " value_current BYTEA,\n" + " value_signed BYTEA,\n" + " PRIMARY KEY (channel_id, name)\n" + ")" + "WITH OIDS"), + GNUNET_PQ_make_execute ("CREATE TABLE IF NOT EXISTS state_sync (\n" + " channel_id BIGINT NOT NULL REFERENCES channels(id),\n" + " name TEXT NOT NULL,\n" + " value BYTEA,\n" + " PRIMARY KEY (channel_id, name)\n" + ")" + "WITH OIDS"), + GNUNET_PQ_EXECUTE_STATEMENT_END + }; + + /* Open database and precompile statements */ + plugin->dbh = GNUNET_PQ_connect_with_cfg (plugin->cfg, + "psycstore-postgres"); + if (NULL == plugin->dbh) + return GNUNET_SYSERR; + if (GNUNET_OK != + GNUNET_PQ_exec_statements (plugin->dbh, + es)) + { + PQfinish (plugin->dbh); + plugin->dbh = NULL; + return GNUNET_SYSERR; + } + + /* Prepare statements */ + { + struct GNUNET_PQ_PreparedStatement ps[] = { + GNUNET_PQ_make_prepare ("transaction_begin", + "BEGIN", 0), + GNUNET_PQ_make_prepare ("transaction_commit", + "COMMIT", 0), + GNUNET_PQ_make_prepare ("transaction_rollback", + "ROLLBACK", 0), + GNUNET_PQ_make_prepare ("insert_channel_key", + "INSERT INTO channels (pub_key) VALUES ($1)" + " ON CONFLICT DO NOTHING", 1), + GNUNET_PQ_make_prepare ("insert_slave_key", + "INSERT INTO slaves (pub_key) VALUES ($1)" + " ON CONFLICT DO NOTHING", 1), + GNUNET_PQ_make_prepare ("insert_membership", + "INSERT INTO membership\n" + " (channel_id, slave_id, did_join, announced_at,\n" + " effective_since, group_generation)\n" + "VALUES (get_chan_id($1),\n" + " get_slave_id($2),\n" + " $3, $4, $5, $6)", 6), + GNUNET_PQ_make_prepare ("select_membership", + "SELECT did_join FROM membership\n" + "WHERE channel_id = get_chan_id($1)\n" + " AND slave_id = get_slave_id($2)\n" + " AND effective_since <= $3 AND did_join = 1\n" + "ORDER BY announced_at DESC LIMIT 1", 3), + GNUNET_PQ_make_prepare ("insert_fragment", + "INSERT INTO messages\n" + " (channel_id, hop_counter, signature, purpose,\n" + " fragment_id, fragment_offset, message_id,\n" + " group_generation, multicast_flags, psycstore_flags, data)\n" + "VALUES (get_chan_id($1),\n" + " $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)" + "ON CONFLICT DO NOTHING", 11), + GNUNET_PQ_make_prepare ("update_message_flags", + "UPDATE messages\n" + "SET psycstore_flags = psycstore_flags | $1\n" + "WHERE channel_id = get_chan_id($2) \n" + " AND message_id = $3 AND fragment_offset = 0", 3), + GNUNET_PQ_make_prepare ("select_fragments", + "SELECT hop_counter, signature, purpose, fragment_id,\n" + " fragment_offset, message_id, group_generation,\n" + " multicast_flags, psycstore_flags, data\n" + "FROM messages\n" + "WHERE channel_id = get_chan_id($1) \n" + " AND $2 <= fragment_id AND fragment_id <= $3", 3), + /** @todo select_messages: add method_prefix filter */ + GNUNET_PQ_make_prepare ("select_messages", + "SELECT hop_counter, signature, purpose, fragment_id,\n" + " fragment_offset, message_id, group_generation,\n" + " multicast_flags, psycstore_flags, data\n" + "FROM messages\n" + "WHERE channel_id = get_chan_id($1) \n" + " AND $2 <= message_id AND message_id <= $3\n" + "LIMIT $4;", 4), + /** @todo select_latest_messages: add method_prefix filter */ + GNUNET_PQ_make_prepare ("select_latest_fragments", + "SELECT rev.hop_counter AS hop_counter,\n" + " rev.signature AS signature,\n" + " rev.purpose AS purpose,\n" + " rev.fragment_id AS fragment_id,\n" + " rev.fragment_offset AS fragment_offset,\n" + " rev.message_id AS message_id,\n" + " rev.group_generation AS group_generation,\n" + " rev.multicast_flags AS multicast_flags,\n" + " rev.psycstore_flags AS psycstore_flags,\n" + " rev.data AS data\n" + " FROM\n" + " (SELECT hop_counter, signature, purpose, fragment_id,\n" + " fragment_offset, message_id, group_generation,\n" + " multicast_flags, psycstore_flags, data \n" + " FROM messages\n" + " WHERE channel_id = get_chan_id($1) \n" + " ORDER BY fragment_id DESC\n" + " LIMIT $2) AS rev\n" + " ORDER BY rev.fragment_id;", 2), + GNUNET_PQ_make_prepare ("select_latest_messages", + "SELECT hop_counter, signature, purpose, fragment_id,\n" + " fragment_offset, message_id, group_generation,\n" + " multicast_flags, psycstore_flags, data\n" + "FROM messages\n" + "WHERE channel_id = get_chan_id($1)\n" + " AND message_id IN\n" + " (SELECT message_id\n" + " FROM messages\n" + " WHERE channel_id = get_chan_id($2) \n" + " GROUP BY message_id\n" + " ORDER BY message_id\n" + " DESC LIMIT $3)\n" + "ORDER BY fragment_id", 3), + GNUNET_PQ_make_prepare ("select_message_fragment", + "SELECT hop_counter, signature, purpose, fragment_id,\n" + " fragment_offset, message_id, group_generation,\n" + " multicast_flags, psycstore_flags, data\n" + "FROM messages\n" + "WHERE channel_id = get_chan_id($1) \n" + " AND message_id = $2 AND fragment_offset = $3", 3), + GNUNET_PQ_make_prepare ("select_counters_message", + "SELECT fragment_id, message_id, group_generation\n" + "FROM messages\n" + "WHERE channel_id = get_chan_id($1)\n" + "ORDER BY fragment_id DESC LIMIT 1", 1), + GNUNET_PQ_make_prepare ("select_counters_state", + "SELECT max_state_message_id\n" + "FROM channels\n" + "WHERE pub_key = $1 AND max_state_message_id IS NOT NULL", 1), + GNUNET_PQ_make_prepare ("update_max_state_message_id", + "UPDATE channels\n" + "SET max_state_message_id = $1\n" + "WHERE pub_key = $2", 2), + + GNUNET_PQ_make_prepare ("update_state_hash_message_id", + "UPDATE channels\n" + "SET state_hash_message_id = $1\n" + "WHERE pub_key = $2", 2), + GNUNET_PQ_make_prepare ("insert_state_current", + "INSERT INTO state\n" + " (channel_id, name, value_current, value_signed)\n" + "SELECT new.channel_id, new.name,\n" + " new.value_current, old.value_signed\n" + "FROM (SELECT get_chan_id($1) AS channel_id,\n" + " $2::TEXT AS name, $3::BYTEA AS value_current) AS new\n" + "LEFT JOIN (SELECT channel_id, name, value_signed\n" + " FROM state) AS old\n" + "ON new.channel_id = old.channel_id AND new.name = old.name\n" + "ON CONFLICT (channel_id, name)\n" + " DO UPDATE SET value_current = EXCLUDED.value_current,\n" + " value_signed = EXCLUDED.value_signed", 3), + GNUNET_PQ_make_prepare ("delete_state_empty", + "DELETE FROM state\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = $1)\n" + " AND (value_current IS NULL OR length(value_current) = 0)\n" + " AND (value_signed IS NULL OR length(value_signed) = 0)", 1), + GNUNET_PQ_make_prepare ("update_state_signed", + "UPDATE state\n" + "SET value_signed = value_current\n" + "WHERE channel_id = get_chan_id($1) ", 1), + GNUNET_PQ_make_prepare ("delete_state", + "DELETE FROM state\n" + "WHERE channel_id = get_chan_id($1) ", 1), + GNUNET_PQ_make_prepare ("insert_state_sync", + "INSERT INTO state_sync (channel_id, name, value)\n" + "VALUES (get_chan_id($1), $2, $3)", 3), + GNUNET_PQ_make_prepare ("insert_state_from_sync", + "INSERT INTO state\n" + " (channel_id, name, value_current, value_signed)\n" + "SELECT channel_id, name, value, value\n" + "FROM state_sync\n" + "WHERE channel_id = get_chan_id($1)", 1), + GNUNET_PQ_make_prepare ("delete_state_sync", + "DELETE FROM state_sync\n" + "WHERE channel_id = get_chan_id($1)", 1), + GNUNET_PQ_make_prepare ("select_state_one", + "SELECT value_current\n" + "FROM state\n" + "WHERE channel_id = get_chan_id($1)\n" + " AND name = $2", 2), + GNUNET_PQ_make_prepare ("select_state_prefix", + "SELECT name, value_current\n" + "FROM state\n" + "WHERE channel_id = get_chan_id($1)\n" + " AND (name = $2 OR substr(name, 1, $3) = $4)", 4), + GNUNET_PQ_make_prepare ("select_state_signed", + "SELECT name, value_signed\n" + "FROM state\n" + "WHERE channel_id = get_chan_id($1)\n" + " AND value_signed IS NOT NULL", 1), + GNUNET_PQ_PREPARED_STATEMENT_END + }; + + if (GNUNET_OK != + GNUNET_PQ_prepare_statements (plugin->dbh, + ps)) + { + PQfinish (plugin->dbh); + plugin->dbh = NULL; + return GNUNET_SYSERR; + } + } + + return GNUNET_OK; +} + + +/** + * Shutdown database connection and associate data + * structures. + * @param plugin the plugin context (state for this module) + */ +static void +database_shutdown (struct Plugin *plugin) +{ + PQfinish (plugin->dbh); + plugin->dbh = NULL; +} + + +/** + * Execute a prepared statement with a @a channel_key argument. + * + * @param plugin Plugin handle. + * @param stmt Statement to execute. + * @param channel_key Public key of the channel. + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +exec_channel (struct Plugin *plugin, const char *stmt, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key) +{ + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_end + }; + + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != + GNUNET_PQ_eval_prepared_non_select (plugin->dbh, stmt, params)) + return GNUNET_SYSERR; + + return GNUNET_OK; +} + + +/** + * Begin a transaction. + */ +static int +transaction_begin (struct Plugin *plugin, enum Transactions transaction) +{ + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_end + }; + + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != + GNUNET_PQ_eval_prepared_non_select (plugin->dbh, "transaction_begin", params)) + return GNUNET_SYSERR; + + plugin->transaction = transaction; + return GNUNET_OK; +} + + +/** + * Commit current transaction. + */ +static int +transaction_commit (struct Plugin *plugin) +{ + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_end + }; + + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != + GNUNET_PQ_eval_prepared_non_select (plugin->dbh, "transaction_commit", params)) + return GNUNET_SYSERR; + + plugin->transaction = TRANSACTION_NONE; + return GNUNET_OK; +} + + +/** + * Roll back current transaction. + */ +static int +transaction_rollback (struct Plugin *plugin) +{ + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_end + }; + + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != + GNUNET_PQ_eval_prepared_non_select (plugin->dbh, "transaction_rollback", params)) + return GNUNET_SYSERR; + + plugin->transaction = TRANSACTION_NONE; + return GNUNET_OK; +} + + +static int +channel_key_store (struct Plugin *plugin, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key) +{ + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_end + }; + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + GNUNET_PQ_eval_prepared_non_select (plugin->dbh, + "insert_channel_key", + params)) + return GNUNET_SYSERR; + + return GNUNET_OK; +} + + +static int +slave_key_store (struct Plugin *plugin, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key) +{ + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (slave_key), + GNUNET_PQ_query_param_end + }; + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + GNUNET_PQ_eval_prepared_non_select (plugin->dbh, "insert_slave_key", params)) + return GNUNET_SYSERR; + + return GNUNET_OK; +} + + +/** + * Store join/leave events for a PSYC channel in order to be able to answer + * membership test queries later. + * + * @see GNUNET_PSYCSTORE_membership_store() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +postgres_membership_store (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + int did_join, + uint64_t announced_at, + uint64_t effective_since, + uint64_t group_generation) +{ + struct Plugin *plugin = cls; + uint32_t idid_join = (uint32_t) did_join; + + GNUNET_assert (TRANSACTION_NONE == plugin->transaction); + + if ( (announced_at > INT64_MAX) || + (effective_since > INT64_MAX) || + (group_generation > INT64_MAX) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if ( (GNUNET_OK != + channel_key_store (plugin, channel_key)) || + (GNUNET_OK != + slave_key_store (plugin, slave_key)) ) + return GNUNET_SYSERR; + + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_auto_from_type (slave_key), + GNUNET_PQ_query_param_uint32 (&idid_join), + GNUNET_PQ_query_param_uint64 (&announced_at), + GNUNET_PQ_query_param_uint64 (&effective_since), + GNUNET_PQ_query_param_uint64 (&group_generation), + GNUNET_PQ_query_param_end + }; + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + GNUNET_PQ_eval_prepared_non_select (plugin->dbh, + "insert_membership", + params)) + return GNUNET_SYSERR; + + return GNUNET_OK; +} + +/** + * Test if a member was admitted to the channel at the given message ID. + * + * @see GNUNET_PSYCSTORE_membership_test() + * + * @return #GNUNET_YES if the member was admitted, #GNUNET_NO if not, + * #GNUNET_SYSERR if there was en error. + */ +static int +membership_test (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + uint64_t message_id) +{ + struct Plugin *plugin = cls; + + uint32_t did_join = 0; + + struct GNUNET_PQ_QueryParam params_select[] = { + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_auto_from_type (slave_key), + GNUNET_PQ_query_param_uint64 (&message_id), + GNUNET_PQ_query_param_end + }; + + struct GNUNET_PQ_ResultSpec results_select[] = { + GNUNET_PQ_result_spec_uint32 ("did_join", &did_join), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh, "select_membership", + params_select, results_select)) + return GNUNET_SYSERR; + + return GNUNET_OK; +} + +/** + * Store a message fragment sent to a channel. + * + * @see GNUNET_PSYCSTORE_fragment_store() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +fragment_store (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_MULTICAST_MessageHeader *msg, + uint32_t psycstore_flags) +{ + struct Plugin *plugin = cls; + + GNUNET_assert (TRANSACTION_NONE == plugin->transaction); + + uint64_t fragment_id = GNUNET_ntohll (msg->fragment_id); + + uint64_t fragment_offset = GNUNET_ntohll (msg->fragment_offset); + uint64_t message_id = GNUNET_ntohll (msg->message_id); + uint64_t group_generation = GNUNET_ntohll (msg->group_generation); + + uint32_t hop_counter = ntohl(msg->hop_counter); + uint32_t flags = ntohl(msg->flags); + + if (fragment_id > INT64_MAX || fragment_offset > INT64_MAX || + message_id > INT64_MAX || group_generation > INT64_MAX) + { + LOG(GNUNET_ERROR_TYPE_ERROR, + "Tried to store fragment with a field > INT64_MAX: " + "%lu, %lu, %lu, %lu\n", fragment_id, fragment_offset, + message_id, group_generation); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != channel_key_store (plugin, channel_key)) + return GNUNET_SYSERR; + + struct GNUNET_PQ_QueryParam params_insert[] = { + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_uint32 (&hop_counter), + GNUNET_PQ_query_param_auto_from_type (&msg->signature), + GNUNET_PQ_query_param_auto_from_type (&msg->purpose), + GNUNET_PQ_query_param_uint64 (&fragment_id), + GNUNET_PQ_query_param_uint64 (&fragment_offset), + GNUNET_PQ_query_param_uint64 (&message_id), + GNUNET_PQ_query_param_uint64 (&group_generation), + GNUNET_PQ_query_param_uint32 (&flags), + GNUNET_PQ_query_param_uint32 (&psycstore_flags), + GNUNET_PQ_query_param_fixed_size (&msg[1], ntohs (msg->header.size) - sizeof (*msg)), + GNUNET_PQ_query_param_end + }; + + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != + GNUNET_PQ_eval_prepared_non_select (plugin->dbh, "insert_fragment", params_insert)) + return GNUNET_SYSERR; + + return GNUNET_OK; +} + +/** + * Set additional flags for a given message. + * + * They are OR'd with any existing flags set. + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +message_add_flags (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id, + uint32_t psycstore_flags) +{ + struct Plugin *plugin = cls; + + struct GNUNET_PQ_QueryParam params_update[] = { + GNUNET_PQ_query_param_uint32 (&psycstore_flags), + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_uint64 (&message_id), + GNUNET_PQ_query_param_end + }; + + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != + GNUNET_PQ_eval_prepared_non_select (plugin->dbh, "update_message_flags", params_update)) + return GNUNET_SYSERR; + + return GNUNET_OK; +} + + +/** + * Closure for #fragment_rows. + */ +struct FragmentRowsContext { + GNUNET_PSYCSTORE_FragmentCallback cb; + void *cb_cls; + + uint64_t *returned_fragments; + + /* I preserved this but I do not see the point since + * it cannot stop the loop early and gets overwritten ?? */ + int ret; +}; + + +/** + * Callback that retrieves the results of a SELECT statement + * reading form the messages table. + * + * Only passed to GNUNET_PQ_eval_prepared_multi_select and + * has type GNUNET_PQ_PostgresResultHandler. + * + * @param cls closure + * @param result the postgres result + * @param num_result the number of results in @a result + */ +void fragment_rows (void *cls, + PGresult *res, + unsigned int num_results) +{ + struct FragmentRowsContext *c = cls; + + for (unsigned int i=0;iheader.size = htons (sizeof (*mp) + buf_size); + mp->header.type = htons (GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE); + mp->hop_counter = htonl (hop_counter); + GNUNET_memcpy (&mp->signature, + signature, signature_size); + GNUNET_memcpy (&mp->purpose, + purpose, purpose_size); + mp->fragment_id = GNUNET_htonll (fragment_id); + mp->fragment_offset = GNUNET_htonll (fragment_offset); + mp->message_id = GNUNET_htonll (message_id); + mp->group_generation = GNUNET_htonll (group_generation); + mp->flags = htonl(msg_flags); + + GNUNET_memcpy (&mp[1], + buf, buf_size); + GNUNET_PQ_cleanup_result(results); + c->ret = c->cb (c->cb_cls, mp, (enum GNUNET_PSYCSTORE_MessageFlags) flags); + if (NULL != c->returned_fragments) + (*c->returned_fragments)++; + } +} + + +static int +fragment_select (struct Plugin *plugin, + const char *stmt, + struct GNUNET_PQ_QueryParam *params, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + /* Stack based closure */ + struct FragmentRowsContext frc = { + .cb = cb, + .cb_cls = cb_cls, + .returned_fragments = returned_fragments, + .ret = GNUNET_SYSERR + }; + + if (0 > GNUNET_PQ_eval_prepared_multi_select (plugin->dbh, + stmt, params, + &fragment_rows, &frc)) + return GNUNET_SYSERR; + return frc.ret; /* GNUNET_OK ?? */ +} + +/** + * Retrieve a message fragment range by fragment ID. + * + * @see GNUNET_PSYCSTORE_fragment_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +fragment_get (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t first_fragment_id, + uint64_t last_fragment_id, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + struct GNUNET_PQ_QueryParam params_select[] = { + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_uint64 (&first_fragment_id), + GNUNET_PQ_query_param_uint64 (&last_fragment_id), + GNUNET_PQ_query_param_end + }; + + *returned_fragments = 0; + return fragment_select (plugin, + "select_fragments", + params_select, + returned_fragments, + cb, cb_cls); +} + + +/** + * Retrieve a message fragment range by fragment ID. + * + * @see GNUNET_PSYCSTORE_fragment_get_latest() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +fragment_get_latest (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t fragment_limit, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + + *returned_fragments = 0; + + struct GNUNET_PQ_QueryParam params_select[] = { + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_uint64 (&fragment_limit), + GNUNET_PQ_query_param_end + }; + + return fragment_select (plugin, + "select_latest_fragments", + params_select, + returned_fragments, + cb, cb_cls); +} + + +/** + * Retrieve all fragments of a message ID range. + * + * @see GNUNET_PSYCSTORE_message_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +message_get (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t first_message_id, + uint64_t last_message_id, + uint64_t fragment_limit, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + struct GNUNET_PQ_QueryParam params_select[] = { + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_uint64 (&first_message_id), + GNUNET_PQ_query_param_uint64 (&last_message_id), + GNUNET_PQ_query_param_uint64 (&fragment_limit), + GNUNET_PQ_query_param_end + }; + + if (0 == fragment_limit) + fragment_limit = INT64_MAX; + *returned_fragments = 0; + return fragment_select (plugin, + "select_messages", + params_select, + returned_fragments, + cb, cb_cls); +} + + +/** + * Retrieve all fragments of the latest messages. + * + * @see GNUNET_PSYCSTORE_message_get_latest() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +message_get_latest (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_limit, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + struct GNUNET_PQ_QueryParam params_select[] = { + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_uint64 (&message_limit), + GNUNET_PQ_query_param_end + }; + + *returned_fragments = 0; + return fragment_select (plugin, + "select_latest_messages", + params_select, + returned_fragments, + cb, cb_cls); +} + + +/** + * Retrieve a fragment of message specified by its message ID and fragment + * offset. + * + * @see GNUNET_PSYCSTORE_message_get_fragment() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +message_get_fragment (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id, + uint64_t fragment_offset, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + const char *stmt = "select_message_fragment"; + + struct GNUNET_PQ_QueryParam params_select[] = { + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_uint64 (&message_id), + GNUNET_PQ_query_param_uint64 (&fragment_offset), + GNUNET_PQ_query_param_end + }; + + /* Stack based closure */ + struct FragmentRowsContext frc = { + .cb = cb, + .cb_cls = cb_cls, + .returned_fragments = NULL, + .ret = GNUNET_SYSERR + }; + + if (0 > GNUNET_PQ_eval_prepared_multi_select (plugin->dbh, + stmt, params_select, + &fragment_rows, &frc)) + return GNUNET_SYSERR; + return frc.ret; /* GNUNET_OK ?? */ +} + +/** + * Retrieve the max. values of message counters for a channel. + * + * @see GNUNET_PSYCSTORE_counters_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +counters_message_get (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t *max_fragment_id, + uint64_t *max_message_id, + uint64_t *max_group_generation) +{ + struct Plugin *plugin = cls; + + const char *stmt = "select_counters_message"; + + struct GNUNET_PQ_QueryParam params_select[] = { + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_end + }; + + struct GNUNET_PQ_ResultSpec results_select[] = { + GNUNET_PQ_result_spec_uint64 ("fragment_id", max_fragment_id), + GNUNET_PQ_result_spec_uint64 ("message_id", max_message_id), + GNUNET_PQ_result_spec_uint64 ("group_generation", max_group_generation), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh, stmt, + params_select, results_select)) + return GNUNET_SYSERR; + + return GNUNET_OK; +} + +/** + * Retrieve the max. values of state counters for a channel. + * + * @see GNUNET_PSYCSTORE_counters_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +counters_state_get (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t *max_state_message_id) +{ + struct Plugin *plugin = cls; + + const char *stmt = "select_counters_state"; + + struct GNUNET_PQ_QueryParam params_select[] = { + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_end + }; + + struct GNUNET_PQ_ResultSpec results_select[] = { + GNUNET_PQ_result_spec_uint64 ("max_state_message_id", max_state_message_id), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh, stmt, + params_select, results_select)) + return GNUNET_SYSERR; + + return GNUNET_OK; +} + + +/** + * Assign a value to a state variable. + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_assign (struct Plugin *plugin, const char *stmt, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, const void *value, size_t value_size) +{ + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_string (name), + GNUNET_PQ_query_param_fixed_size (value, value_size), + GNUNET_PQ_query_param_end + }; + + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != + GNUNET_PQ_eval_prepared_non_select (plugin->dbh, stmt, params)) + return GNUNET_SYSERR; + + return GNUNET_OK; +} + + +static int +update_message_id (struct Plugin *plugin, + const char *stmt, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id) +{ + struct GNUNET_PQ_QueryParam params[] = { + GNUNET_PQ_query_param_uint64 (&message_id), + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_end + }; + + if (GNUNET_DB_STATUS_SUCCESS_NO_RESULTS != + GNUNET_PQ_eval_prepared_non_select (plugin->dbh, stmt, params)) + return GNUNET_SYSERR; + + return GNUNET_OK; +} + + +/** + * Begin modifying current state. + */ +static int +state_modify_begin (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id, uint64_t state_delta) +{ + struct Plugin *plugin = cls; + + if (state_delta > 0) + { + /** + * We can only apply state modifiers in the current message if modifiers in + * the previous stateful message (message_id - state_delta) were already + * applied. + */ + + uint64_t max_state_message_id = 0; + int ret = counters_state_get (plugin, channel_key, &max_state_message_id); + switch (ret) + { + case GNUNET_OK: + case GNUNET_NO: // no state yet + ret = GNUNET_OK; + break; + + default: + return ret; + } + + if (max_state_message_id < message_id - state_delta) + return GNUNET_NO; /* some stateful messages not yet applied */ + else if (message_id - state_delta < max_state_message_id) + return GNUNET_NO; /* changes already applied */ + } + + if (TRANSACTION_NONE != plugin->transaction) + { + /** @todo FIXME: wait for other transaction to finish */ + return GNUNET_SYSERR; + } + return transaction_begin (plugin, TRANSACTION_STATE_MODIFY); +} + + +/** + * Set the current value of state variable. + * + * @see GNUNET_PSYCSTORE_state_modify() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_modify_op (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + enum GNUNET_PSYC_Operator op, + const char *name, const void *value, size_t value_size) +{ + struct Plugin *plugin = cls; + GNUNET_assert (TRANSACTION_STATE_MODIFY == plugin->transaction); + + switch (op) + { + case GNUNET_PSYC_OP_ASSIGN: + return state_assign (plugin, "insert_state_current", + channel_key, name, value, value_size); + + default: /** @todo implement more state operations */ + GNUNET_break (0); + return GNUNET_SYSERR; + } +} + + +/** + * End modifying current state. + */ +static int +state_modify_end (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id) +{ + struct Plugin *plugin = cls; + GNUNET_assert (TRANSACTION_STATE_MODIFY == plugin->transaction); + + return + GNUNET_OK == exec_channel (plugin, "delete_state_empty", channel_key) + && GNUNET_OK == update_message_id (plugin, + "update_max_state_message_id", + channel_key, message_id) + && GNUNET_OK == transaction_commit (plugin) + ? GNUNET_OK : GNUNET_SYSERR; +} + + +/** + * Begin state synchronization. + */ +static int +state_sync_begin (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key) +{ + struct Plugin *plugin = cls; + return exec_channel (plugin, "delete_state_sync", channel_key); +} + + +/** + * Assign current value of a state variable. + * + * @see GNUNET_PSYCSTORE_state_modify() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_sync_assign (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, const void *value, size_t value_size) +{ + struct Plugin *plugin = cls; + return state_assign (plugin, "insert_state_sync", + channel_key, name, value, value_size); +} + + +/** + * End modifying current state. + */ +static int +state_sync_end (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t max_state_message_id, + uint64_t state_hash_message_id) +{ + struct Plugin *plugin = cls; + int ret = GNUNET_SYSERR; + + if (TRANSACTION_NONE != plugin->transaction) + { + /** @todo FIXME: wait for other transaction to finish */ + return GNUNET_SYSERR; + } + + GNUNET_OK == transaction_begin (plugin, TRANSACTION_STATE_SYNC) + && GNUNET_OK == exec_channel (plugin, "delete_state", channel_key) + && GNUNET_OK == exec_channel (plugin, "insert_state_from_sync", + channel_key) + && GNUNET_OK == exec_channel (plugin, "delete_state_sync", + channel_key) + && GNUNET_OK == update_message_id (plugin, + "update_state_hash_message_id", + channel_key, state_hash_message_id) + && GNUNET_OK == update_message_id (plugin, + "update_max_state_message_id", + channel_key, max_state_message_id) + && GNUNET_OK == transaction_commit (plugin) + ? ret = GNUNET_OK + : transaction_rollback (plugin); + return ret; +} + + +/** + * Delete the whole state. + * + * @see GNUNET_PSYCSTORE_state_reset() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_reset (void *cls, const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key) +{ + struct Plugin *plugin = cls; + return exec_channel (plugin, "delete_state", channel_key); +} + + +/** + * Update signed values of state variables in the state store. + * + * @see GNUNET_PSYCSTORE_state_hash_update() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_update_signed (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key) +{ + struct Plugin *plugin = cls; + return exec_channel (plugin, "update_state_signed", channel_key); +} + + +/** + * Retrieve a state variable by name. + * + * @see GNUNET_PSYCSTORE_state_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_get (void *cls, const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, GNUNET_PSYCSTORE_StateCallback cb, void *cb_cls) +{ + struct Plugin *plugin = cls; + + const char *stmt = "select_state_one"; + + struct GNUNET_PQ_QueryParam params_select[] = { + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_string (name), + GNUNET_PQ_query_param_end + }; + + void *value_current = NULL; + size_t value_size = 0; + + struct GNUNET_PQ_ResultSpec results_select[] = { + GNUNET_PQ_result_spec_variable_size ("value_current", &value_current, &value_size), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_DB_STATUS_SUCCESS_ONE_RESULT != + GNUNET_PQ_eval_prepared_singleton_select (plugin->dbh, stmt, + params_select, results_select)) + return GNUNET_SYSERR; + + return cb (cb_cls, name, value_current, + value_size); +} + + + +/** + * Closure for #get_state_cb. + */ +struct GetStateContext { + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key; + // const char *name, + GNUNET_PSYCSTORE_StateCallback cb; + void *cb_cls; + + const char *value_id; + + /* I preserved this but I do not see the point since + * it cannot stop the loop early and gets overwritten ?? */ + int ret; +}; + + +/** + * Callback that retrieves the results of a SELECT statement + * reading form the state table. + * + * Only passed to GNUNET_PQ_eval_prepared_multi_select and + * has type GNUNET_PQ_PostgresResultHandler. + * + * @param cls closure + * @param result the postgres result + * @param num_result the number of results in @a result + */ +static void +get_state_cb (void *cls, + PGresult *res, + unsigned int num_results) +{ + struct GetStateContext *c = cls; + + for (unsigned int i=0;ivalue_id, &value, &value_size), + GNUNET_PQ_result_spec_end + }; + + if (GNUNET_YES != GNUNET_PQ_extract_result (res, results, i)) + { + GNUNET_PQ_cleanup_result(results); /* previously invoked via PQclear?? */ + break; /* nothing more?? */ + } + + c->ret = c->cb (c->cb_cls, (const char *) name, value, value_size); + GNUNET_PQ_cleanup_result(results); + } +} + +/** + * Retrieve all state variables for a channel with the given prefix. + * + * @see GNUNET_PSYCSTORE_state_get_prefix() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_get_prefix (void *cls, const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, GNUNET_PSYCSTORE_StateCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + + const char *stmt = "select_state_prefix"; + + uint32_t name_len = (uint32_t) strlen (name); + + struct GNUNET_PQ_QueryParam params_select[] = { + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_string (name), + GNUNET_PQ_query_param_uint32 (&name_len), + GNUNET_PQ_query_param_string (name), + GNUNET_PQ_query_param_end + }; + + struct GetStateContext gsc = { + .cb = cb, + .cb_cls = cb_cls, + .value_id = "value_current", + .ret = GNUNET_NO + }; + + if (0 > GNUNET_PQ_eval_prepared_multi_select (plugin->dbh, + stmt, params_select, + &get_state_cb, &gsc)) + return GNUNET_SYSERR; + return gsc.ret; /* GNUNET_OK ?? */ +} + + +/** + * Retrieve all signed state variables for a channel. + * + * @see GNUNET_PSYCSTORE_state_get_signed() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_get_signed (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + GNUNET_PSYCSTORE_StateCallback cb, void *cb_cls) +{ + struct Plugin *plugin = cls; + + const char *stmt = "select_state_signed"; + + struct GNUNET_PQ_QueryParam params_select[] = { + GNUNET_PQ_query_param_auto_from_type (channel_key), + GNUNET_PQ_query_param_end + }; + + struct GetStateContext gsc = { + .cb = cb, + .cb_cls = cb_cls, + .value_id = "value_signed", + .ret = GNUNET_NO + }; + + if (0 > GNUNET_PQ_eval_prepared_multi_select (plugin->dbh, + stmt, params_select, + &get_state_cb, &gsc)) + return GNUNET_SYSERR; + return gsc.ret; /* GNUNET_OK ?? */ +} + + +/** + * Entry point for the plugin. + * + * @param cls The struct GNUNET_CONFIGURATION_Handle. + * @return NULL on error, otherwise the plugin context + */ +void * +libgnunet_plugin_psycstore_postgres_init (void *cls) +{ + static struct Plugin plugin; + const struct GNUNET_CONFIGURATION_Handle *cfg = cls; + struct GNUNET_PSYCSTORE_PluginFunctions *api; + + if (NULL != plugin.cfg) + return NULL; /* can only initialize once! */ + memset (&plugin, 0, sizeof (struct Plugin)); + plugin.cfg = cfg; + if (GNUNET_OK != database_setup (&plugin)) + { + database_shutdown (&plugin); + return NULL; + } + api = GNUNET_new (struct GNUNET_PSYCSTORE_PluginFunctions); + api->cls = &plugin; + api->membership_store = &postgres_membership_store; + api->membership_test = &membership_test; + api->fragment_store = &fragment_store; + api->message_add_flags = &message_add_flags; + api->fragment_get = &fragment_get; + api->fragment_get_latest = &fragment_get_latest; + api->message_get = &message_get; + api->message_get_latest = &message_get_latest; + api->message_get_fragment = &message_get_fragment; + api->counters_message_get = &counters_message_get; + api->counters_state_get = &counters_state_get; + api->state_modify_begin = &state_modify_begin; + api->state_modify_op = &state_modify_op; + api->state_modify_end = &state_modify_end; + api->state_sync_begin = &state_sync_begin; + api->state_sync_assign = &state_sync_assign; + api->state_sync_end = &state_sync_end; + api->state_reset = &state_reset; + api->state_update_signed = &state_update_signed; + api->state_get = &state_get; + api->state_get_prefix = &state_get_prefix; + api->state_get_signed = &state_get_signed; + + LOG (GNUNET_ERROR_TYPE_INFO, _("Postgres database running\n")); + return api; +} + + +/** + * Exit point from the plugin. + * + * @param cls The plugin context (as returned by "init") + * @return Always NULL + */ +void * +libgnunet_plugin_psycstore_postgres_done (void *cls) +{ + struct GNUNET_PSYCSTORE_PluginFunctions *api = cls; + struct Plugin *plugin = api->cls; + + database_shutdown (plugin); + plugin->cfg = NULL; + GNUNET_free (api); + LOG (GNUNET_ERROR_TYPE_DEBUG, "Postgres plugin has finished\n"); + return NULL; +} + +/* end of plugin_psycstore_postgres.c */ diff --git a/src/psycstore/plugin_psycstore_sqlite.c b/src/psycstore/plugin_psycstore_sqlite.c new file mode 100644 index 0000000..24de383 --- /dev/null +++ b/src/psycstore/plugin_psycstore_sqlite.c @@ -0,0 +1,1948 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file psycstore/plugin_psycstore_sqlite.c + * @brief sqlite-based psycstore backend + * @author Gabor X Toth + * @author Christian Grothoff + */ + +/* + * FIXME: SQLite3 only supports signed 64-bit integers natively, + * thus it can only store 63 bits of the uint64_t's. + */ + +#include "platform.h" +#include "gnunet_psycstore_plugin.h" +#include "gnunet_psycstore_service.h" +#include "gnunet_multicast_service.h" +#include "gnunet_crypto_lib.h" +#include "gnunet_psyc_util_lib.h" +#include "psycstore.h" +#include + +/** + * After how many ms "busy" should a DB operation fail for good? A + * low value makes sure that we are more responsive to requests + * (especially PUTs). A high value guarantees a higher success rate + * (SELECTs in iterate can take several seconds despite LIMIT=1). + * + * The default value of 1s should ensure that users do not experience + * huge latencies while at the same time allowing operations to + * succeed with reasonable probability. + */ +#define BUSY_TIMEOUT_MS 1000 + +#define DEBUG_PSYCSTORE GNUNET_EXTRA_LOGGING + +/** + * Log an error message at log-level 'level' that indicates + * a failure of the command 'cmd' on file 'filename' + * with the message given by strerror(errno). + */ +#define LOG_SQLITE(db, level, cmd) do { GNUNET_log_from (level, "psycstore-sqlite", _("`%s' failed at %s:%d with error: %s (%d)\n"), cmd, __FILE__, __LINE__, sqlite3_errmsg(db->dbh), sqlite3_errcode(db->dbh)); } while(0) + +#define LOG(kind,...) GNUNET_log_from (kind, "psycstore-sqlite", __VA_ARGS__) + +enum Transactions { + TRANSACTION_NONE = 0, + TRANSACTION_STATE_MODIFY, + TRANSACTION_STATE_SYNC, +}; + +/** + * Context for all functions in this plugin. + */ +struct Plugin +{ + + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Database filename. + */ + char *fn; + + /** + * Native SQLite database handle. + */ + sqlite3 *dbh; + + /** + * Current transaction. + */ + enum Transactions transaction; + + sqlite3_stmt *transaction_begin; + + sqlite3_stmt *transaction_commit; + + sqlite3_stmt *transaction_rollback; + + /** + * Precompiled SQL for channel_key_store() + */ + sqlite3_stmt *insert_channel_key; + + /** + * Precompiled SQL for slave_key_store() + */ + sqlite3_stmt *insert_slave_key; + + + /** + * Precompiled SQL for membership_store() + */ + sqlite3_stmt *insert_membership; + + /** + * Precompiled SQL for membership_test() + */ + sqlite3_stmt *select_membership; + + + /** + * Precompiled SQL for fragment_store() + */ + sqlite3_stmt *insert_fragment; + + /** + * Precompiled SQL for message_add_flags() + */ + sqlite3_stmt *update_message_flags; + + /** + * Precompiled SQL for fragment_get() + */ + sqlite3_stmt *select_fragments; + + /** + * Precompiled SQL for fragment_get() + */ + sqlite3_stmt *select_latest_fragments; + + /** + * Precompiled SQL for message_get() + */ + sqlite3_stmt *select_messages; + + /** + * Precompiled SQL for message_get() + */ + sqlite3_stmt *select_latest_messages; + + /** + * Precompiled SQL for message_get_fragment() + */ + sqlite3_stmt *select_message_fragment; + + /** + * Precompiled SQL for counters_get_message() + */ + sqlite3_stmt *select_counters_message; + + /** + * Precompiled SQL for counters_get_state() + */ + sqlite3_stmt *select_counters_state; + + /** + * Precompiled SQL for state_modify_end() + */ + sqlite3_stmt *update_state_hash_message_id; + + /** + * Precompiled SQL for state_sync_end() + */ + sqlite3_stmt *update_max_state_message_id; + + /** + * Precompiled SQL for state_modify_op() + */ + sqlite3_stmt *insert_state_current; + + /** + * Precompiled SQL for state_modify_end() + */ + sqlite3_stmt *delete_state_empty; + + /** + * Precompiled SQL for state_set_signed() + */ + sqlite3_stmt *update_state_signed; + + /** + * Precompiled SQL for state_sync() + */ + sqlite3_stmt *insert_state_sync; + + /** + * Precompiled SQL for state_sync() + */ + sqlite3_stmt *delete_state; + + /** + * Precompiled SQL for state_sync() + */ + sqlite3_stmt *insert_state_from_sync; + + /** + * Precompiled SQL for state_sync() + */ + sqlite3_stmt *delete_state_sync; + + /** + * Precompiled SQL for state_get_signed() + */ + sqlite3_stmt *select_state_signed; + + /** + * Precompiled SQL for state_get() + */ + sqlite3_stmt *select_state_one; + + /** + * Precompiled SQL for state_get_prefix() + */ + sqlite3_stmt *select_state_prefix; + +}; + +#if DEBUG_PSYCSTORE + +static void +sql_trace (void *cls, const char *sql) +{ + LOG (GNUNET_ERROR_TYPE_DEBUG, "SQL query:\n%s\n", sql); +} + +#endif + +/** + * @brief Prepare a SQL statement + * + * @param dbh handle to the database + * @param sql SQL statement, UTF-8 encoded + * @param stmt set to the prepared statement + * @return 0 on success + */ +static int +sql_prepare (sqlite3 *dbh, const char *sql, sqlite3_stmt **stmt) +{ + char *tail; + int result; + + result = sqlite3_prepare_v2 (dbh, sql, strlen (sql), stmt, + (const char **) &tail); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Prepared `%s' / %p: %d\n", sql, *stmt, result); + if (result != SQLITE_OK) + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Error preparing SQL query: %s\n %s\n"), + sqlite3_errmsg (dbh), sql); + return result; +} + + +/** + * @brief Prepare a SQL statement + * + * @param dbh handle to the database + * @param sql SQL statement, UTF-8 encoded + * @return 0 on success + */ +static int +sql_exec (sqlite3 *dbh, const char *sql) +{ + int result; + + result = sqlite3_exec (dbh, sql, NULL, NULL, NULL); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Executed `%s' / %d\n", sql, result); + if (result != SQLITE_OK) + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Error executing SQL query: %s\n %s\n"), + sqlite3_errmsg (dbh), sql); + return result; +} + + +/** + * Initialize the database connections and associated + * data structures (create tables and indices + * as needed as well). + * + * @param plugin the plugin context (state for this module) + * @return GNUNET_OK on success + */ +static int +database_setup (struct Plugin *plugin) +{ + char *filename; + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (plugin->cfg, "psycstore-sqlite", + "FILENAME", &filename)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "psycstore-sqlite", "FILENAME"); + return GNUNET_SYSERR; + } + if (GNUNET_OK != GNUNET_DISK_file_test (filename)) + { + if (GNUNET_OK != GNUNET_DISK_directory_create_for_file (filename)) + { + GNUNET_break (0); + GNUNET_free (filename); + return GNUNET_SYSERR; + } + } + /* filename should be UTF-8-encoded. If it isn't, it's a bug */ + plugin->fn = filename; + + /* Open database and precompile statements */ + if (SQLITE_OK != sqlite3_open (plugin->fn, &plugin->dbh)) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + _("Unable to initialize SQLite: %s.\n"), + sqlite3_errmsg (plugin->dbh)); + return GNUNET_SYSERR; + } + +#if DEBUG_PSYCSTORE + sqlite3_trace (plugin->dbh, &sql_trace, NULL); +#endif + + sql_exec (plugin->dbh, "PRAGMA temp_store=MEMORY"); + sql_exec (plugin->dbh, "PRAGMA synchronous=NORMAL"); + sql_exec (plugin->dbh, "PRAGMA legacy_file_format=OFF"); + sql_exec (plugin->dbh, "PRAGMA auto_vacuum=INCREMENTAL"); + sql_exec (plugin->dbh, "PRAGMA encoding=\"UTF-8\""); +#if ! DEBUG_PSYCSTORE + sql_exec (plugin->dbh, "PRAGMA locking_mode=EXCLUSIVE"); +#endif + sql_exec (plugin->dbh, "PRAGMA page_size=4096"); + + sqlite3_busy_timeout (plugin->dbh, BUSY_TIMEOUT_MS); + + /* Create tables */ + + sql_exec (plugin->dbh, + "CREATE TABLE IF NOT EXISTS channels (\n" + " id INTEGER PRIMARY KEY,\n" + " pub_key BLOB(32) UNIQUE,\n" + " max_state_message_id INTEGER,\n" // last applied state message ID + " state_hash_message_id INTEGER\n" // last message ID with a state hash + ");"); + + sql_exec (plugin->dbh, + "CREATE TABLE IF NOT EXISTS slaves (\n" + " id INTEGER PRIMARY KEY,\n" + " pub_key BLOB(32) UNIQUE\n" + ");"); + + sql_exec (plugin->dbh, + "CREATE TABLE IF NOT EXISTS membership (\n" + " channel_id INTEGER NOT NULL REFERENCES channels(id),\n" + " slave_id INTEGER NOT NULL REFERENCES slaves(id),\n" + " did_join INTEGER NOT NULL,\n" + " announced_at INTEGER NOT NULL,\n" + " effective_since INTEGER NOT NULL,\n" + " group_generation INTEGER NOT NULL\n" + ");"); + sql_exec (plugin->dbh, + "CREATE INDEX IF NOT EXISTS idx_membership_channel_id_slave_id " + "ON membership (channel_id, slave_id);"); + + /** @todo messages table: add method_name column */ + sql_exec (plugin->dbh, + "CREATE TABLE IF NOT EXISTS messages (\n" + " channel_id INTEGER NOT NULL REFERENCES channels(id),\n" + " hop_counter INTEGER NOT NULL,\n" + " signature BLOB,\n" + " purpose BLOB,\n" + " fragment_id INTEGER NOT NULL,\n" + " fragment_offset INTEGER NOT NULL,\n" + " message_id INTEGER NOT NULL,\n" + " group_generation INTEGER NOT NULL,\n" + " multicast_flags INTEGER NOT NULL,\n" + " psycstore_flags INTEGER NOT NULL,\n" + " data BLOB,\n" + " PRIMARY KEY (channel_id, fragment_id),\n" + " UNIQUE (channel_id, message_id, fragment_offset)\n" + ");"); + + sql_exec (plugin->dbh, + "CREATE TABLE IF NOT EXISTS state (\n" + " channel_id INTEGER NOT NULL REFERENCES channels(id),\n" + " name TEXT NOT NULL,\n" + " value_current BLOB,\n" + " value_signed BLOB,\n" + " PRIMARY KEY (channel_id, name)\n" + ");"); + + sql_exec (plugin->dbh, + "CREATE TABLE IF NOT EXISTS state_sync (\n" + " channel_id INTEGER NOT NULL REFERENCES channels(id),\n" + " name TEXT NOT NULL,\n" + " value BLOB,\n" + " PRIMARY KEY (channel_id, name)\n" + ");"); + + /* Prepare statements */ + + sql_prepare (plugin->dbh, "BEGIN;", &plugin->transaction_begin); + + sql_prepare (plugin->dbh, "COMMIT;", &plugin->transaction_commit); + + sql_prepare (plugin->dbh, "ROLLBACK;", &plugin->transaction_rollback); + + sql_prepare (plugin->dbh, + "INSERT OR IGNORE INTO channels (pub_key) VALUES (?);", + &plugin->insert_channel_key); + + sql_prepare (plugin->dbh, + "INSERT OR IGNORE INTO slaves (pub_key) VALUES (?);", + &plugin->insert_slave_key); + + sql_prepare (plugin->dbh, + "INSERT INTO membership\n" + " (channel_id, slave_id, did_join, announced_at,\n" + " effective_since, group_generation)\n" + "VALUES ((SELECT id FROM channels WHERE pub_key = ?),\n" + " (SELECT id FROM slaves WHERE pub_key = ?),\n" + " ?, ?, ?, ?);", + &plugin->insert_membership); + + sql_prepare (plugin->dbh, + "SELECT did_join FROM membership\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND slave_id = (SELECT id FROM slaves WHERE pub_key = ?)\n" + " AND effective_since <= ? AND did_join = 1\n" + "ORDER BY announced_at DESC LIMIT 1;", + &plugin->select_membership); + + sql_prepare (plugin->dbh, + "INSERT OR IGNORE INTO messages\n" + " (channel_id, hop_counter, signature, purpose,\n" + " fragment_id, fragment_offset, message_id,\n" + " group_generation, multicast_flags, psycstore_flags, data)\n" + "VALUES ((SELECT id FROM channels WHERE pub_key = ?),\n" + " ?, ?, ?, ?, ?, ?, ?, ?, ?, ?);", + &plugin->insert_fragment); + + sql_prepare (plugin->dbh, + "UPDATE messages\n" + "SET psycstore_flags = psycstore_flags | ?\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND message_id = ? AND fragment_offset = 0;", + &plugin->update_message_flags); + + sql_prepare (plugin->dbh, + "SELECT hop_counter, signature, purpose, fragment_id,\n" + " fragment_offset, message_id, group_generation,\n" + " multicast_flags, psycstore_flags, data\n" + "FROM messages\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND ? <= fragment_id AND fragment_id <= ?;", + &plugin->select_fragments); + + /** @todo select_messages: add method_prefix filter */ + sql_prepare (plugin->dbh, + "SELECT hop_counter, signature, purpose, fragment_id,\n" + " fragment_offset, message_id, group_generation,\n" + " multicast_flags, psycstore_flags, data\n" + "FROM messages\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND ? <= message_id AND message_id <= ?" + "LIMIT ?;", + &plugin->select_messages); + + sql_prepare (plugin->dbh, + "SELECT * FROM\n" + "(SELECT hop_counter, signature, purpose, fragment_id,\n" + " fragment_offset, message_id, group_generation,\n" + " multicast_flags, psycstore_flags, data\n" + " FROM messages\n" + " WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " ORDER BY fragment_id DESC\n" + " LIMIT ?)\n" + "ORDER BY fragment_id;", + &plugin->select_latest_fragments); + + /** @todo select_latest_messages: add method_prefix filter */ + sql_prepare (plugin->dbh, + "SELECT hop_counter, signature, purpose, fragment_id,\n" + " fragment_offset, message_id, group_generation,\n" + " multicast_flags, psycstore_flags, data\n" + "FROM messages\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND message_id IN\n" + " (SELECT message_id\n" + " FROM messages\n" + " WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " GROUP BY message_id\n" + " ORDER BY message_id\n" + " DESC LIMIT ?)\n" + "ORDER BY fragment_id;", + &plugin->select_latest_messages); + + sql_prepare (plugin->dbh, + "SELECT hop_counter, signature, purpose, fragment_id,\n" + " fragment_offset, message_id, group_generation,\n" + " multicast_flags, psycstore_flags, data\n" + "FROM messages\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND message_id = ? AND fragment_offset = ?;", + &plugin->select_message_fragment); + + sql_prepare (plugin->dbh, + "SELECT fragment_id, message_id, group_generation\n" + "FROM messages\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + "ORDER BY fragment_id DESC LIMIT 1;", + &plugin->select_counters_message); + + sql_prepare (plugin->dbh, + "SELECT max_state_message_id\n" + "FROM channels\n" + "WHERE pub_key = ? AND max_state_message_id IS NOT NULL;", + &plugin->select_counters_state); + + sql_prepare (plugin->dbh, + "UPDATE channels\n" + "SET max_state_message_id = ?\n" + "WHERE pub_key = ?;", + &plugin->update_max_state_message_id); + + sql_prepare (plugin->dbh, + "UPDATE channels\n" + "SET state_hash_message_id = ?\n" + "WHERE pub_key = ?;", + &plugin->update_state_hash_message_id); + + sql_prepare (plugin->dbh, + "INSERT OR REPLACE INTO state\n" + " (channel_id, name, value_current, value_signed)\n" + "SELECT new.channel_id, new.name,\n" + " new.value_current, old.value_signed\n" + "FROM (SELECT (SELECT id FROM channels WHERE pub_key = ?)\n" + " AS channel_id,\n" + " ? AS name, ? AS value_current) AS new\n" + "LEFT JOIN (SELECT channel_id, name, value_signed\n" + " FROM state) AS old\n" + "ON new.channel_id = old.channel_id AND new.name = old.name;", + &plugin->insert_state_current); + + sql_prepare (plugin->dbh, + "DELETE FROM state\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND (value_current IS NULL OR length(value_current) = 0)\n" + " AND (value_signed IS NULL OR length(value_signed) = 0);", + &plugin->delete_state_empty); + + sql_prepare (plugin->dbh, + "UPDATE state\n" + "SET value_signed = value_current\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?);", + &plugin->update_state_signed); + + sql_prepare (plugin->dbh, + "DELETE FROM state\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?);", + &plugin->delete_state); + + sql_prepare (plugin->dbh, + "INSERT INTO state_sync (channel_id, name, value)\n" + "VALUES ((SELECT id FROM channels WHERE pub_key = ?), ?, ?);", + &plugin->insert_state_sync); + + sql_prepare (plugin->dbh, + "INSERT INTO state\n" + " (channel_id, name, value_current, value_signed)\n" + "SELECT channel_id, name, value, value\n" + "FROM state_sync\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?);", + &plugin->insert_state_from_sync); + + sql_prepare (plugin->dbh, + "DELETE FROM state_sync\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?);", + &plugin->delete_state_sync); + + sql_prepare (plugin->dbh, + "SELECT value_current\n" + "FROM state\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND name = ?;", + &plugin->select_state_one); + + sql_prepare (plugin->dbh, + "SELECT name, value_current\n" + "FROM state\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)\n" + " AND (name = ? OR substr(name, 1, ?) = ?);", + &plugin->select_state_prefix); + + sql_prepare (plugin->dbh, + "SELECT name, value_signed\n" + "FROM state\n" + "WHERE channel_id = (SELECT id FROM channels WHERE pub_key = ?)" + " AND value_signed IS NOT NULL;", + &plugin->select_state_signed); + + return GNUNET_OK; +} + + +/** + * Shutdown database connection and associate data + * structures. + * @param plugin the plugin context (state for this module) + */ +static void +database_shutdown (struct Plugin *plugin) +{ + int result; + sqlite3_stmt *stmt; + while (NULL != (stmt = sqlite3_next_stmt (plugin->dbh, NULL))) + { + result = sqlite3_finalize (stmt); + if (SQLITE_OK != result) + LOG (GNUNET_ERROR_TYPE_WARNING, + "Failed to close statement %p: %d\n", stmt, result); + } + if (SQLITE_OK != sqlite3_close (plugin->dbh)) + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR, "sqlite3_close"); + + GNUNET_free_non_null (plugin->fn); +} + +/** + * Execute a prepared statement with a @a channel_key argument. + * + * @param plugin Plugin handle. + * @param stmt Statement to execute. + * @param channel_key Public key of the channel. + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +exec_channel (struct Plugin *plugin, sqlite3_stmt *stmt, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key) +{ + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, channel_key, + sizeof (*channel_key), SQLITE_STATIC)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else if (SQLITE_DONE != sqlite3_step (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + +/** + * Begin a transaction. + */ +static int +transaction_begin (struct Plugin *plugin, enum Transactions transaction) +{ + sqlite3_stmt *stmt = plugin->transaction_begin; + + if (SQLITE_DONE != sqlite3_step (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + return GNUNET_SYSERR; + } + + plugin->transaction = transaction; + return GNUNET_OK; +} + + +/** + * Commit current transaction. + */ +static int +transaction_commit (struct Plugin *plugin) +{ + sqlite3_stmt *stmt = plugin->transaction_commit; + + if (SQLITE_DONE != sqlite3_step (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + return GNUNET_SYSERR; + } + + plugin->transaction = TRANSACTION_NONE; + return GNUNET_OK; +} + + +/** + * Roll back current transaction. + */ +static int +transaction_rollback (struct Plugin *plugin) +{ + sqlite3_stmt *stmt = plugin->transaction_rollback; + + if (SQLITE_DONE != sqlite3_step (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + return GNUNET_SYSERR; + } + plugin->transaction = TRANSACTION_NONE; + return GNUNET_OK; +} + + +static int +channel_key_store (struct Plugin *plugin, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key) +{ + sqlite3_stmt *stmt = plugin->insert_channel_key; + + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, channel_key, + sizeof (*channel_key), SQLITE_STATIC)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else if (SQLITE_DONE != sqlite3_step (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +static int +slave_key_store (struct Plugin *plugin, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key) +{ + sqlite3_stmt *stmt = plugin->insert_slave_key; + + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, slave_key, + sizeof (*slave_key), SQLITE_STATIC)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else if (SQLITE_DONE != sqlite3_step (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +/** + * Store join/leave events for a PSYC channel in order to be able to answer + * membership test queries later. + * + * @see GNUNET_PSYCSTORE_membership_store() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +sqlite_membership_store (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + int did_join, + uint64_t announced_at, + uint64_t effective_since, + uint64_t group_generation) +{ + struct Plugin *plugin = cls; + sqlite3_stmt *stmt = plugin->insert_membership; + + GNUNET_assert (TRANSACTION_NONE == plugin->transaction); + + if (announced_at > INT64_MAX || + effective_since > INT64_MAX || + group_generation > INT64_MAX) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != channel_key_store (plugin, channel_key) + || GNUNET_OK != slave_key_store (plugin, slave_key)) + return GNUNET_SYSERR; + + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, channel_key, + sizeof (*channel_key), SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_blob (stmt, 2, slave_key, + sizeof (*slave_key), SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_int (stmt, 3, did_join) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 4, announced_at) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 5, effective_since) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 6, group_generation)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else if (SQLITE_DONE != sqlite3_step (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + +/** + * Test if a member was admitted to the channel at the given message ID. + * + * @see GNUNET_PSYCSTORE_membership_test() + * + * @return #GNUNET_YES if the member was admitted, #GNUNET_NO if not, + * #GNUNET_SYSERR if there was en error. + */ +static int +membership_test (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + uint64_t message_id) +{ + struct Plugin *plugin = cls; + sqlite3_stmt *stmt = plugin->select_membership; + int ret = GNUNET_SYSERR; + + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, channel_key, + sizeof (*channel_key), SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_blob (stmt, 2, slave_key, + sizeof (*slave_key), SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 3, message_id)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else + { + switch (sqlite3_step (stmt)) + { + case SQLITE_DONE: + ret = GNUNET_NO; + break; + case SQLITE_ROW: + ret = GNUNET_YES; + } + } + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + } + + return ret; +} + +/** + * Store a message fragment sent to a channel. + * + * @see GNUNET_PSYCSTORE_fragment_store() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +fragment_store (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_MULTICAST_MessageHeader *msg, + uint32_t psycstore_flags) +{ + struct Plugin *plugin = cls; + sqlite3_stmt *stmt = plugin->insert_fragment; + + GNUNET_assert (TRANSACTION_NONE == plugin->transaction); + + uint64_t fragment_id = GNUNET_ntohll (msg->fragment_id); + uint64_t fragment_offset = GNUNET_ntohll (msg->fragment_offset); + uint64_t message_id = GNUNET_ntohll (msg->message_id); + uint64_t group_generation = GNUNET_ntohll (msg->group_generation); + + if (fragment_id > INT64_MAX || fragment_offset > INT64_MAX || + message_id > INT64_MAX || group_generation > INT64_MAX) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + "Tried to store fragment with a field > INT64_MAX: " + "%lu, %lu, %lu, %lu\n", fragment_id, fragment_offset, + message_id, group_generation); + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (GNUNET_OK != channel_key_store (plugin, channel_key)) + return GNUNET_SYSERR; + + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, channel_key, + sizeof (*channel_key), SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 2, ntohl (msg->hop_counter) ) + || SQLITE_OK != sqlite3_bind_blob (stmt, 3, (const void *) &msg->signature, + sizeof (msg->signature), SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_blob (stmt, 4, (const void *) &msg->purpose, + sizeof (msg->purpose), SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 5, fragment_id) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 6, fragment_offset) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 7, message_id) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 8, group_generation) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 9, ntohl (msg->flags)) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 10, psycstore_flags) + || SQLITE_OK != sqlite3_bind_blob (stmt, 11, (const void *) &msg[1], + ntohs (msg->header.size) + - sizeof (*msg), SQLITE_STATIC)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else if (SQLITE_DONE != sqlite3_step (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + +/** + * Set additional flags for a given message. + * + * They are OR'd with any existing flags set. + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +message_add_flags (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id, + uint32_t psycstore_flags) +{ + struct Plugin *plugin = cls; + sqlite3_stmt *stmt = plugin->update_message_flags; + int ret = GNUNET_SYSERR; + + if (SQLITE_OK != sqlite3_bind_int64 (stmt, 1, psycstore_flags) + || SQLITE_OK != sqlite3_bind_blob (stmt, 2, channel_key, + sizeof (*channel_key), SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 3, message_id)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else + { + switch (sqlite3_step (stmt)) + { + case SQLITE_DONE: + ret = sqlite3_total_changes (plugin->dbh) > 0 ? GNUNET_OK : GNUNET_NO; + break; + default: + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + } + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + return GNUNET_SYSERR; + } + + return ret; +} + +static int +fragment_row (sqlite3_stmt *stmt, GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + int data_size = sqlite3_column_bytes (stmt, 9); + struct GNUNET_MULTICAST_MessageHeader *msg + = GNUNET_malloc (sizeof (*msg) + data_size); + + msg->header.size = htons (sizeof (*msg) + data_size); + msg->header.type = htons (GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE); + msg->hop_counter = htonl ((uint32_t) sqlite3_column_int64 (stmt, 0)); + GNUNET_memcpy (&msg->signature, + sqlite3_column_blob (stmt, 1), + sqlite3_column_bytes (stmt, 1)); + GNUNET_memcpy (&msg->purpose, + sqlite3_column_blob (stmt, 2), + sqlite3_column_bytes (stmt, 2)); + msg->fragment_id = GNUNET_htonll (sqlite3_column_int64 (stmt, 3)); + msg->fragment_offset = GNUNET_htonll (sqlite3_column_int64 (stmt, 4)); + msg->message_id = GNUNET_htonll (sqlite3_column_int64 (stmt, 5)); + msg->group_generation = GNUNET_htonll (sqlite3_column_int64 (stmt, 6)); + msg->flags = htonl (sqlite3_column_int64 (stmt, 7)); + GNUNET_memcpy (&msg[1], sqlite3_column_blob (stmt, 9), data_size); + + return cb (cb_cls, (void *) msg, sqlite3_column_int64 (stmt, 8)); +} + + +static int +fragment_select (struct Plugin *plugin, sqlite3_stmt *stmt, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, void *cb_cls) +{ + int ret = GNUNET_SYSERR; + int sql_ret; + + do + { + sql_ret = sqlite3_step (stmt); + switch (sql_ret) + { + case SQLITE_DONE: + if (ret != GNUNET_OK) + ret = GNUNET_NO; + break; + case SQLITE_ROW: + ret = fragment_row (stmt, cb, cb_cls); + (*returned_fragments)++; + if (ret != GNUNET_YES) + sql_ret = SQLITE_DONE; + break; + default: + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + } + while (sql_ret == SQLITE_ROW); + + return ret; +} + +/** + * Retrieve a message fragment range by fragment ID. + * + * @see GNUNET_PSYCSTORE_fragment_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +fragment_get (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t first_fragment_id, + uint64_t last_fragment_id, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + sqlite3_stmt *stmt = plugin->select_fragments; + int ret = GNUNET_SYSERR; + *returned_fragments = 0; + + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, channel_key, + sizeof (*channel_key), + SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 2, first_fragment_id) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 3, last_fragment_id)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else + { + ret = fragment_select (plugin, stmt, returned_fragments, cb, cb_cls); + } + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + } + + return ret; +} + + +/** + * Retrieve a message fragment range by fragment ID. + * + * @see GNUNET_PSYCSTORE_fragment_get_latest() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +fragment_get_latest (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t fragment_limit, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + sqlite3_stmt *stmt = plugin->select_latest_fragments; + int ret = GNUNET_SYSERR; + *returned_fragments = 0; + + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, channel_key, + sizeof (*channel_key), + SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 2, fragment_limit)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else + { + ret = fragment_select (plugin, stmt, returned_fragments, cb, cb_cls); + } + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + } + + return ret; +} + + +/** + * Retrieve all fragments of a message ID range. + * + * @see GNUNET_PSYCSTORE_message_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +message_get (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t first_message_id, + uint64_t last_message_id, + uint64_t fragment_limit, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + sqlite3_stmt *stmt = plugin->select_messages; + int ret = GNUNET_SYSERR; + *returned_fragments = 0; + + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, channel_key, + sizeof (*channel_key), + SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 2, first_message_id) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 3, last_message_id) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 4, + (0 != fragment_limit) + ? fragment_limit + : INT64_MAX)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else + { + ret = fragment_select (plugin, stmt, returned_fragments, cb, cb_cls); + } + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + } + + return ret; +} + + +/** + * Retrieve all fragments of the latest messages. + * + * @see GNUNET_PSYCSTORE_message_get_latest() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +message_get_latest (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_limit, + uint64_t *returned_fragments, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + sqlite3_stmt *stmt = plugin->select_latest_messages; + int ret = GNUNET_SYSERR; + *returned_fragments = 0; + + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, channel_key, + sizeof (*channel_key), + SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_blob (stmt, 2, channel_key, + sizeof (*channel_key), + SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 3, message_limit)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else + { + ret = fragment_select (plugin, stmt, returned_fragments, cb, cb_cls); + } + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + } + + return ret; +} + + +/** + * Retrieve a fragment of message specified by its message ID and fragment + * offset. + * + * @see GNUNET_PSYCSTORE_message_get_fragment() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +message_get_fragment (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id, + uint64_t fragment_offset, + GNUNET_PSYCSTORE_FragmentCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + sqlite3_stmt *stmt = plugin->select_message_fragment; + int ret = GNUNET_SYSERR; + + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, channel_key, + sizeof (*channel_key), + SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 2, message_id) + || SQLITE_OK != sqlite3_bind_int64 (stmt, 3, fragment_offset)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else + { + switch (sqlite3_step (stmt)) + { + case SQLITE_DONE: + ret = GNUNET_NO; + break; + case SQLITE_ROW: + ret = fragment_row (stmt, cb, cb_cls); + break; + default: + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + } + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + } + + return ret; +} + +/** + * Retrieve the max. values of message counters for a channel. + * + * @see GNUNET_PSYCSTORE_counters_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +counters_message_get (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t *max_fragment_id, + uint64_t *max_message_id, + uint64_t *max_group_generation) +{ + struct Plugin *plugin = cls; + sqlite3_stmt *stmt = plugin->select_counters_message; + int ret = GNUNET_SYSERR; + + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, channel_key, + sizeof (*channel_key), + SQLITE_STATIC)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else + { + switch (sqlite3_step (stmt)) + { + case SQLITE_DONE: + ret = GNUNET_NO; + break; + case SQLITE_ROW: + *max_fragment_id = sqlite3_column_int64 (stmt, 0); + *max_message_id = sqlite3_column_int64 (stmt, 1); + *max_group_generation = sqlite3_column_int64 (stmt, 2); + ret = GNUNET_OK; + break; + default: + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + } + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + } + + return ret; +} + +/** + * Retrieve the max. values of state counters for a channel. + * + * @see GNUNET_PSYCSTORE_counters_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +counters_state_get (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t *max_state_message_id) +{ + struct Plugin *plugin = cls; + sqlite3_stmt *stmt = plugin->select_counters_state; + int ret = GNUNET_SYSERR; + + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, channel_key, + sizeof (*channel_key), + SQLITE_STATIC)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else + { + switch (sqlite3_step (stmt)) + { + case SQLITE_DONE: + ret = GNUNET_NO; + break; + case SQLITE_ROW: + *max_state_message_id = sqlite3_column_int64 (stmt, 0); + ret = GNUNET_OK; + break; + default: + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + } + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + } + + return ret; +} + + +/** + * Assign a value to a state variable. + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_assign (struct Plugin *plugin, sqlite3_stmt *stmt, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, const void *value, size_t value_size) +{ + int ret = GNUNET_SYSERR; + + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, channel_key, + sizeof (*channel_key), SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_text (stmt, 2, name, -1, SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_blob (stmt, 3, value, value_size, + SQLITE_STATIC)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else + { + switch (sqlite3_step (stmt)) + { + case SQLITE_DONE: + ret = 0 < sqlite3_total_changes (plugin->dbh) ? GNUNET_OK : GNUNET_NO; + break; + default: + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + } + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + return GNUNET_SYSERR; + } + + return ret; +} + + +static int +update_message_id (struct Plugin *plugin, sqlite3_stmt *stmt, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id) +{ + if (SQLITE_OK != sqlite3_bind_int64 (stmt, 1, message_id) + || SQLITE_OK != sqlite3_bind_blob (stmt, 2, channel_key, + sizeof (*channel_key), SQLITE_STATIC)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else if (SQLITE_DONE != sqlite3_step (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Begin modifying current state. + */ +static int +state_modify_begin (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id, uint64_t state_delta) +{ + struct Plugin *plugin = cls; + + if (state_delta > 0) + { + /** + * We can only apply state modifiers in the current message if modifiers in + * the previous stateful message (message_id - state_delta) were already + * applied. + */ + + uint64_t max_state_message_id = 0; + int ret = counters_state_get (plugin, channel_key, &max_state_message_id); + switch (ret) + { + case GNUNET_OK: + case GNUNET_NO: // no state yet + ret = GNUNET_OK; + break; + default: + return ret; + } + + if (max_state_message_id < message_id - state_delta) + return GNUNET_NO; /* some stateful messages not yet applied */ + else if (message_id - state_delta < max_state_message_id) + return GNUNET_NO; /* changes already applied */ + } + + if (TRANSACTION_NONE != plugin->transaction) + { + /** @todo FIXME: wait for other transaction to finish */ + return GNUNET_SYSERR; + } + return transaction_begin (plugin, TRANSACTION_STATE_MODIFY); +} + + +/** + * Set the current value of state variable. + * + * @see GNUNET_PSYCSTORE_state_modify() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_modify_op (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + enum GNUNET_PSYC_Operator op, + const char *name, const void *value, size_t value_size) +{ + struct Plugin *plugin = cls; + GNUNET_assert (TRANSACTION_STATE_MODIFY == plugin->transaction); + + switch (op) + { + case GNUNET_PSYC_OP_ASSIGN: + return state_assign (plugin, plugin->insert_state_current, channel_key, + name, value, value_size); + + default: /** @todo implement more state operations */ + GNUNET_break (0); + return GNUNET_SYSERR; + } +} + + +/** + * End modifying current state. + */ +static int +state_modify_end (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id) +{ + struct Plugin *plugin = cls; + GNUNET_assert (TRANSACTION_STATE_MODIFY == plugin->transaction); + + return + GNUNET_OK == exec_channel (plugin, plugin->delete_state_empty, channel_key) + && GNUNET_OK == update_message_id (plugin, + plugin->update_max_state_message_id, + channel_key, message_id) + && GNUNET_OK == transaction_commit (plugin) + ? GNUNET_OK : GNUNET_SYSERR; +} + + +/** + * Begin state synchronization. + */ +static int +state_sync_begin (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key) +{ + struct Plugin *plugin = cls; + return exec_channel (plugin, plugin->delete_state_sync, channel_key); +} + + +/** + * Assign current value of a state variable. + * + * @see GNUNET_PSYCSTORE_state_modify() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_sync_assign (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, const void *value, size_t value_size) +{ + struct Plugin *plugin = cls; + return state_assign (cls, plugin->insert_state_sync, channel_key, + name, value, value_size); +} + + +/** + * End modifying current state. + */ +static int +state_sync_end (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t max_state_message_id, + uint64_t state_hash_message_id) +{ + struct Plugin *plugin = cls; + int ret = GNUNET_SYSERR; + + if (TRANSACTION_NONE != plugin->transaction) + { + /** @todo FIXME: wait for other transaction to finish */ + return GNUNET_SYSERR; + } + + GNUNET_OK == transaction_begin (plugin, TRANSACTION_STATE_SYNC) + && GNUNET_OK == exec_channel (plugin, plugin->delete_state, channel_key) + && GNUNET_OK == exec_channel (plugin, plugin->insert_state_from_sync, + channel_key) + && GNUNET_OK == exec_channel (plugin, plugin->delete_state_sync, + channel_key) + && GNUNET_OK == update_message_id (plugin, + plugin->update_state_hash_message_id, + channel_key, state_hash_message_id) + && GNUNET_OK == update_message_id (plugin, + plugin->update_max_state_message_id, + channel_key, max_state_message_id) + && GNUNET_OK == transaction_commit (plugin) + ? ret = GNUNET_OK + : transaction_rollback (plugin); + return ret; +} + + +/** + * Delete the whole state. + * + * @see GNUNET_PSYCSTORE_state_reset() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_reset (void *cls, const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key) +{ + struct Plugin *plugin = cls; + return exec_channel (plugin, plugin->delete_state, channel_key); +} + + +/** + * Update signed values of state variables in the state store. + * + * @see GNUNET_PSYCSTORE_state_hash_update() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_update_signed (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key) +{ + struct Plugin *plugin = cls; + return exec_channel (plugin, plugin->update_state_signed, channel_key); +} + + +/** + * Retrieve a state variable by name. + * + * @see GNUNET_PSYCSTORE_state_get() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_get (void *cls, const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, GNUNET_PSYCSTORE_StateCallback cb, void *cb_cls) +{ + struct Plugin *plugin = cls; + int ret = GNUNET_SYSERR; + + sqlite3_stmt *stmt = plugin->select_state_one; + + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, channel_key, + sizeof (*channel_key), + SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_text (stmt, 2, name, -1, SQLITE_STATIC)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else + { + switch (sqlite3_step (stmt)) + { + case SQLITE_DONE: + ret = GNUNET_NO; + break; + case SQLITE_ROW: + ret = cb (cb_cls, name, sqlite3_column_blob (stmt, 0), + sqlite3_column_bytes (stmt, 0)); + break; + default: + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + } + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + } + + return ret; +} + + +/** + * Retrieve all state variables for a channel with the given prefix. + * + * @see GNUNET_PSYCSTORE_state_get_prefix() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_get_prefix (void *cls, const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, GNUNET_PSYCSTORE_StateCallback cb, + void *cb_cls) +{ + struct Plugin *plugin = cls; + int ret = GNUNET_SYSERR; + sqlite3_stmt *stmt = plugin->select_state_prefix; + size_t name_len = strlen (name); + + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, channel_key, + sizeof (*channel_key), SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_text (stmt, 2, name, name_len, SQLITE_STATIC) + || SQLITE_OK != sqlite3_bind_int (stmt, 3, name_len) + || SQLITE_OK != sqlite3_bind_text (stmt, 4, name, name_len, SQLITE_STATIC)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else + { + int sql_ret; + do + { + sql_ret = sqlite3_step (stmt); + switch (sql_ret) + { + case SQLITE_DONE: + if (ret != GNUNET_OK) + ret = GNUNET_NO; + break; + case SQLITE_ROW: + ret = cb (cb_cls, (const char *) sqlite3_column_text (stmt, 0), + sqlite3_column_blob (stmt, 1), + sqlite3_column_bytes (stmt, 1)); + if (ret != GNUNET_YES) + sql_ret = SQLITE_DONE; + break; + default: + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + } + while (sql_ret == SQLITE_ROW); + } + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + } + return ret; +} + + +/** + * Retrieve all signed state variables for a channel. + * + * @see GNUNET_PSYCSTORE_state_get_signed() + * + * @return #GNUNET_OK on success, else #GNUNET_SYSERR + */ +static int +state_get_signed (void *cls, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + GNUNET_PSYCSTORE_StateCallback cb, void *cb_cls) +{ + struct Plugin *plugin = cls; + int ret = GNUNET_SYSERR; + + sqlite3_stmt *stmt = plugin->select_state_signed; + + if (SQLITE_OK != sqlite3_bind_blob (stmt, 1, channel_key, + sizeof (*channel_key), SQLITE_STATIC)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_bind"); + } + else + { + int sql_ret; + do + { + sql_ret = sqlite3_step (stmt); + switch (sql_ret) + { + case SQLITE_DONE: + if (ret != GNUNET_OK) + ret = GNUNET_NO; + break; + case SQLITE_ROW: + ret = cb (cb_cls, (const char *) sqlite3_column_text (stmt, 0), + sqlite3_column_blob (stmt, 1), + sqlite3_column_bytes (stmt, 1)); + if (ret != GNUNET_YES) + sql_ret = SQLITE_DONE; + break; + default: + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_step"); + } + } + while (sql_ret == SQLITE_ROW); + } + + if (SQLITE_OK != sqlite3_reset (stmt)) + { + LOG_SQLITE (plugin, GNUNET_ERROR_TYPE_ERROR | GNUNET_ERROR_TYPE_BULK, + "sqlite3_reset"); + } + + return ret; +} + + +/** + * Entry point for the plugin. + * + * @param cls The struct GNUNET_CONFIGURATION_Handle. + * @return NULL on error, otherwise the plugin context + */ +void * +libgnunet_plugin_psycstore_sqlite_init (void *cls) +{ + static struct Plugin plugin; + const struct GNUNET_CONFIGURATION_Handle *cfg = cls; + struct GNUNET_PSYCSTORE_PluginFunctions *api; + + if (NULL != plugin.cfg) + return NULL; /* can only initialize once! */ + memset (&plugin, 0, sizeof (struct Plugin)); + plugin.cfg = cfg; + if (GNUNET_OK != database_setup (&plugin)) + { + database_shutdown (&plugin); + return NULL; + } + api = GNUNET_new (struct GNUNET_PSYCSTORE_PluginFunctions); + api->cls = &plugin; + api->membership_store = &sqlite_membership_store; + api->membership_test = &membership_test; + api->fragment_store = &fragment_store; + api->message_add_flags = &message_add_flags; + api->fragment_get = &fragment_get; + api->fragment_get_latest = &fragment_get_latest; + api->message_get = &message_get; + api->message_get_latest = &message_get_latest; + api->message_get_fragment = &message_get_fragment; + api->counters_message_get = &counters_message_get; + api->counters_state_get = &counters_state_get; + api->state_modify_begin = &state_modify_begin; + api->state_modify_op = &state_modify_op; + api->state_modify_end = &state_modify_end; + api->state_sync_begin = &state_sync_begin; + api->state_sync_assign = &state_sync_assign; + api->state_sync_end = &state_sync_end; + api->state_reset = &state_reset; + api->state_update_signed = &state_update_signed; + api->state_get = &state_get; + api->state_get_prefix = &state_get_prefix; + api->state_get_signed = &state_get_signed; + + LOG (GNUNET_ERROR_TYPE_INFO, _("SQLite database running\n")); + return api; +} + + +/** + * Exit point from the plugin. + * + * @param cls The plugin context (as returned by "init") + * @return Always NULL + */ +void * +libgnunet_plugin_psycstore_sqlite_done (void *cls) +{ + struct GNUNET_PSYCSTORE_PluginFunctions *api = cls; + struct Plugin *plugin = api->cls; + + database_shutdown (plugin); + plugin->cfg = NULL; + GNUNET_free (api); + LOG (GNUNET_ERROR_TYPE_DEBUG, "SQLite plugin is finished\n"); + return NULL; +} + +/* end of plugin_psycstore_sqlite.c */ diff --git a/src/psycstore/psycstore.conf.in b/src/psycstore/psycstore.conf.in new file mode 100644 index 0000000..3905db1 --- /dev/null +++ b/src/psycstore/psycstore.conf.in @@ -0,0 +1,28 @@ +[psycstore] +START_ON_DEMAND = @START_ON_DEMAND@ +BINARY = gnunet-service-psycstore + +UNIXPATH = $GNUNET_RUNTIME_DIR/gnunet-service-psycstore.sock +UNIX_MATCH_UID = YES +UNIX_MATCH_GID = YES + +@UNIXONLY@PORT = 2111 +HOSTNAME = localhost +ACCEPT_FROM = 127.0.0.1; +ACCEPT_FROM6 = ::1; + +DATABASE = sqlite + +[psycstore-sqlite] +FILENAME = $GNUNET_DATA_HOME/psycstore/sqlite.db + +[psycstore-mysql] +DATABASE = gnunet +CONFIG = ~/.my.cnf +# USER = gnunet +# PASSWORD = +# HOST = localhost +# PORT = 3306 + +[psycstore-postgres] +CONFIG = connect_timeout=10; dbname=gnunet diff --git a/src/psycstore/psycstore.h b/src/psycstore/psycstore.h new file mode 100644 index 0000000..9a1c06a --- /dev/null +++ b/src/psycstore/psycstore.h @@ -0,0 +1,520 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file psycstore/psycstore.h + * @brief Common type definitions for the PSYCstore service and API. + * @author Gabor X Toth + */ + +#ifndef GNUNET_PSYCSTORE_H +#define GNUNET_PSYCSTORE_H + +#include "gnunet_common.h" + + +GNUNET_NETWORK_STRUCT_BEGIN + +/** + * Answer from service to client about last operation. + */ +struct OperationResult +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYCSTORE_RESULT_CODE + */ + struct GNUNET_MessageHeader header; + + uint32_t reserved GNUNET_PACKED; + + /** + * Operation ID. + */ + uint64_t op_id GNUNET_PACKED; + + /**lowed by + * Status code for the operation. + */ + uint64_t result_code GNUNET_PACKED; + + /* followed by 0-terminated error message (on error) */ + +}; + + +/** + * Answer from service to client about master counters. + * + * @see GNUNET_PSYCSTORE_counters_get() + */ +struct CountersResult +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYCSTORE_RESULT_COUNTERS + */ + struct GNUNET_MessageHeader header; + + /** + * Status code for the operation: + * #GNUNET_OK: success, counter values are returned. + * #GNUNET_NO: no message has been sent to the channel yet. + * #GNUNET_SYSERR: an error occurred. + */ + uint32_t result_code GNUNET_PACKED; + + /** + * Operation ID. + */ + uint64_t op_id GNUNET_PACKED; + + uint64_t max_fragment_id GNUNET_PACKED; + + uint64_t max_message_id GNUNET_PACKED; + + uint64_t max_group_generation GNUNET_PACKED; + + uint64_t max_state_message_id GNUNET_PACKED; +}; + + +/** + * Answer from service to client containing a message fragment. + */ +struct FragmentResult +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYCSTORE_RESULT_CODE + */ + struct GNUNET_MessageHeader header; + + uint32_t psycstore_flags GNUNET_PACKED; + + /** + * Operation ID. + */ + uint64_t op_id GNUNET_PACKED; + + /* Followed by GNUNET_MULTICAST_MessageHeader */ +}; + + +/** + * Answer from service to client containing a state variable. + */ +struct StateResult +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYCSTORE_RESULT_CODE + */ + struct GNUNET_MessageHeader header; + + uint16_t name_size GNUNET_PACKED; + + uint16_t reserved GNUNET_PACKED; + + /** + * Operation ID. + */ + uint64_t op_id GNUNET_PACKED; + + /* Followed by name and value */ +}; + + +/** + * Generic operation request. + */ +struct OperationRequest +{ + struct GNUNET_MessageHeader header; + + uint32_t reserved GNUNET_PACKED; + + /** + * Operation ID. + */ + uint64_t op_id GNUNET_PACKED; + + struct GNUNET_CRYPTO_EddsaPublicKey channel_key; +}; + + +/** + * @see GNUNET_PSYCSTORE_membership_store() + */ +struct MembershipStoreRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYCSTORE_MEMBERSHIP_STORE + */ + struct GNUNET_MessageHeader header; + + uint32_t reserved GNUNET_PACKED; + + /** + * Operation ID. + */ + uint64_t op_id GNUNET_PACKED; + + /** + * Channel's public key. + */ + struct GNUNET_CRYPTO_EddsaPublicKey channel_key; + + /** + * Slave's public key. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey slave_key; + + uint64_t announced_at GNUNET_PACKED; + uint64_t effective_since GNUNET_PACKED; + uint64_t group_generation GNUNET_PACKED; + uint8_t did_join; +}; + + +/** + * @see GNUNET_PSYCSTORE_membership_test() + */ +struct MembershipTestRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYCSTORE_MEMBERSHIP_TEST + */ + struct GNUNET_MessageHeader header; + + uint32_t reserved GNUNET_PACKED; + + /** + * Operation ID. + */ + uint64_t op_id GNUNET_PACKED; + + /** + * Channel's public key. + */ + struct GNUNET_CRYPTO_EddsaPublicKey channel_key; + + /** + * Slave's public key. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey slave_key; + + uint64_t message_id GNUNET_PACKED; + + uint64_t group_generation GNUNET_PACKED; +}; + + +/** + * @see GNUNET_PSYCSTORE_fragment_store() + */ +struct FragmentStoreRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYCSTORE_FRAGMENT_STORE + */ + struct GNUNET_MessageHeader header; + + /** + * enum GNUNET_PSYCSTORE_MessageFlags + */ + uint32_t psycstore_flags GNUNET_PACKED; + + /** + * Channel's public key. + */ + struct GNUNET_CRYPTO_EddsaPublicKey channel_key; + + /** + * Operation ID. + */ + uint64_t op_id; + + /* Followed by fragment */ +}; + + +/** + * @see GNUNET_PSYCSTORE_fragment_get() + */ +struct FragmentGetRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYCSTORE_FRAGMENT_GET + */ + struct GNUNET_MessageHeader header; + + uint32_t reserved GNUNET_PACKED; + + /** + * Operation ID. + */ + uint64_t op_id GNUNET_PACKED; + + /** + * Channel's public key. + */ + struct GNUNET_CRYPTO_EddsaPublicKey channel_key; + + /** + * Slave's public key. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey slave_key; + + /** + * First fragment ID to request. + */ + uint64_t first_fragment_id GNUNET_PACKED; + + /** + * Last fragment ID to request. + */ + uint64_t last_fragment_id GNUNET_PACKED; + + /** + * Maximum number of fragments to retrieve. + */ + uint64_t fragment_limit GNUNET_PACKED; + + /** + * Do membership test with @a slave_key before returning fragment? + * #GNUNET_YES or #GNUNET_NO + */ + uint8_t do_membership_test; +}; + + +/** + * @see GNUNET_PSYCSTORE_message_get() + */ +struct MessageGetRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYCSTORE_MESSAGE_GET + */ + struct GNUNET_MessageHeader header; + + uint32_t reserved GNUNET_PACKED; + + /** + * Operation ID. + */ + uint64_t op_id GNUNET_PACKED; + + /** + * Channel's public key. + */ + struct GNUNET_CRYPTO_EddsaPublicKey channel_key; + + /** + * Slave's public key. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey slave_key; + + /** + * First message ID to request. + */ + uint64_t first_message_id GNUNET_PACKED; + + /** + * Last message ID to request. + */ + uint64_t last_message_id GNUNET_PACKED; + + /** + * Maximum number of messages to retrieve. + */ + uint64_t message_limit GNUNET_PACKED; + + /** + * Maximum number of fragments to retrieve. + */ + uint64_t fragment_limit GNUNET_PACKED; + + /** + * Do membership test with @a slave_key before returning fragment? + * #GNUNET_YES or #GNUNET_NO + */ + uint8_t do_membership_test; + + /* Followed by method_prefix */ +}; + + +/** + * @see GNUNET_PSYCSTORE_message_get_fragment() + */ +struct MessageGetFragmentRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYCSTORE_MESSAGE_FRAGMENT_GET + */ + struct GNUNET_MessageHeader header; + + uint32_t reserved GNUNET_PACKED; + + /** + * Operation ID. + */ + uint64_t op_id GNUNET_PACKED; + + /** + * Channel's public key. + */ + struct GNUNET_CRYPTO_EddsaPublicKey channel_key; + + /** + * Slave's public key. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey slave_key; + + /** + * Requested message ID. + */ + uint64_t message_id GNUNET_PACKED; + + /** + * Requested fragment offset. + */ + uint64_t fragment_offset GNUNET_PACKED; + + /** + * Do membership test with @a slave_key before returning fragment? + * #GNUNET_YES or #GNUNET_NO + */ + uint8_t do_membership_test; +}; + + +/** + * @see GNUNET_PSYCSTORE_state_hash_update() + */ +struct StateHashUpdateRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYCSTORE_STATE_HASH_UPDATE + */ + struct GNUNET_MessageHeader header; + + uint32_t reserved GNUNET_PACKED; + + /** + * Operation ID. + */ + uint64_t op_id GNUNET_PACKED; + + /** + * Channel's public key. + */ + struct GNUNET_CRYPTO_EddsaPublicKey channel_key; + + struct GNUNET_HashCode hash; +}; + + +enum StateOpFlags +{ + STATE_OP_FIRST = 1 << 0, + STATE_OP_LAST = 1 << 1 +}; + + +/** + * @see GNUNET_PSYCSTORE_state_modify() + */ +struct StateModifyRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYCSTORE_STATE_MODIFY + */ + struct GNUNET_MessageHeader header; + + /** + * Operation ID. + */ + uint64_t op_id GNUNET_PACKED; + + /** + * ID of the message to apply the state changes in. + */ + uint64_t message_id GNUNET_PACKED; + + /** + * State delta of the message with ID @a message_id. + */ + uint64_t state_delta GNUNET_PACKED; + + /** + * Channel's public key. + */ + struct GNUNET_CRYPTO_EddsaPublicKey channel_key; +}; + + +/** + * @see GNUNET_PSYCSTORE_state_sync() + */ +struct StateSyncRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_PSYCSTORE_STATE_SYNC + */ + struct GNUNET_MessageHeader header; + + /** + * Size of name, including NUL terminator. + */ + uint16_t name_size GNUNET_PACKED; + + /** + * OR'd StateOpFlags + */ + uint8_t flags; + + uint8_t reserved; + + /** + * Operation ID. + */ + uint64_t op_id GNUNET_PACKED; + + /** + * ID of the message that contains the state_hash PSYC header variable. + */ + uint64_t state_hash_message_id GNUNET_PACKED; + + /** + * ID of the last stateful message before @a state_hash_message_id. + */ + uint64_t max_state_message_id GNUNET_PACKED; + + /** + * Channel's public key. + */ + struct GNUNET_CRYPTO_EddsaPublicKey channel_key; + + /* Followed by NUL-terminated name, then the value. */ +}; + + +GNUNET_NETWORK_STRUCT_END + +#endif diff --git a/src/psycstore/psycstore_api.c b/src/psycstore/psycstore_api.c new file mode 100644 index 0000000..ab4cd0f --- /dev/null +++ b/src/psycstore/psycstore_api.c @@ -0,0 +1,1285 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file psycstore/psycstore_api.c + * @brief API to interact with the PSYCstore service + * @author Gabor X Toth + * @author Christian Grothoff + */ + +#include + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_constants.h" +#include "gnunet_protocols.h" +#include "gnunet_psycstore_service.h" +#include "gnunet_multicast_service.h" +#include "psycstore.h" + +#define LOG(kind,...) GNUNET_log_from (kind, "psycstore-api",__VA_ARGS__) + +/** + * Handle for an operation with the PSYCstore service. + */ +struct GNUNET_PSYCSTORE_OperationHandle +{ + + /** + * Main PSYCstore handle. + */ + struct GNUNET_PSYCSTORE_Handle *h; + + /** + * Data callbacks. + */ + union { + GNUNET_PSYCSTORE_FragmentCallback fragment_cb; + GNUNET_PSYCSTORE_CountersCallback counters_cb; + GNUNET_PSYCSTORE_StateCallback state_cb; + }; + + /** + * Closure for callbacks. + */ + void *cls; + + /** + * Message envelope. + */ + struct GNUNET_MQ_Envelope *env; + + /** + * Operation ID. + */ + uint64_t op_id; +}; + + +/** + * Handle for the service. + */ +struct GNUNET_PSYCSTORE_Handle +{ + /** + * Configuration to use. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Client connection. + */ + struct GNUNET_MQ_Handle *mq; + + /** + * Async operations. + */ + struct GNUNET_OP_Handle *op; + + /** + * Task doing exponential back-off trying to reconnect. + */ + struct GNUNET_SCHEDULER_Task *reconnect_task; + + /** + * Delay for next connect retry. + */ + struct GNUNET_TIME_Relative reconnect_delay; + + + GNUNET_PSYCSTORE_FragmentCallback *fragment_cb; + + GNUNET_PSYCSTORE_CountersCallback *counters_cb; + + GNUNET_PSYCSTORE_StateCallback *state_cb; + /** + * Closure for callbacks. + */ + void *cb_cls; +}; + + +static int +check_result_code (void *cls, const struct OperationResult *opres) +{ + uint16_t size = ntohs (opres->header.size); + const char *str = (const char *) &opres[1]; + if ( (sizeof (*opres) < size) && + ('\0' != str[size - sizeof (*opres) - 1]) ) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + + +static void +handle_result_code (void *cls, const struct OperationResult *opres) +{ + struct GNUNET_PSYCSTORE_Handle *h = cls; + struct GNUNET_PSYCSTORE_OperationHandle *op = NULL; + uint16_t size = ntohs (opres->header.size); + + const char * + str = (sizeof (*opres) < size) ? (const char *) &opres[1] : ""; + + if (GNUNET_YES == GNUNET_OP_result (h->op, GNUNET_ntohll (opres->op_id), + GNUNET_ntohll (opres->result_code) + INT64_MIN, + str, size - sizeof (*opres), (void **) &op)) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "handle_result_code: Received result message with OP ID: %" PRIu64 "\n", + GNUNET_ntohll (opres->op_id)); + GNUNET_free (op); + } + else + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "handle_result_code: No callback registered for OP ID %" PRIu64 ".\n", + GNUNET_ntohll (opres->op_id)); + } + h->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS; +} + + +static void +handle_result_counters (void *cls, const struct CountersResult *cres) +{ + struct GNUNET_PSYCSTORE_Handle *h = cls; + struct GNUNET_PSYCSTORE_OperationHandle *op = NULL; + + if (GNUNET_YES == GNUNET_OP_get (h->op, GNUNET_ntohll (cres->op_id), + NULL, NULL, (void **) &op)) + { + GNUNET_assert (NULL != op); + if (NULL != op->counters_cb) + { + op->counters_cb (op->cls, + ntohl (cres->result_code), + GNUNET_ntohll (cres->max_fragment_id), + GNUNET_ntohll (cres->max_message_id), + GNUNET_ntohll (cres->max_group_generation), + GNUNET_ntohll (cres->max_state_message_id)); + } + GNUNET_OP_remove (h->op, GNUNET_ntohll (cres->op_id)); + GNUNET_free (op); + } + else + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "handle_result_counters: No callback registered for OP ID %" PRIu64 ".\n", + GNUNET_ntohll (cres->op_id)); + } + h->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS; +} + + +static int +check_result_fragment (void *cls, const struct FragmentResult *fres) +{ + uint16_t size = ntohs (fres->header.size); + struct GNUNET_MULTICAST_MessageHeader *mmsg = + (struct GNUNET_MULTICAST_MessageHeader *) &fres[1]; + if (sizeof (*fres) + sizeof (*mmsg) < size + && sizeof (*fres) + ntohs (mmsg->header.size) != size) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + "check_result_fragment: Received message with invalid length %lu bytes.\n", + size, sizeof (*fres)); + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +static void +handle_result_fragment (void *cls, const struct FragmentResult *fres) +{ + struct GNUNET_PSYCSTORE_Handle *h = cls; + struct GNUNET_PSYCSTORE_OperationHandle *op = NULL; + + if (GNUNET_YES == GNUNET_OP_get (h->op, GNUNET_ntohll (fres->op_id), + NULL, NULL, (void **) &op)) + { + GNUNET_assert (NULL != op); + if (NULL != op->fragment_cb) + op->fragment_cb (op->cls, + (struct GNUNET_MULTICAST_MessageHeader *) &fres[1], + ntohl (fres->psycstore_flags)); + //GNUNET_OP_remove (h->op, GNUNET_ntohll (fres->op_id)); + //GNUNET_free (op); + } + else + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "handle_result_fragment: No callback registered for OP ID %" PRIu64 ".\n", + GNUNET_ntohll (fres->op_id)); + } + h->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS; +} + + +static int +check_result_state (void *cls, const struct StateResult *sres) +{ + const char *name = (const char *) &sres[1]; + uint16_t size = ntohs (sres->header.size); + uint16_t name_size = ntohs (sres->name_size); + + if (name_size <= 2 + || size - sizeof (*sres) < name_size + || '\0' != name[name_size - 1]) + { + LOG (GNUNET_ERROR_TYPE_ERROR, + "check_result_state: Received state result message with invalid name.\n"); + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +static void +handle_result_state (void *cls, const struct StateResult *sres) +{ + struct GNUNET_PSYCSTORE_Handle *h = cls; + struct GNUNET_PSYCSTORE_OperationHandle *op = NULL; + + const char *name = (const char *) &sres[1]; + uint16_t name_size = ntohs (sres->name_size); + + if (GNUNET_YES == GNUNET_OP_get (h->op, GNUNET_ntohll (sres->op_id), + NULL, NULL, (void **) &op)) + { + GNUNET_assert (NULL != op); + if (NULL != op->state_cb) + op->state_cb (op->cls, name, (char *) &sres[1] + name_size, + ntohs (sres->header.size) - sizeof (*sres) - name_size); + //GNUNET_OP_remove (h->op, GNUNET_ntohll (sres->op_id)); + //GNUNET_free (op); + } + else + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "handle_result_state: No callback registered for OP ID %" PRIu64 ".\n", + GNUNET_ntohll (sres->op_id)); + } + h->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS; +} + + +static void +reconnect (void *cls); + + +/** + * Client disconnected from service. + * + * Reconnect after backoff period.= + */ +static void +disconnected (void *cls, enum GNUNET_MQ_Error error) +{ + struct GNUNET_PSYCSTORE_Handle *h = cls; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Origin client disconnected (%d), re-connecting\n", + (int) error); + if (NULL != h->mq) + { + GNUNET_MQ_destroy (h->mq); + GNUNET_OP_destroy (h->op); + h->mq = NULL; + h->op = NULL; + } + + h->reconnect_task = GNUNET_SCHEDULER_add_delayed (h->reconnect_delay, + &reconnect, h); + h->reconnect_delay = GNUNET_TIME_STD_BACKOFF (h->reconnect_delay); +} + + +static void +do_connect (struct GNUNET_PSYCSTORE_Handle *h) +{ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Connecting to PSYCstore service.\n"); + + struct GNUNET_MQ_MessageHandler handlers[] = { + GNUNET_MQ_hd_var_size (result_code, + GNUNET_MESSAGE_TYPE_PSYCSTORE_RESULT_CODE, + struct OperationResult, + h), + GNUNET_MQ_hd_fixed_size (result_counters, + GNUNET_MESSAGE_TYPE_PSYCSTORE_RESULT_COUNTERS, + struct CountersResult, + h), + GNUNET_MQ_hd_var_size (result_fragment, + GNUNET_MESSAGE_TYPE_PSYCSTORE_RESULT_FRAGMENT, + struct FragmentResult, + h), + GNUNET_MQ_hd_var_size (result_state, + GNUNET_MESSAGE_TYPE_PSYCSTORE_RESULT_STATE, + struct StateResult, + h), + GNUNET_MQ_handler_end () + }; + + h->op = GNUNET_OP_create (); + GNUNET_assert (NULL == h->mq); + h->mq = GNUNET_CLIENT_connect (h->cfg, "psycstore", + handlers, disconnected, h); + GNUNET_assert (NULL != h->mq); +} + + +/** + * Try again to connect to the PSYCstore service. + * + * @param cls Handle to the PSYCstore service. + */ +static void +reconnect (void *cls) +{ + struct GNUNET_PSYCSTORE_Handle *h = cls; + + h->reconnect_task = NULL; + do_connect (cls); +} + + +/** + * Connect to the PSYCstore service. + * + * @param cfg The configuration to use + * @return Handle to use + */ +struct GNUNET_PSYCSTORE_Handle * +GNUNET_PSYCSTORE_connect (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct GNUNET_PSYCSTORE_Handle *h + = GNUNET_new (struct GNUNET_PSYCSTORE_Handle); + h->cfg = cfg; + h->reconnect_delay = GNUNET_TIME_UNIT_MILLISECONDS; + do_connect (h); + return h; +} + + +/** + * Disconnect from PSYCstore service + * + * @param h Handle to destroy + */ +void +GNUNET_PSYCSTORE_disconnect (struct GNUNET_PSYCSTORE_Handle *h) +{ + GNUNET_assert (NULL != h); + if (h->reconnect_task != NULL) + { + GNUNET_SCHEDULER_cancel (h->reconnect_task); + h->reconnect_task = NULL; + } + if (NULL != h->mq) + { + // FIXME: free data structures for pending operations + GNUNET_MQ_destroy (h->mq); + h->mq = NULL; + } + GNUNET_free (h); +} + + +/** + * Message sent notification. + * + * Remove invalidated envelope pointer. + */ +static void +message_sent (void *cls) +{ + struct GNUNET_PSYCSTORE_OperationHandle *op = cls; + op->env = NULL; +} + + +/** + * Create a new operation. + */ +static struct GNUNET_PSYCSTORE_OperationHandle * +op_create (struct GNUNET_PSYCSTORE_Handle *h, + struct GNUNET_OP_Handle *hop, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls) +{ + struct GNUNET_PSYCSTORE_OperationHandle * + op = GNUNET_malloc (sizeof (*op)); + op->h = h; + op->op_id = GNUNET_OP_add (hop, + (GNUNET_ResultCallback) result_cb, + cls, op); + return op; +} + + +/** + * Send a message associated with an operation. + * + * @param h + * PSYCstore handle. + * @param op + * Operation handle. + * @param env + * Message envelope to send. + * @param[out] op_id + * Operation ID to write in network byte order. NULL if not needed. + * + * @return Operation handle. + * + */ +static struct GNUNET_PSYCSTORE_OperationHandle * +op_send (struct GNUNET_PSYCSTORE_Handle *h, + struct GNUNET_PSYCSTORE_OperationHandle *op, + struct GNUNET_MQ_Envelope *env, + uint64_t *op_id) +{ + op->env = env; + if (NULL != op_id) + *op_id = GNUNET_htonll (op->op_id); + + GNUNET_MQ_notify_sent (env, message_sent, op); + GNUNET_MQ_send (h->mq, env); + return op; +} + + +/** + * Cancel a PSYCstore operation. Note that the operation MAY still + * be executed; this merely cancels the continuation; if the request + * was already transmitted, the service may still choose to complete + * the operation. + * + * @param op Operation to cancel. + * + * @return #GNUNET_YES if message was not sent yet and got discarded, + * #GNUNET_NO if it was already sent, and only the callbacks got cancelled. + */ +int +GNUNET_PSYCSTORE_operation_cancel (struct GNUNET_PSYCSTORE_OperationHandle *op) +{ + struct GNUNET_PSYCSTORE_Handle *h = op->h; + int ret = GNUNET_NO; + + if (NULL != op->env) + { + GNUNET_MQ_send_cancel (op->env); + ret = GNUNET_YES; + } + + GNUNET_OP_remove (h->op, op->op_id); + GNUNET_free (op); + + return ret; +} + + +/** + * Store join/leave events for a PSYC channel in order to be able to answer + * membership test queries later. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel where the event happened. + * @param slave_key + * Public key of joining/leaving slave. + * @param did_join + * #GNUNET_YES on join, #GNUNET_NO on part. + * @param announced_at + * ID of the message that announced the membership change. + * @param effective_since + * Message ID this membership change is in effect since. + * For joins it is <= announced_at, for parts it is always 0. + * @param group_generation + * In case of a part, the last group generation the slave has access to. + * It has relevance when a larger message have fragments with different + * group generations. + * @param result_cb + * Callback to call with the result of the storage operation. + * @param cls + * Closure for the callback. + * + * @return Operation handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_membership_store (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + int did_join, + uint64_t announced_at, + uint64_t effective_since, + uint64_t group_generation, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls) +{ + GNUNET_assert (NULL != h); + GNUNET_assert (NULL != channel_key); + GNUNET_assert (NULL != slave_key); + GNUNET_assert (GNUNET_YES == did_join || GNUNET_NO == did_join); + GNUNET_assert (did_join + ? effective_since <= announced_at + : effective_since == 0); + + struct MembershipStoreRequest *req; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (req, GNUNET_MESSAGE_TYPE_PSYCSTORE_MEMBERSHIP_STORE); + req->channel_key = *channel_key; + req->slave_key = *slave_key; + req->did_join = did_join; + req->announced_at = GNUNET_htonll (announced_at); + req->effective_since = GNUNET_htonll (effective_since); + req->group_generation = GNUNET_htonll (group_generation); + + return + op_send (h, op_create (h, h->op, result_cb, cls), + env, &req->op_id); +} + + +/** + * Test if a member was admitted to the channel at the given message ID. + * + * This is useful when relaying and replaying messages to check if a particular + * slave has access to the message fragment with a given group generation. It + * is also used when handling join requests to determine whether the slave is + * currently admitted to the channel. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param slave_key + * Public key of slave whose membership to check. + * @param message_id + * Message ID for which to do the membership test. + * @param group_generation + * Group generation of the fragment of the message to test. + * It has relevance if the message consists of multiple fragments with + * different group generations. + * @param result_cb + * Callback to call with the test result. + * @param cls + * Closure for the callback. + * + * @return Operation handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_membership_test (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + uint64_t message_id, + uint64_t group_generation, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls) +{ + struct MembershipTestRequest *req; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (req, GNUNET_MESSAGE_TYPE_PSYCSTORE_MEMBERSHIP_TEST); + req->channel_key = *channel_key; + req->slave_key = *slave_key; + req->message_id = GNUNET_htonll (message_id); + req->group_generation = GNUNET_htonll (group_generation); + + return + op_send (h, op_create (h, h->op, result_cb, cls), + env, &req->op_id); +} + + +/** + * Store a message fragment sent to a channel. + * + * @param h Handle for the PSYCstore. + * @param channel_key The channel the message belongs to. + * @param message Message to store. + * @param psycstore_flags Flags indicating whether the PSYC message contains + * state modifiers. + * @param result_cb Callback to call with the result of the operation. + * @param cls Closure for the callback. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_fragment_store (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_MULTICAST_MessageHeader *msg, + enum GNUNET_PSYCSTORE_MessageFlags psycstore_flags, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls) +{ + uint16_t size = ntohs (msg->header.size); + struct FragmentStoreRequest *req; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (req, size, + GNUNET_MESSAGE_TYPE_PSYCSTORE_FRAGMENT_STORE); + req->channel_key = *channel_key; + req->psycstore_flags = htonl (psycstore_flags); + GNUNET_memcpy (&req[1], msg, size); + + return + op_send (h, op_create (h, h->op, result_cb, cls), + env, &req->op_id); +} + + +/** + * Retrieve message fragments by fragment ID range. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param slave_key + * The slave requesting the fragment. If not NULL, a membership test is + * performed first and the fragment is only returned if the slave has + * access to it. + * @param first_fragment_id + * First fragment ID to retrieve. + * Use 0 to get the latest message fragment. + * @param last_fragment_id + * Last consecutive fragment ID to retrieve. + * Use 0 to get the latest message fragment. + * @param fragment_limit + * Maximum number of fragments to retrieve. + * @param fragment_cb + * Callback to call with the retrieved fragments. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callbacks. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_fragment_get (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + uint64_t first_fragment_id, + uint64_t last_fragment_id, + GNUNET_PSYCSTORE_FragmentCallback fragment_cb, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls) +{ + struct FragmentGetRequest *req; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (req, GNUNET_MESSAGE_TYPE_PSYCSTORE_FRAGMENT_GET); + req->channel_key = *channel_key; + req->first_fragment_id = GNUNET_htonll (first_fragment_id); + req->last_fragment_id = GNUNET_htonll (last_fragment_id); + if (NULL != slave_key) + { + req->slave_key = *slave_key; + req->do_membership_test = GNUNET_YES; + } + + struct GNUNET_PSYCSTORE_OperationHandle * + op = op_create (h, h->op, result_cb, cls); + op->fragment_cb = fragment_cb; + op->cls = cls; + return op_send (h, op, env, &req->op_id); +} + + +/** + * Retrieve latest message fragments. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param slave_key + * The slave requesting the fragment. If not NULL, a membership test is + * performed first and the fragment is only returned if the slave has + * access to it. + * @param first_fragment_id + * First fragment ID to retrieve. + * Use 0 to get the latest message fragment. + * @param last_fragment_id + * Last consecutive fragment ID to retrieve. + * Use 0 to get the latest message fragment. + * @param fragment_limit + * Maximum number of fragments to retrieve. + * @param fragment_cb + * Callback to call with the retrieved fragments. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callbacks. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_fragment_get_latest (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + uint64_t fragment_limit, + GNUNET_PSYCSTORE_FragmentCallback fragment_cb, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls) +{ + struct FragmentGetRequest *req; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (req, GNUNET_MESSAGE_TYPE_PSYCSTORE_FRAGMENT_GET); + req->channel_key = *channel_key; + req->fragment_limit = GNUNET_ntohll (fragment_limit); + if (NULL != slave_key) + { + req->slave_key = *slave_key; + req->do_membership_test = GNUNET_YES; + } + + struct GNUNET_PSYCSTORE_OperationHandle * + op = op_create (h, h->op, result_cb, cls); + op->fragment_cb = fragment_cb; + op->cls = cls; + return op_send (h, op, env, &req->op_id); +} + + +/** + * Retrieve all fragments of messages in a message ID range. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param slave_key + * The slave requesting the message. + * If not NULL, a membership test is performed first + * and the message is only returned if the slave has access to it. + * @param first_message_id + * First message ID to retrieve. + * @param last_message_id + * Last consecutive message ID to retrieve. + * @param fragment_limit + * Maximum number of fragments to retrieve. + * @param method_prefix + * Retrieve only messages with a matching method prefix. + * @todo Implement method_prefix query. + * @param fragment_cb + * Callback to call with the retrieved fragments. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callbacks. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_message_get (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + uint64_t first_message_id, + uint64_t last_message_id, + uint64_t fragment_limit, + const char *method_prefix, + GNUNET_PSYCSTORE_FragmentCallback fragment_cb, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls) +{ + struct MessageGetRequest *req; + if (NULL == method_prefix) + method_prefix = ""; + uint16_t method_size = strnlen (method_prefix, + GNUNET_MAX_MESSAGE_SIZE + - sizeof (*req)) + 1; + + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (req, method_size, + GNUNET_MESSAGE_TYPE_PSYCSTORE_MESSAGE_GET); + req->channel_key = *channel_key; + req->first_message_id = GNUNET_htonll (first_message_id); + req->last_message_id = GNUNET_htonll (last_message_id); + req->fragment_limit = GNUNET_htonll (fragment_limit); + if (NULL != slave_key) + { + req->slave_key = *slave_key; + req->do_membership_test = GNUNET_YES; + } + GNUNET_memcpy (&req[1], method_prefix, method_size); + ((char *) &req[1])[method_size - 1] = '\0'; + + struct GNUNET_PSYCSTORE_OperationHandle * + op = op_create (h, h->op, result_cb, cls); + op->fragment_cb = fragment_cb; + op->cls = cls; + return op_send (h, op, env, &req->op_id); +} + + +/** + * Retrieve all fragments of the latest messages. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param slave_key + * The slave requesting the message. + * If not NULL, a membership test is performed first + * and the message is only returned if the slave has access to it. + * @param message_limit + * Maximum number of messages to retrieve. + * @param method_prefix + * Retrieve only messages with a matching method prefix. + * @todo Implement method_prefix query. + * @param fragment_cb + * Callback to call with the retrieved fragments. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callbacks. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_message_get_latest (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + uint64_t message_limit, + const char *method_prefix, + GNUNET_PSYCSTORE_FragmentCallback fragment_cb, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls) +{ + struct MessageGetRequest *req; + + if (NULL == method_prefix) + method_prefix = ""; + uint16_t method_size = strnlen (method_prefix, + GNUNET_MAX_MESSAGE_SIZE + - sizeof (*req)) + 1; + GNUNET_assert ('\0' == method_prefix[method_size - 1]); + + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (req, method_size, + GNUNET_MESSAGE_TYPE_PSYCSTORE_MESSAGE_GET); + req->channel_key = *channel_key; + req->message_limit = GNUNET_ntohll (message_limit); + if (NULL != slave_key) + { + req->slave_key = *slave_key; + req->do_membership_test = GNUNET_YES; + } + GNUNET_memcpy (&req[1], method_prefix, method_size); + + struct GNUNET_PSYCSTORE_OperationHandle * + op = op_create (h, h->op, result_cb, cls); + op->fragment_cb = fragment_cb; + op->cls = cls; + return op_send (h, op, env, &req->op_id); +} + + +/** + * Retrieve a fragment of message specified by its message ID and fragment + * offset. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param slave_key + * The slave requesting the message fragment. If not NULL, a membership + * test is performed first and the message fragment is only returned + * if the slave has access to it. + * @param message_id + * Message ID to retrieve. Use 0 to get the latest message. + * @param fragment_offset + * Offset of the fragment to retrieve. + * @param fragment_cb + * Callback to call with the retrieved fragments. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callbacks. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_message_get_fragment (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + uint64_t message_id, + uint64_t fragment_offset, + GNUNET_PSYCSTORE_FragmentCallback fragment_cb, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls) +{ + struct MessageGetFragmentRequest *req; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (req, GNUNET_MESSAGE_TYPE_PSYCSTORE_MESSAGE_GET_FRAGMENT); + + req->channel_key = *channel_key; + req->message_id = GNUNET_htonll (message_id); + req->fragment_offset = GNUNET_htonll (fragment_offset); + if (NULL != slave_key) + { + req->slave_key = *slave_key; + req->do_membership_test = GNUNET_YES; + } + + struct GNUNET_PSYCSTORE_OperationHandle * + op = op_create (h, h->op, result_cb, cls); + op->fragment_cb = fragment_cb; + op->cls = cls; + return op_send (h, op, env, &req->op_id); +} + + +/** + * Retrieve latest values of counters for a channel master. + * + * The current value of counters are needed when a channel master is restarted, + * so that it can continue incrementing the counters from their last value. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * Public key that identifies the channel. + * @param ccb + * Callback to call with the result. + * @param ccb_cls + * Closure for the @a ccb callback. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_counters_get (struct GNUNET_PSYCSTORE_Handle *h, + struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + GNUNET_PSYCSTORE_CountersCallback counters_cb, + void *cls) +{ + struct OperationRequest *req; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (req, GNUNET_MESSAGE_TYPE_PSYCSTORE_COUNTERS_GET); + req->channel_key = *channel_key; + + struct GNUNET_PSYCSTORE_OperationHandle * + op = op_create (h, h->op, NULL, NULL); + op->counters_cb = counters_cb; + op->cls = cls; + return op_send (h, op, env, &req->op_id); +} + + +/** + * Apply modifiers of a message to the current channel state. + * + * An error is returned if there are missing messages containing state + * operations before the current one. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param message_id + * ID of the message that contains the @a modifiers. + * @param state_delta + * Value of the _state_delta PSYC header variable of the message. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for @a result_cb. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_state_modify (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id, + uint64_t state_delta, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls) +{ + struct StateModifyRequest *req; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (req, GNUNET_MESSAGE_TYPE_PSYCSTORE_STATE_MODIFY); + req->channel_key = *channel_key; + req->message_id = GNUNET_htonll (message_id); + req->state_delta = GNUNET_htonll (state_delta); + + return op_send (h, op_create (h, h->op, result_cb, cls), + env, &req->op_id); +} + + +struct StateSyncClosure +{ + GNUNET_PSYCSTORE_ResultCallback result_cb; + void *cls; + uint8_t last; +}; + + +static void +state_sync_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + struct StateSyncClosure *ssc = cls; + if (GNUNET_OK != result || ssc->last) + ssc->result_cb (ssc->cls, result, err_msg, err_msg_size); + GNUNET_free (ssc); +} + + +/** + * Store synchronized state. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param max_state_message_id + * ID of the last stateful message before @a state_hash_message_id. + * @param state_hash_message_id + * ID of the message that contains the state_hash PSYC header variable. + * @param modifier_count + * Number of elements in the @a modifiers array. + * @param modifiers + * Full state to store. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callback. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_state_sync (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t max_state_message_id, + uint64_t state_hash_message_id, + size_t modifier_count, + const struct GNUNET_PSYC_Modifier *modifiers, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls) +{ + struct GNUNET_PSYCSTORE_OperationHandle *op = NULL; + size_t i; + + for (i = 0; i < modifier_count; i++) { + struct StateSyncRequest *req; + uint16_t name_size = strlen (modifiers[i].name) + 1; + + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (req, + sizeof (*req) + name_size + modifiers[i].value_size, + GNUNET_MESSAGE_TYPE_PSYCSTORE_STATE_SYNC); + + req->header.type = htons (GNUNET_MESSAGE_TYPE_PSYCSTORE_STATE_SYNC); + req->header.size = htons (sizeof (*req) + name_size + + modifiers[i].value_size); + req->channel_key = *channel_key; + req->max_state_message_id = GNUNET_htonll (max_state_message_id); + req->state_hash_message_id = GNUNET_htonll (state_hash_message_id); + req->name_size = htons (name_size); + req->flags + = (0 == i) + ? STATE_OP_FIRST + : (modifier_count - 1 == i) + ? STATE_OP_LAST + : 0; + + GNUNET_memcpy (&req[1], modifiers[i].name, name_size); + GNUNET_memcpy ((char *) &req[1] + name_size, modifiers[i].value, modifiers[i].value_size); + + struct StateSyncClosure *ssc = GNUNET_malloc (sizeof (*ssc)); + ssc->last = (req->flags & STATE_OP_LAST); + ssc->result_cb = result_cb; + ssc->cls = cls; + + op_send (h, op_create (h, h->op, state_sync_result, ssc), + env, &req->op_id); + } + // FIXME: only one operation is returned, + // add pointers to other operations and make all cancellable. + return op; +} + + +/** + * Reset the state of a channel. + * + * Delete all state variables stored for the given channel. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callback. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_state_reset (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey + *channel_key, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls) +{ + struct OperationRequest *req; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (req, GNUNET_MESSAGE_TYPE_PSYCSTORE_STATE_RESET); + req->channel_key = *channel_key; + + return + op_send (h, op_create (h, h->op, result_cb, cls), + env, &req->op_id); +} + + +/** + * Update signed values of state variables in the state store. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param message_id + * Message ID that contained the state @a hash. + * @param hash + * Hash of the serialized full state. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callback. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_state_hash_update (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + uint64_t message_id, + const struct GNUNET_HashCode *hash, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls) +{ + struct StateHashUpdateRequest *req; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (req, GNUNET_MESSAGE_TYPE_PSYCSTORE_STATE_HASH_UPDATE); + req->channel_key = *channel_key; + req->hash = *hash; + + return + op_send (h, op_create (h, h->op, result_cb, cls), + env, &req->op_id); +} + + +/** + * Retrieve the best matching state variable. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param name + * Name of variable to match, the returned variable might be less specific. + * @param state_cb + * Callback to return the matching state variable. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callbacks. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_state_get (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name, + GNUNET_PSYCSTORE_StateCallback state_cb, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls) +{ + size_t name_size = strlen (name) + 1; + struct OperationRequest *req; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (req, name_size, + GNUNET_MESSAGE_TYPE_PSYCSTORE_STATE_GET); + req->channel_key = *channel_key; + GNUNET_memcpy (&req[1], name, name_size); + + struct GNUNET_PSYCSTORE_OperationHandle * + op = op_create (h, h->op, result_cb, cls); + op->state_cb = state_cb; + op->cls = cls; + return op_send (h, op, env, &req->op_id); +} + + +/** + * Retrieve all state variables for a channel with the given prefix. + * + * @param h + * Handle for the PSYCstore. + * @param channel_key + * The channel we are interested in. + * @param name_prefix + * Prefix of state variable names to match. + * @param state_cb + * Callback to return matching state variables. + * @param result_cb + * Callback to call with the result of the operation. + * @param cls + * Closure for the callbacks. + * + * @return Handle that can be used to cancel the operation. + */ +struct GNUNET_PSYCSTORE_OperationHandle * +GNUNET_PSYCSTORE_state_get_prefix (struct GNUNET_PSYCSTORE_Handle *h, + const struct GNUNET_CRYPTO_EddsaPublicKey *channel_key, + const char *name_prefix, + GNUNET_PSYCSTORE_StateCallback state_cb, + GNUNET_PSYCSTORE_ResultCallback result_cb, + void *cls) +{ + size_t name_size = strlen (name_prefix) + 1; + struct OperationRequest *req; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (req, name_size, + GNUNET_MESSAGE_TYPE_PSYCSTORE_STATE_GET_PREFIX); + req->channel_key = *channel_key; + GNUNET_memcpy (&req[1], name_prefix, name_size); + + struct GNUNET_PSYCSTORE_OperationHandle * + op = op_create (h, h->op, result_cb, cls); + op->state_cb = state_cb; + op->cls = cls; + return op_send (h, op, env, &req->op_id); +} + +/* end of psycstore_api.c */ diff --git a/src/psycstore/test_plugin_psycstore.c b/src/psycstore/test_plugin_psycstore.c new file mode 100644 index 0000000..ff4eac8 --- /dev/null +++ b/src/psycstore/test_plugin_psycstore.c @@ -0,0 +1,532 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @author Gabor X Toth + * @author Christian Grothoff + * + * @file + * Test for the PSYCstore plugins. + */ + +#include + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_testing_lib.h" +#include "gnunet_psycstore_plugin.h" +#include "gnunet_psycstore_service.h" +#include "gnunet_multicast_service.h" + +#define DEBUG_PSYCSTORE GNUNET_EXTRA_LOGGING +#if DEBUG_PSYCSTORE +# define LOG_LEVEL "DEBUG" +#else +# define LOG_LEVEL "WARNING" +#endif + +#define C2ARG(str) str, (sizeof (str) - 1) + +#define LOG(kind,...) \ + GNUNET_log_from (kind, "test-plugin-psycstore", __VA_ARGS__) + +static int ok; + +/** + * Name of plugin under test. + */ +static const char *plugin_name; + +static struct GNUNET_CRYPTO_EddsaPrivateKey *channel_key; +static struct GNUNET_CRYPTO_EcdsaPrivateKey *slave_key; + +static struct GNUNET_CRYPTO_EddsaPublicKey channel_pub_key; +static struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key; + +/** + * Function called when the service shuts down. Unloads our psycstore + * plugin. + * + * @param api api to unload + */ +static void +unload_plugin (struct GNUNET_PSYCSTORE_PluginFunctions *api) +{ + char *libname; + + GNUNET_asprintf (&libname, "libgnunet_plugin_psycstore_%s", plugin_name); + GNUNET_break (NULL == GNUNET_PLUGIN_unload (libname, api)); + GNUNET_free (libname); +} + + +/** + * Load the psycstore plugin. + * + * @param cfg configuration to pass + * @return NULL on error + */ +static struct GNUNET_PSYCSTORE_PluginFunctions * +load_plugin (const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct GNUNET_PSYCSTORE_PluginFunctions *ret; + char *libname; + + GNUNET_log (GNUNET_ERROR_TYPE_INFO, _ ("Loading `%s' psycstore plugin\n"), + plugin_name); + GNUNET_asprintf (&libname, "libgnunet_plugin_psycstore_%s", plugin_name); + if (NULL == (ret = GNUNET_PLUGIN_load (libname, (void*) cfg))) + { + FPRINTF (stderr, "Failed to load plugin `%s'!\n", plugin_name); + return NULL; + } + GNUNET_free (libname); + return ret; +} + + +#define MAX_MSG 16 + +struct FragmentClosure +{ + uint8_t n; + uint64_t flags[MAX_MSG]; + struct GNUNET_MULTICAST_MessageHeader *msg[MAX_MSG]; +}; + +static int +fragment_cb (void *cls, struct GNUNET_MULTICAST_MessageHeader *msg2, + enum GNUNET_PSYCSTORE_MessageFlags flags) +{ + struct FragmentClosure *fcls = cls; + struct GNUNET_MULTICAST_MessageHeader *msg1; + uint64_t flags1; + int ret; + + if (fcls->n >= MAX_MSG) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + msg1 = fcls->msg[fcls->n]; + flags1 = fcls->flags[fcls->n++]; + if (NULL == msg1) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + + if (flags1 == flags && msg1->header.size == msg2->header.size + && 0 == memcmp (msg1, msg2, ntohs (msg1->header.size))) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, "Fragment %llu matches\n", + GNUNET_ntohll (msg1->fragment_id)); + ret = GNUNET_YES; + } + else + { + LOG (GNUNET_ERROR_TYPE_ERROR, "Fragment %llu differs\n", + GNUNET_ntohll (msg1->fragment_id)); + ret = GNUNET_SYSERR; + } + + GNUNET_free (msg2); + return ret; +} + + +struct StateClosure { + size_t n; + char *name[16]; + void *value[16]; + size_t value_size[16]; +}; + +static int +state_cb (void *cls, const char *name, const void *value, uint32_t value_size) +{ + struct StateClosure *scls = cls; + const void *val = scls->value[scls->n]; // FIXME: check for n out-of-bounds FIRST! + size_t val_size = scls->value_size[scls->n++]; + + /* FIXME: check name */ + + LOG (GNUNET_ERROR_TYPE_DEBUG, + " name = %s, value_size = %u\n", + name, value_size); + + return GNUNET_YES; + return value_size == val_size && 0 == memcmp (value, val, val_size) + ? GNUNET_YES + : GNUNET_SYSERR; +} + + +static void +run (void *cls, char *const *args, const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *cfg) +{ + struct GNUNET_PSYCSTORE_PluginFunctions *db; + + ok = 1; + db = load_plugin (cfg); + if (NULL == db) + { + FPRINTF (stderr, + "%s", + "Failed to initialize PSYCstore. " + "Database likely not setup, skipping test.\n"); + ok = 77; + return; + } + + /* Store & test membership */ + + LOG (GNUNET_ERROR_TYPE_INFO, "MEMBERSHIP\n"); + + channel_key = GNUNET_CRYPTO_eddsa_key_create (); + slave_key = GNUNET_CRYPTO_ecdsa_key_create (); + + GNUNET_CRYPTO_eddsa_key_get_public (channel_key, + &channel_pub_key); + GNUNET_CRYPTO_ecdsa_key_get_public (slave_key, &slave_pub_key); + + LOG (GNUNET_ERROR_TYPE_INFO, "membership_store()\n"); + + GNUNET_assert (GNUNET_OK == db->membership_store (db->cls, &channel_pub_key, + &slave_pub_key, GNUNET_YES, + 4, 2, 1)); + + LOG (GNUNET_ERROR_TYPE_INFO, "membership_test()\n"); + + GNUNET_assert (GNUNET_YES == db->membership_test (db->cls, &channel_pub_key, + &slave_pub_key, 4)); + + GNUNET_assert (GNUNET_YES == db->membership_test (db->cls, &channel_pub_key, + &slave_pub_key, 2)); + + GNUNET_assert (GNUNET_NO == db->membership_test (db->cls, &channel_pub_key, + &slave_pub_key, 1)); + + /* Store & get messages */ + + LOG (GNUNET_ERROR_TYPE_INFO, "MESSAGES\n"); + + struct GNUNET_MULTICAST_MessageHeader *msg + = GNUNET_malloc (sizeof (*msg) + sizeof (channel_pub_key)); + GNUNET_assert (msg != NULL); + + msg->header.type = htons (GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE); + msg->header.size = htons (sizeof (*msg) + sizeof (channel_pub_key)); + + uint64_t fragment_id = INT64_MAX - 1; + msg->fragment_id = GNUNET_htonll (fragment_id); + + uint64_t message_id = INT64_MAX - 10; + msg->message_id = GNUNET_htonll (message_id); + + uint64_t group_generation = INT64_MAX - 3; + msg->group_generation = GNUNET_htonll (group_generation); + + msg->hop_counter = htonl (9); + msg->fragment_offset = GNUNET_htonll (0); + msg->flags = htonl (GNUNET_MULTICAST_MESSAGE_LAST_FRAGMENT); + + GNUNET_memcpy (&msg[1], &channel_pub_key, sizeof (channel_pub_key)); + + msg->purpose.size = htonl (ntohs (msg->header.size) + - sizeof (msg->header) + - sizeof (msg->hop_counter) + - sizeof (msg->signature)); + msg->purpose.purpose = htonl (234); + GNUNET_assert (GNUNET_OK == + GNUNET_CRYPTO_eddsa_sign (channel_key, &msg->purpose, &msg->signature)); + + LOG (GNUNET_ERROR_TYPE_INFO, "fragment_store()\n"); + + struct FragmentClosure fcls = { 0 }; + fcls.n = 0; + fcls.msg[0] = msg; + fcls.flags[0] = GNUNET_PSYCSTORE_MESSAGE_STATE; + + GNUNET_assert ( + GNUNET_OK == db->fragment_store (db->cls, &channel_pub_key, msg, + fcls.flags[0])); + + LOG (GNUNET_ERROR_TYPE_INFO, "fragment_get(%" PRIu64 ")\n", fragment_id); + + uint64_t ret_frags = 0; + GNUNET_assert ( + GNUNET_OK == db->fragment_get (db->cls, &channel_pub_key, + fragment_id, fragment_id, + &ret_frags, fragment_cb, &fcls)); + GNUNET_assert (fcls.n == 1); + + LOG (GNUNET_ERROR_TYPE_INFO, "message_get_fragment()\n"); + + fcls.n = 0; + GNUNET_assert ( + GNUNET_OK == db->message_get_fragment (db->cls, &channel_pub_key, + GNUNET_ntohll (msg->message_id), + GNUNET_ntohll (msg->fragment_offset), + fragment_cb, &fcls)); + GNUNET_assert (fcls.n == 1); + + LOG (GNUNET_ERROR_TYPE_INFO, "message_add_flags()\n"); + GNUNET_assert ( + GNUNET_OK == db->message_add_flags (db->cls, &channel_pub_key, + GNUNET_ntohll (msg->message_id), + GNUNET_PSYCSTORE_MESSAGE_STATE_APPLIED)); + LOG (GNUNET_ERROR_TYPE_INFO, "fragment_get(%" PRIu64 ")\n", fragment_id); + + fcls.n = 0; + fcls.flags[0] |= GNUNET_PSYCSTORE_MESSAGE_STATE_APPLIED; + + GNUNET_assert ( + GNUNET_OK == db->fragment_get (db->cls, &channel_pub_key, + fragment_id, fragment_id, + &ret_frags, fragment_cb, &fcls)); + + GNUNET_assert (fcls.n == 1); + + LOG (GNUNET_ERROR_TYPE_INFO, "fragment_store()\n"); + + struct GNUNET_MULTICAST_MessageHeader *msg1 + = GNUNET_malloc (sizeof (*msg1) + sizeof (channel_pub_key)); + + GNUNET_memcpy (msg1, msg, sizeof (*msg1) + sizeof (channel_pub_key)); + + msg1->fragment_id = GNUNET_htonll (INT64_MAX); + msg1->fragment_offset = GNUNET_htonll (32768); + + fcls.n = 0; + fcls.msg[1] = msg1; + fcls.flags[1] = GNUNET_PSYCSTORE_MESSAGE_STATE_HASH; + + GNUNET_assert (GNUNET_OK == db->fragment_store (db->cls, &channel_pub_key, msg1, + fcls.flags[1])); + + LOG (GNUNET_ERROR_TYPE_INFO, "message_get()\n"); + + GNUNET_assert ( + GNUNET_OK == db->message_get (db->cls, &channel_pub_key, + message_id, message_id, 0, + &ret_frags, fragment_cb, &fcls)); + GNUNET_assert (fcls.n == 2 && ret_frags == 2); + + /* Message counters */ + + LOG (GNUNET_ERROR_TYPE_INFO, "counters_message_get()\n"); + + fragment_id = 0; + message_id = 0; + group_generation = 0; + GNUNET_assert ( + GNUNET_OK == db->counters_message_get (db->cls, &channel_pub_key, + &fragment_id, &message_id, + &group_generation) + && fragment_id == GNUNET_ntohll (msg1->fragment_id) + && message_id == GNUNET_ntohll (msg1->message_id) + && group_generation == GNUNET_ntohll (msg1->group_generation)); + + /* Modify state */ + + LOG (GNUNET_ERROR_TYPE_INFO, "STATE\n"); + + LOG (GNUNET_ERROR_TYPE_INFO, "state_modify_*()\n"); + + message_id = GNUNET_ntohll (fcls.msg[0]->message_id) + 1; + GNUNET_assert (GNUNET_OK == db->state_modify_begin (db->cls, &channel_pub_key, + message_id, 0)); + + GNUNET_assert (GNUNET_OK == db->state_modify_op (db->cls, &channel_pub_key, + GNUNET_PSYC_OP_ASSIGN, + "_foo", + C2ARG("one two three"))); + + GNUNET_assert (GNUNET_OK == db->state_modify_op (db->cls, &channel_pub_key, + GNUNET_PSYC_OP_ASSIGN, + "_foo_bar", slave_key, + sizeof (*slave_key))); + + GNUNET_assert (GNUNET_OK == db->state_modify_end (db->cls, &channel_pub_key, + message_id)); + + LOG (GNUNET_ERROR_TYPE_INFO, "state_get()\n"); + + struct StateClosure scls = { 0 }; + scls.n = 0; + scls.value[0] = "one two three"; + scls.value_size[0] = strlen ("one two three"); + + GNUNET_assert (GNUNET_OK == db->state_get (db->cls, &channel_pub_key, "_foo", + state_cb, &scls)); + GNUNET_assert (scls.n == 1); + + LOG (GNUNET_ERROR_TYPE_INFO, "state_get_prefix()\n"); + + scls.n = 0; + scls.value[1] = slave_key; + scls.value_size[1] = sizeof (*slave_key); + + GNUNET_assert (GNUNET_OK == db->state_get_prefix (db->cls, &channel_pub_key, + "_foo", state_cb, &scls)); + GNUNET_assert (scls.n == 2); + + LOG (GNUNET_ERROR_TYPE_INFO, "state_get_signed()\n"); + + scls.n = 0; + GNUNET_assert (GNUNET_NO == db->state_get_signed (db->cls, &channel_pub_key, + state_cb, &scls)); + GNUNET_assert (scls.n == 0); + + LOG (GNUNET_ERROR_TYPE_INFO, "state_update_signed()\n"); + + GNUNET_assert (GNUNET_OK == db->state_update_signed (db->cls, + &channel_pub_key)); + + LOG (GNUNET_ERROR_TYPE_INFO, "state_get_signed()\n"); + + scls.n = 0; + GNUNET_assert (GNUNET_YES == db->state_get_signed (db->cls, &channel_pub_key, + state_cb, &scls)); + GNUNET_assert (scls.n == 2); + + /* State counters */ + + LOG (GNUNET_ERROR_TYPE_INFO, "counters_state_get()\n"); + + uint64_t max_state_msg_id = 0; + GNUNET_assert (GNUNET_OK == db->counters_state_get (db->cls, &channel_pub_key, + &max_state_msg_id) + && max_state_msg_id == message_id); + + /* State sync */ + + LOG (GNUNET_ERROR_TYPE_INFO, "state_sync_*()\n"); + + scls.n = 0; + scls.value[0] = channel_key; + scls.value_size[0] = sizeof (*channel_key); + scls.value[1] = "three two one"; + scls.value_size[1] = strlen ("three two one"); + + GNUNET_assert (GNUNET_OK == db->state_sync_begin (db->cls, &channel_pub_key)); + + GNUNET_assert (GNUNET_OK == db->state_sync_assign (db->cls, &channel_pub_key, + "_sync_bar", scls.value[0], + scls.value_size[0])); + + GNUNET_assert (GNUNET_OK == db->state_sync_assign (db->cls, &channel_pub_key, + "_sync_foo", scls.value[1], + scls.value_size[1])); + + GNUNET_assert (GNUNET_OK == db->state_sync_end (db->cls, &channel_pub_key, + max_state_msg_id, + INT64_MAX - 5)); + + GNUNET_assert (GNUNET_NO == db->state_get_prefix (db->cls, &channel_pub_key, + "_foo", state_cb, &scls)); + GNUNET_assert (scls.n == 0); + + GNUNET_assert (GNUNET_OK == db->state_get_prefix (db->cls, &channel_pub_key, + "_sync", state_cb, &scls)); + GNUNET_assert (scls.n == 2); + + scls.n = 0; + GNUNET_assert (GNUNET_OK == db->state_get_signed (db->cls, &channel_pub_key, + state_cb, &scls)); + GNUNET_assert (scls.n == 2); + + /* Modify state after sync */ + + LOG (GNUNET_ERROR_TYPE_INFO, "state_modify_*()\n"); + + message_id = GNUNET_ntohll (fcls.msg[0]->message_id) + 6; + GNUNET_assert (GNUNET_OK == db->state_modify_begin (db->cls, &channel_pub_key, + message_id, + message_id - max_state_msg_id)); + + GNUNET_assert (GNUNET_OK == db->state_modify_op (db->cls, &channel_pub_key, + GNUNET_PSYC_OP_ASSIGN, + "_sync_foo", + C2ARG("five six seven"))); + + GNUNET_assert (GNUNET_OK == db->state_modify_end (db->cls, &channel_pub_key, + message_id)); + + /* Reset state */ + + LOG (GNUNET_ERROR_TYPE_INFO, "state_reset()\n"); + + scls.n = 0; + GNUNET_assert (GNUNET_OK == db->state_reset (db->cls, &channel_pub_key)); + GNUNET_assert (scls.n == 0); + + ok = 0; + + if (NULL != channel_key) + { + GNUNET_free (channel_key); + channel_key = NULL; + } + if (NULL != slave_key) + { + GNUNET_free (slave_key); + slave_key = NULL; + } + + unload_plugin (db); +} + + +int +main (int argc, char *argv[]) +{ + char cfg_name[128]; + char *const xargv[] = { + "test-plugin-psycstore", + "-c", cfg_name, + "-L", LOG_LEVEL, + NULL + }; + struct GNUNET_GETOPT_CommandLineOption options[] = { + GNUNET_GETOPT_OPTION_END + }; + GNUNET_DISK_directory_remove ("/tmp/gnunet-test-plugin-psycstore-sqlite"); + GNUNET_log_setup ("test-plugin-psycstore", LOG_LEVEL, NULL); + plugin_name = GNUNET_TESTING_get_testname_from_underscore (argv[0]); + GNUNET_snprintf (cfg_name, sizeof (cfg_name), "test_plugin_psycstore_%s.conf", + plugin_name); + GNUNET_PROGRAM_run ((sizeof (xargv) / sizeof (char *)) - 1, xargv, + "test-plugin-psycstore", "nohelp", options, &run, NULL); + + if ( (0 != ok) && + (77 != ok) ) + FPRINTF (stderr, "Missed some testcases: %d\n", ok); + +#if ! DEBUG_PSYCSTORE + GNUNET_DISK_directory_remove ("/tmp/gnunet-test-plugin-psycstore-sqlite"); +#endif + + return ok; +} + +/* end of test_plugin_psycstore.c */ diff --git a/src/psycstore/test_plugin_psycstore_mysql.conf b/src/psycstore/test_plugin_psycstore_mysql.conf new file mode 100644 index 0000000..e15b3fd --- /dev/null +++ b/src/psycstore/test_plugin_psycstore_mysql.conf @@ -0,0 +1,7 @@ +[psycstore-mysql] +DATABASE = test +# CONFIG = ~/.my.cnf +# USER = gnunet +# PASSWORD = +# HOST = localhost +# PORT = 3306 diff --git a/src/psycstore/test_plugin_psycstore_postgres.conf b/src/psycstore/test_plugin_psycstore_postgres.conf new file mode 100644 index 0000000..4b870dd --- /dev/null +++ b/src/psycstore/test_plugin_psycstore_postgres.conf @@ -0,0 +1,2 @@ +[psycstore-postgres] +CONFIG = connect_timeout=10; dbname=template1 diff --git a/src/psycstore/test_plugin_psycstore_sqlite.conf b/src/psycstore/test_plugin_psycstore_sqlite.conf new file mode 100644 index 0000000..498b1d0 --- /dev/null +++ b/src/psycstore/test_plugin_psycstore_sqlite.conf @@ -0,0 +1,2 @@ +[psycstore-sqlite] +FILENAME = $GNUNET_TMP/gnunet-test-plugin-psycstore-sqlite/sqlite.db diff --git a/src/psycstore/test_psycstore.c b/src/psycstore/test_psycstore.c new file mode 100644 index 0000000..ca50904 --- /dev/null +++ b/src/psycstore/test_psycstore.c @@ -0,0 +1,586 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file psycstore/test_psycstore.c + * @brief Test for the PSYCstore service. + * @author Gabor X Toth + * @author Christian Grothoff + */ + +#include + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_common.h" +#include "gnunet_testing_lib.h" +#include "gnunet_psycstore_service.h" + +#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30) + + +/** + * Return value from 'main'. + */ +static int res; + +/** + * Handle to PSYCstore service. + */ +static struct GNUNET_PSYCSTORE_Handle *h; + +/** + * Handle to PSYCstore operation. + */ +static struct GNUNET_PSYCSTORE_OperationHandle *op; + +/** + * Handle for task for timeout termination. + */ +static struct GNUNET_SCHEDULER_Task *end_badly_task; + +static struct GNUNET_CRYPTO_EddsaPrivateKey *channel_key; +static struct GNUNET_CRYPTO_EcdsaPrivateKey *slave_key; + +static struct GNUNET_CRYPTO_EddsaPublicKey channel_pub_key; +static struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key; + +static struct FragmentClosure +{ + uint8_t n; + uint8_t n_expected; + uint64_t flags[16]; + struct GNUNET_MULTICAST_MessageHeader *msg[16]; +} fcls; + +struct StateClosure { + size_t n; + char *name[16]; + void *value[16]; + size_t value_size[16]; +} scls; + +static struct GNUNET_PSYC_Modifier modifiers[16]; + +/** + * Clean up all resources used. + */ +static void +cleanup () +{ + if (NULL != op) + { + GNUNET_PSYCSTORE_operation_cancel (op); + op = NULL; + } + if (NULL != h) + { + GNUNET_PSYCSTORE_disconnect (h); + h = NULL; + } + if (NULL != channel_key) + { + GNUNET_free (channel_key); + channel_key = NULL; + } + if (NULL != slave_key) + { + GNUNET_free (slave_key); + slave_key = NULL; + } + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Terminate the testcase (failure). + * + * @param cls NULL + */ +static void +end_badly (void *cls) +{ + res = 1; + cleanup (); +} + + +/** + * Terminate the testcase (success). + * + * @param cls NULL + */ +static void +end_normally (void *cls) +{ + res = 0; + cleanup (); +} + + +/** + * Finish the testcase (successfully). + */ +static void +end () +{ + if (NULL != end_badly_task) + { + GNUNET_SCHEDULER_cancel (end_badly_task); + end_badly_task = NULL; + } + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_MILLISECONDS, + &end_normally, NULL); +} + + +static void +state_reset_result (void *cls, + int64_t result, + const char *err_msg, + uint16_t err_msg_size) +{ + op = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "state_reset_result:\t%d\n", + (int) result); + GNUNET_assert (GNUNET_OK == result); + + op = GNUNET_PSYCSTORE_state_reset (h, &channel_pub_key, + &state_reset_result, cls); + GNUNET_PSYCSTORE_operation_cancel (op); + op = NULL; + end (); +} + + +static int +state_result (void *cls, + const char *name, + const void *value, + uint32_t value_size) +{ + struct StateClosure *scls = cls; + const char *nam = scls->name[scls->n]; + const void *val = scls->value[scls->n]; + size_t val_size = scls->value_size[scls->n++]; + + if (value_size == val_size + && 0 == memcmp (value, val, val_size) + && 0 == strcmp (name, nam)) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + " variable %s matches\n", + name); + return GNUNET_YES; + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + " variable %s differs\nReceived: %.*s\nExpected: %.*s\n", + name, (int) value_size, (char*) value, (int) val_size, (char*) val); + GNUNET_assert (0); + return GNUNET_SYSERR; + } +} + + +static void +state_get_prefix_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + struct StateClosure *scls = cls; + op = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "state_get_prefix_result:\t%ld\n", (long int) result); + GNUNET_assert (GNUNET_OK == result && 2 == scls->n); + + op = GNUNET_PSYCSTORE_state_reset (h, &channel_pub_key, + &state_reset_result, cls); +} + + +static void +state_get_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + op = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "state_get_result:\t%ld\n", (long int) result); + GNUNET_assert (GNUNET_OK == result); + + scls.n = 0; + + scls.name[0] = "_sync_bar"; + scls.value[0] = "ten eleven twelve"; + scls.value_size[0] = sizeof ("ten eleven twelve") - 1; + + scls.name[1] = "_sync_foo"; + scls.value[1] = "three two one"; + scls.value_size[1] = sizeof ("three two one") - 1; + + op = GNUNET_PSYCSTORE_state_get_prefix (h, &channel_pub_key, "_sync", + &state_result, + &state_get_prefix_result, &scls); +} + + +static void +counters_result (void *cls, int status, uint64_t max_fragment_id, + uint64_t max_message_id, uint64_t max_group_generation, + uint64_t max_state_message_id) +{ + struct FragmentClosure *fcls = cls; + int result = 0; + op = NULL; + + if (GNUNET_OK == status + && max_fragment_id == GNUNET_ntohll (fcls->msg[2]->fragment_id) + && max_message_id == GNUNET_ntohll (fcls->msg[2]->message_id) + && max_group_generation == GNUNET_ntohll (fcls->msg[2]->group_generation) + && max_state_message_id == GNUNET_ntohll (fcls->msg[0]->message_id)) + result = 1; + + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "counters_get:\t%d\n", result); + GNUNET_assert (result == 1); + + scls.n = 0; + scls.name[0] = "_sync_bar"; + scls.value[0] = "ten eleven twelve"; + scls.value_size[0] = sizeof ("ten eleven twelve") - 1; + + op = GNUNET_PSYCSTORE_state_get (h, &channel_pub_key, "_sync_bar_x_yy_zzz", + &state_result, &state_get_result, &scls); +} + + +static void +state_modify_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + op = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "state_modify_result:\t%ld\n", (long int) result); + GNUNET_assert (GNUNET_OK == result); + + op = GNUNET_PSYCSTORE_counters_get (h, &channel_pub_key, + &counters_result, cls); +} + + +static void +state_sync_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + struct FragmentClosure *fcls = cls; + op = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "state_sync_result:\t%ld\n", (long int) result); + GNUNET_assert (GNUNET_OK == result); + + op = GNUNET_PSYCSTORE_state_modify (h, &channel_pub_key, + GNUNET_ntohll (fcls->msg[0]->message_id), + 0, state_modify_result, fcls); +} + + +static int +fragment_result (void *cls, + struct GNUNET_MULTICAST_MessageHeader *msg, + enum GNUNET_PSYCSTORE_MessageFlags flags) +{ + struct FragmentClosure *fcls = cls; + GNUNET_assert (fcls->n < fcls->n_expected); + struct GNUNET_MULTICAST_MessageHeader *msg0 = fcls->msg[fcls->n]; + uint64_t flags0 = fcls->flags[fcls->n++]; + + if (flags == flags0 && msg->header.size == msg0->header.size + && 0 == memcmp (msg, msg0, ntohs (msg->header.size))) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, " fragment %" PRIu64 " matches\n", + GNUNET_ntohll (msg->fragment_id)); + return GNUNET_YES; + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + " fragment differs: expected %" PRIu64 ", got %" PRIu64 "\n", + GNUNET_ntohll (msg0->fragment_id), + GNUNET_ntohll (msg->fragment_id)); + GNUNET_assert (0); + return GNUNET_SYSERR; + } +} + + +static void +message_get_latest_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + struct FragmentClosure *fcls = cls; + op = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "message_get_latest:\t%ld\n", (long int) result); + GNUNET_assert (0 < result && fcls->n == fcls->n_expected); + + modifiers[0] = (struct GNUNET_PSYC_Modifier) { + .oper = '=', + .name = "_sync_foo", + .value = "three two one", + .value_size = sizeof ("three two one") - 1 + }; + modifiers[1] = (struct GNUNET_PSYC_Modifier) { + .oper = '=', + .name = "_sync_bar", + .value = "ten eleven twelve", + .value_size = sizeof ("ten eleven twelve") - 1 + }; + + op = GNUNET_PSYCSTORE_state_sync (h, &channel_pub_key, + GNUNET_ntohll (fcls->msg[0]->message_id) + 1, + GNUNET_ntohll (fcls->msg[0]->message_id) + 2, + 2, modifiers, state_sync_result, fcls); +} + + +static void +message_get_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + struct FragmentClosure *fcls = cls; + op = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "message_get:\t%ld\n", (long int) result); + GNUNET_assert (0 < result && fcls->n == fcls->n_expected); + + fcls->n = 0; + fcls->n_expected = 3; + op = GNUNET_PSYCSTORE_message_get_latest (h, &channel_pub_key, &slave_pub_key, + 1, "", &fragment_result, + &message_get_latest_result, fcls); +} + + +static void +message_get_fragment_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + struct FragmentClosure *fcls = cls; + op = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "message_get_fragment:\t%ld\n", (long int) result); + GNUNET_assert (0 < result && fcls->n == fcls->n_expected); + + fcls->n = 0; + fcls->n_expected = 3; + uint64_t message_id = GNUNET_ntohll (fcls->msg[0]->message_id); + op = GNUNET_PSYCSTORE_message_get (h, &channel_pub_key, &slave_pub_key, + message_id, message_id, 0, "", + &fragment_result, + &message_get_result, fcls); +} + + +static void +fragment_get_latest_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + struct FragmentClosure *fcls = cls; + op = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "fragment_get_latest:\t%ld\n", (long int) result); + GNUNET_assert (0 < result && fcls->n == fcls->n_expected); + + fcls->n = 1; + fcls->n_expected = 2; + op = GNUNET_PSYCSTORE_message_get_fragment (h, &channel_pub_key, &slave_pub_key, + GNUNET_ntohll (fcls->msg[1]->message_id), + GNUNET_ntohll (fcls->msg[1]->fragment_offset), + &fragment_result, + &message_get_fragment_result, fcls); +} + + +static void +fragment_get_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + struct FragmentClosure *fcls = cls; + op = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "fragment_get:\t%d\n", + (int) result); + GNUNET_assert (0 < result && fcls->n == fcls->n_expected); + + fcls->n = 0; + fcls->n_expected = 3; + op = GNUNET_PSYCSTORE_fragment_get_latest (h, &channel_pub_key, + &slave_pub_key, fcls->n_expected, + &fragment_result, + &fragment_get_latest_result, fcls); +} + + +static void +fragment_store_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + op = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "fragment_store:\t%ld\n", (long int) result); + GNUNET_assert (GNUNET_OK == result); + + if ((intptr_t) cls == GNUNET_YES) + { /* last fragment */ + fcls.n = 0; + fcls.n_expected = 1; + uint64_t fragment_id = GNUNET_ntohll (fcls.msg[0]->fragment_id); + op = GNUNET_PSYCSTORE_fragment_get (h, &channel_pub_key, &slave_pub_key, + fragment_id, fragment_id, + &fragment_result, + &fragment_get_result, &fcls); + } +} + + +static void +fragment_store () +{ + struct GNUNET_MULTICAST_MessageHeader *msg; + fcls.flags[0] = GNUNET_PSYCSTORE_MESSAGE_STATE; + fcls.msg[0] = msg = GNUNET_malloc (sizeof (*msg) + sizeof (channel_pub_key)); + GNUNET_assert (msg != NULL); + + msg->header.type = htons (GNUNET_MESSAGE_TYPE_MULTICAST_MESSAGE); + msg->header.size = htons (sizeof (*msg) + sizeof (channel_pub_key)); + + msg->hop_counter = htonl (9); + msg->fragment_id = GNUNET_htonll (INT64_MAX - 8); + msg->fragment_offset = GNUNET_htonll (0); + msg->message_id = GNUNET_htonll (INT64_MAX - 10); + msg->group_generation = GNUNET_htonll (INT64_MAX - 3); + msg->flags = htonl (GNUNET_MULTICAST_MESSAGE_LAST_FRAGMENT); + + GNUNET_memcpy (&msg[1], &channel_pub_key, sizeof (channel_pub_key)); + + msg->purpose.size = htonl (ntohs (msg->header.size) + - sizeof (msg->header) + - sizeof (msg->hop_counter) + - sizeof (msg->signature)); + msg->purpose.purpose = htonl (234); + GNUNET_assert (GNUNET_OK == GNUNET_CRYPTO_eddsa_sign (channel_key, &msg->purpose, + &msg->signature)); + + op = GNUNET_PSYCSTORE_fragment_store (h, &channel_pub_key, msg, fcls.flags[0], + &fragment_store_result, GNUNET_NO); + + fcls.flags[1] = GNUNET_PSYCSTORE_MESSAGE_STATE_APPLIED; + fcls.msg[1] = msg = GNUNET_malloc (sizeof (*msg) + sizeof (channel_pub_key)); + GNUNET_memcpy (msg, fcls.msg[0], sizeof (*msg) + sizeof (channel_pub_key)); + msg->fragment_id = GNUNET_htonll (INT64_MAX - 4); + msg->fragment_offset = GNUNET_htonll (1024); + + op = GNUNET_PSYCSTORE_fragment_store (h, &channel_pub_key, msg, fcls.flags[1], + &fragment_store_result, GNUNET_NO); + + fcls.flags[2] = GNUNET_PSYCSTORE_MESSAGE_STATE_HASH; + fcls.msg[2] = msg = GNUNET_malloc (sizeof (*msg) + sizeof (channel_pub_key)); + GNUNET_memcpy (msg, fcls.msg[1], sizeof (*msg) + sizeof (channel_pub_key)); + msg->fragment_id = GNUNET_htonll (INT64_MAX); + msg->fragment_offset = GNUNET_htonll (16384); + + op = GNUNET_PSYCSTORE_fragment_store (h, &channel_pub_key, msg, fcls.flags[2], + &fragment_store_result, (void *) GNUNET_YES); +} + + +static void +membership_test_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + op = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "membership_test:\t%ld\n", (long int) result); + GNUNET_assert (GNUNET_OK == result); + + fragment_store (); +} + + +static void +membership_store_result (void *cls, int64_t result, + const char *err_msg, uint16_t err_msg_size) +{ + op = NULL; + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, "membership_store:\t%ld\n", (long int) result); + GNUNET_assert (GNUNET_OK == result); + + op = GNUNET_PSYCSTORE_membership_test (h, &channel_pub_key, &slave_pub_key, + INT64_MAX - 10, 2, + &membership_test_result, NULL); +} + + +/** + * Main function of the test, run from scheduler. + * + * @param cls NULL + * @param cfg configuration we use (also to connect to PSYCstore service) + * @param peer handle to access more of the peer (not used) + */ +static void +#if DEBUG_TEST_PSYCSTORE +run (void *cls, char *const *args, const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *cfg) +#else +run (void *cls, + const struct GNUNET_CONFIGURATION_Handle *cfg, + struct GNUNET_TESTING_Peer *peer) +#endif +{ + end_badly_task = GNUNET_SCHEDULER_add_delayed (TIMEOUT, &end_badly, NULL); + + h = GNUNET_PSYCSTORE_connect (cfg); + GNUNET_assert (NULL != h); + + channel_key = GNUNET_CRYPTO_eddsa_key_create (); + slave_key = GNUNET_CRYPTO_ecdsa_key_create (); + + GNUNET_CRYPTO_eddsa_key_get_public (channel_key, &channel_pub_key); + GNUNET_CRYPTO_ecdsa_key_get_public (slave_key, &slave_pub_key); + + op = GNUNET_PSYCSTORE_membership_store (h, &channel_pub_key, &slave_pub_key, + GNUNET_YES, INT64_MAX - 5, + INT64_MAX - 10, 2, + &membership_store_result, NULL); +} + + +int +main (int argc, char *argv[]) +{ + res = 1; +#if DEBUG_TEST_PSYCSTORE + const struct GNUNET_GETOPT_CommandLineOption opts[] = { + GNUNET_GETOPT_OPTION_END + }; + if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv, "test-psycstore", + "test-psycstore [options]", + opts, &run, NULL)) + return 1; +#else + if (0 != GNUNET_TESTING_service_run ("test-psycstore", "psycstore", + "test_psycstore.conf", &run, NULL)) + return 1; +#endif + return res; +} + +/* end of test_psycstore.c */ diff --git a/src/psycstore/test_psycstore.conf b/src/psycstore/test_psycstore.conf new file mode 100644 index 0000000..fa7c2d0 --- /dev/null +++ b/src/psycstore/test_psycstore.conf @@ -0,0 +1,8 @@ +[PATHS] +GNUNET_TEST_HOME = $GNUNET_TMP/test-gnunet-psycstore/ + +[psycstore] +DATABASE = sqlite + +[psycstore-sqlite] +FILENAME = $GNUNET_TEST_HOME/psycstore/sqlite.db diff --git a/src/psycutil/.gitignore b/src/psycutil/.gitignore new file mode 100644 index 0000000..03d8197 --- /dev/null +++ b/src/psycutil/.gitignore @@ -0,0 +1 @@ +test_psyc_env diff --git a/src/psycutil/Makefile.am b/src/psycutil/Makefile.am new file mode 100644 index 0000000..2732c3a --- /dev/null +++ b/src/psycutil/Makefile.am @@ -0,0 +1,45 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +pkgcfgdir= $(pkgdatadir)/config.d/ + +libexecdir= $(pkglibdir)/libexec/ + +if MINGW + WINFLAGS = -Wl,--no-undefined -Wl,--export-all-symbols +endif + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +lib_LTLIBRARIES = libgnunetpsycutil.la + +libgnunetpsycutil_la_SOURCES = \ + psyc_env.c \ + psyc_message.c \ + psyc_slicer.c +libgnunetpsycutil_la_LIBADD = \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(GN_LIBINTL) $(XLIB) +libgnunetpsycutil_la_LDFLAGS = \ + $(GN_LIB_LDFLAGS) $(WINFLAGS) \ + -version-info 0:0:0 + +if HAVE_TESTING +check_PROGRAMS = \ + test_psyc_env +endif + +if ENABLE_TEST_RUN +AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@};export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME; +TESTS = $(check_PROGRAMS) +endif + +test_psyc_env_SOURCES = \ + test_psyc_env.c +test_psyc_env_LDADD = \ + libgnunetpsycutil.la \ + $(top_builddir)/src/testing/libgnunettesting.la \ + $(top_builddir)/src/util/libgnunetutil.la diff --git a/src/psycutil/psyc_env.c b/src/psycutil/psyc_env.c new file mode 100644 index 0000000..fc4b8eb --- /dev/null +++ b/src/psycutil/psyc_env.c @@ -0,0 +1,196 @@ +/* + * This file is part of GNUnet. + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @author Gabor X Toth + * + * @file + * Library providing operations for the @e environment of + * PSYC and Social messages. + */ + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_psyc_env.h" + +/** + * Environment for a message. + * + * Contains modifiers. + */ +struct GNUNET_PSYC_Environment +{ + struct GNUNET_PSYC_Modifier *mod_head; + struct GNUNET_PSYC_Modifier *mod_tail; + size_t mod_count; +}; + + +/** + * Create an environment. + * + * @return A newly allocated environment. + */ +struct GNUNET_PSYC_Environment * +GNUNET_PSYC_env_create () +{ + return GNUNET_new (struct GNUNET_PSYC_Environment); +} + + +/** + * Add a modifier to the environment. + * + * @param env The environment. + * @param oper Operation to perform. + * @param name Name of the variable. + * @param value Value of the variable. + * @param value_size Size of @a value. + */ +void +GNUNET_PSYC_env_add (struct GNUNET_PSYC_Environment *env, + enum GNUNET_PSYC_Operator oper, const char *name, + const void *value, size_t value_size) +{ + struct GNUNET_PSYC_Modifier *mod = GNUNET_new (struct GNUNET_PSYC_Modifier); + mod->oper = oper; + mod->name = name; + mod->value = value; + mod->value_size = value_size; + GNUNET_CONTAINER_DLL_insert_tail (env->mod_head, env->mod_tail, mod); + env->mod_count++; +} + + +/** + * Get the first modifier of the environment. + */ +struct GNUNET_PSYC_Modifier * +GNUNET_PSYC_env_head (const struct GNUNET_PSYC_Environment *env) +{ + return env->mod_head; +} + + +/** + * Get the last modifier of the environment. + */ +struct GNUNET_PSYC_Modifier * +GNUNET_PSYC_env_tail (const struct GNUNET_PSYC_Environment *env) +{ + return env->mod_tail; +} + + +/** + * Remove a modifier from the environment. + */ +void +GNUNET_PSYC_env_remove (struct GNUNET_PSYC_Environment *env, + struct GNUNET_PSYC_Modifier *mod) +{ + GNUNET_CONTAINER_DLL_remove (env->mod_head, env->mod_tail, mod); +} + + +/** + * Get the modifier at the beginning of an environment and remove it. + * + * @param env + * @param oper + * @param name + * @param value + * @param value_size + * + * @return + */ +int +GNUNET_PSYC_env_shift (struct GNUNET_PSYC_Environment *env, + enum GNUNET_PSYC_Operator *oper, const char **name, + const void **value, size_t *value_size) +{ + if (NULL == env->mod_head) + return GNUNET_NO; + + struct GNUNET_PSYC_Modifier *mod = env->mod_head; + *oper = mod->oper; + *name = mod->name; + *value = mod->value; + *value_size = mod->value_size; + + GNUNET_CONTAINER_DLL_remove (env->mod_head, env->mod_tail, mod); + GNUNET_free (mod); + env->mod_count--; + + return GNUNET_YES; +} + + +/** + * Iterate through all modifiers in the environment. + * + * @param env The environment. + * @param it Iterator. + * @param it_cls Closure for iterator. + */ +void +GNUNET_PSYC_env_iterate (const struct GNUNET_PSYC_Environment *env, + GNUNET_PSYC_Iterator it, void *it_cls) +{ + struct GNUNET_PSYC_Modifier *mod; + for (mod = env->mod_head; NULL != mod; mod = mod->next) + it (it_cls, mod->oper, mod->name, mod->value, mod->value_size); +} + + +/** + * Get the number of modifiers in the environment. + * + * @param env The environment. + * + * @return Number of modifiers. + */ +size_t +GNUNET_PSYC_env_get_count (const struct GNUNET_PSYC_Environment *env) +{ + return env->mod_count; +} + + +/** + * Destroy an environment. + * + * @param env The environment to destroy. + */ +void +GNUNET_PSYC_env_destroy (struct GNUNET_PSYC_Environment *env) +{ + struct GNUNET_PSYC_Modifier *mod, *prev = NULL; + for (mod = env->mod_head; NULL != mod; mod = mod->next) + { + if (NULL != prev) + GNUNET_free (prev); + prev = mod; + } + if (NULL != prev) + GNUNET_free (prev); + + GNUNET_free (env); +} diff --git a/src/psycutil/psyc_message.c b/src/psycutil/psyc_message.c new file mode 100644 index 0000000..a03eff4 --- /dev/null +++ b/src/psycutil/psyc_message.c @@ -0,0 +1,1355 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file psycutil/psyc_message.c + * @brief PSYC utilities; receiving/transmitting/logging PSYC messages. + * @author Gabor X Toth + */ + +#include + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_psyc_util_lib.h" +#include "gnunet_psyc_service.h" + +#define LOG(kind,...) GNUNET_log_from (kind, "psyc-util",__VA_ARGS__) + + +struct GNUNET_PSYC_TransmitHandle +{ + /** + * Client connection to service. + */ + struct GNUNET_MQ_Handle *mq; + + /** + * Message currently being received from the client. + */ + struct GNUNET_MessageHeader *msg; + + /** + * Envelope for @a msg + */ + struct GNUNET_MQ_Envelope *env; + + /** + * Callback to request next modifier from client. + */ + GNUNET_PSYC_TransmitNotifyModifier notify_mod; + + /** + * Closure for the notify callbacks. + */ + void *notify_mod_cls; + + /** + * Callback to request next data fragment from client. + */ + GNUNET_PSYC_TransmitNotifyData notify_data; + + /** + * Closure for the notify callbacks. + */ + void *notify_data_cls; + + /** + * Modifier of the environment that is currently being transmitted. + */ + struct GNUNET_PSYC_Modifier *mod; + + /** + * + */ + const char *mod_value; + + /** + * Number of bytes remaining to be transmitted from the current modifier value. + */ + uint32_t mod_value_remaining; + + /** + * State of the current message being received from client. + */ + enum GNUNET_PSYC_MessageState state; + + /** + * Number of PSYC_TRANSMIT_ACK messages we are still waiting for. + */ + uint8_t acks_pending; + + /** + * Is transmission paused? + */ + uint8_t paused; + + /** + * Are we currently transmitting a message? + */ + uint8_t in_transmit; + + /** + * Notify callback is currently being called. + */ + uint8_t in_notify; + +}; + + + +struct GNUNET_PSYC_ReceiveHandle +{ + /** + * Message callback. + */ + GNUNET_PSYC_MessageCallback message_cb; + + /** + * Message part callback. + */ + GNUNET_PSYC_MessagePartCallback message_part_cb; + + /** + * Closure for the callbacks. + */ + void *cb_cls; + + /** + * ID of the message being received from the PSYC service. + */ + uint64_t message_id; + + /** + * Public key of the slave from which a message is being received. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey slave_pub_key; + + /** + * State of the currently being received message from the PSYC service. + */ + enum GNUNET_PSYC_MessageState state; + + /** + * Flags for the currently being received message from the PSYC service. + */ + enum GNUNET_PSYC_MessageFlags flags; + + /** + * Expected value size for the modifier being received from the PSYC service. + */ + uint32_t mod_value_size_expected; + + /** + * Actual value size for the modifier being received from the PSYC service. + */ + uint32_t mod_value_size; +}; + + +/**** Messages ****/ + + +/** + * Create a PSYC message. + * + * @param method_name + * PSYC method for the message. + * @param env + * Environment for the message. + * @param data + * Data payload for the message. + * @param data_size + * Size of @a data. + * + * @return Message header with size information, + * followed by the message parts. + */ +struct GNUNET_PSYC_Message * +GNUNET_PSYC_message_create (const char *method_name, + const struct GNUNET_PSYC_Environment *env, + const void *data, + size_t data_size) +{ + struct GNUNET_PSYC_Modifier *mod = NULL; + struct GNUNET_PSYC_MessageMethod *pmeth = NULL; + struct GNUNET_PSYC_MessageModifier *pmod = NULL; + struct GNUNET_MessageHeader *pmsg = NULL; + uint16_t env_size = 0; + if (NULL != env) + { + mod = GNUNET_PSYC_env_head (env); + while (NULL != mod) + { + env_size += sizeof (*pmod) + strlen (mod->name) + 1 + mod->value_size; + mod = mod->next; + } + } + + struct GNUNET_PSYC_Message *msg; + uint16_t method_name_size = strlen (method_name) + 1; + if (method_name_size == 1) + return NULL; + + uint16_t msg_size = sizeof (*msg) /* header */ + + sizeof (*pmeth) + method_name_size /* method */ + + env_size /* modifiers */ + + ((0 < data_size) ? sizeof (*pmsg) + data_size : 0) /* data */ + + sizeof (*pmsg); /* end of message */ + msg = GNUNET_malloc (msg_size); + msg->header.size = htons (msg_size); + msg->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE); /* FIXME */ + + pmeth = (struct GNUNET_PSYC_MessageMethod *) &msg[1]; + pmeth->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD); + pmeth->header.size = htons (sizeof (*pmeth) + method_name_size); + GNUNET_memcpy (&pmeth[1], method_name, method_name_size); + + uint16_t p = sizeof (*msg) + sizeof (*pmeth) + method_name_size; + if (NULL != env) + { + mod = GNUNET_PSYC_env_head (env); + while (NULL != mod) + { + uint16_t mod_name_size = strlen (mod->name) + 1; + pmod = (struct GNUNET_PSYC_MessageModifier *) ((char *) msg + p); + pmod->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER); + pmod->header.size = sizeof (*pmod) + mod_name_size + mod->value_size; + p += pmod->header.size; + pmod->header.size = htons (pmod->header.size); + + pmod->oper = mod->oper; + pmod->name_size = htons (mod_name_size); + pmod->value_size = htonl (mod->value_size); + + GNUNET_memcpy (&pmod[1], mod->name, mod_name_size); + if (0 < mod->value_size) + GNUNET_memcpy ((char *) &pmod[1] + mod_name_size, mod->value, mod->value_size); + + mod = mod->next; + } + } + + if (0 < data_size) + { + pmsg = (struct GNUNET_MessageHeader *) ((char *) msg + p); + pmsg->size = sizeof (*pmsg) + data_size; + p += pmsg->size; + pmsg->size = htons (pmsg->size); + pmsg->type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_DATA); + GNUNET_memcpy (&pmsg[1], data, data_size); + } + + pmsg = (struct GNUNET_MessageHeader *) ((char *) msg + p); + pmsg->size = htons (sizeof (*pmsg)); + pmsg->type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END); + + GNUNET_assert (p + sizeof (*pmsg) == msg_size); + return msg; +} + + +void +GNUNET_PSYC_log_message (enum GNUNET_ErrorType kind, + const struct GNUNET_MessageHeader *msg) +{ + uint16_t size = ntohs (msg->size); + uint16_t type = ntohs (msg->type); + + GNUNET_log (kind, + "Message of type %d and size %u:\n", + type, + size); + switch (type) + { + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE: + { + const struct GNUNET_PSYC_MessageHeader *pmsg + = (const struct GNUNET_PSYC_MessageHeader *) msg; + GNUNET_log (kind, + "\tID: %" PRIu64 "\tflags: %x" PRIu32 "\n", + GNUNET_ntohll (pmsg->message_id), + ntohl (pmsg->flags)); + break; + } + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD: + { + const struct GNUNET_PSYC_MessageMethod *meth + = (const struct GNUNET_PSYC_MessageMethod *) msg; + GNUNET_log (kind, + "\t%.*s\n", + (int) (size - sizeof (*meth)), + (const char *) &meth[1]); + break; + } + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER: + { + const struct GNUNET_PSYC_MessageModifier *mod + = (const struct GNUNET_PSYC_MessageModifier *) msg; + uint16_t name_size = ntohs (mod->name_size); + char oper = ' ' < mod->oper ? mod->oper : ' '; + GNUNET_log (kind, + "\t%c%.*s\t%.*s\n", + oper, + (int) name_size, + (const char *) &mod[1], + (int) (size - sizeof (*mod) - name_size), + ((const char *) &mod[1]) + name_size); + break; + } + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT: + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_DATA: + GNUNET_log (kind, + "\t%.*s\n", + (int) (size - sizeof (*msg)), + (const char *) &msg[1]); + break; + } +} + + +/**** Transmitting messages ****/ + + +/** + * Create a transmission handle. + */ +struct GNUNET_PSYC_TransmitHandle * +GNUNET_PSYC_transmit_create (struct GNUNET_MQ_Handle *mq) +{ + struct GNUNET_PSYC_TransmitHandle *tmit = GNUNET_new (struct GNUNET_PSYC_TransmitHandle); + + tmit->mq = mq; + return tmit; +} + + +/** + * Destroy a transmission handle. + */ +void +GNUNET_PSYC_transmit_destroy (struct GNUNET_PSYC_TransmitHandle *tmit) +{ + GNUNET_free (tmit); +} + + +/** + * Queue a message part for transmission. + * + * The message part is added to the current message buffer. + * When this buffer is full, it is added to the transmission queue. + * + * @param tmit + * Transmission handle. + * @param msg + * Message part, or NULL. + * @param tmit_now + * Transmit message now, or wait for buffer to fill up? + * #GNUNET_YES or #GNUNET_NO. + */ +static void +transmit_queue_insert (struct GNUNET_PSYC_TransmitHandle *tmit, + const struct GNUNET_MessageHeader *msg, + uint8_t tmit_now) +{ + uint16_t size = (NULL != msg) ? ntohs (msg->size) : 0; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Queueing message part of type %u and size %u (tmit_now: %u)).\n", + NULL != msg ? ntohs (msg->type) : 0, size, tmit_now); + + if (NULL != tmit->msg) + { + if (NULL == msg + || GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD < tmit->msg->size + size) + { + /* End of message or buffer is full, add it to transmission queue + * and start with empty buffer */ + tmit->msg->size = htons (tmit->msg->size); + GNUNET_MQ_send (tmit->mq, tmit->env); + tmit->env = NULL; + tmit->msg = NULL; + tmit->acks_pending++; + } + else + { + /* Message fits in current buffer, append */ + GNUNET_memcpy ((char *) tmit->msg + tmit->msg->size, msg, size); + tmit->msg->size += size; + } + } + + if (NULL == tmit->msg && NULL != msg) + { + /* Empty buffer, copy over message. */ + tmit->env = GNUNET_MQ_msg_extra (tmit->msg, + GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD, + GNUNET_MESSAGE_TYPE_PSYC_MESSAGE); + /* store current message size in host byte order + * then later switch it to network byte order before sending */ + tmit->msg->size = sizeof (*tmit->msg) + size; + + GNUNET_memcpy (&tmit->msg[1], msg, size); + } + + if (NULL != tmit->msg + && (GNUNET_YES == tmit_now + || (GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD + < tmit->msg->size + sizeof (struct GNUNET_MessageHeader)))) + { + /* End of message or buffer is full, add it to transmission queue. */ + tmit->msg->size = htons (tmit->msg->size); + GNUNET_MQ_send (tmit->mq, tmit->env); + tmit->env = NULL; + tmit->msg = NULL; + tmit->acks_pending++; + } +} + + +/** + * Request data from client to transmit. + * + * @param tmit Transmission handle. + */ +static void +transmit_data (struct GNUNET_PSYC_TransmitHandle *tmit) +{ + int notify_ret = GNUNET_YES; + uint16_t data_size = 0; + char data[GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD] = ""; + struct GNUNET_MessageHeader *msg = (struct GNUNET_MessageHeader *) data; + msg->type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_DATA); + + if (NULL != tmit->notify_data) + { + data_size = GNUNET_PSYC_DATA_MAX_PAYLOAD; + tmit->in_notify = GNUNET_YES; + notify_ret = tmit->notify_data (tmit->notify_data_cls, &data_size, &msg[1]); + tmit->in_notify = GNUNET_NO; + } + LOG (GNUNET_ERROR_TYPE_DEBUG, + "transmit_data (ret: %d, size: %u): %.*s\n", + notify_ret, data_size, data_size, &msg[1]); + switch (notify_ret) + { + case GNUNET_NO: + if (0 == data_size) + { + /* Transmission paused, nothing to send. */ + tmit->paused = GNUNET_YES; + return; + } + break; + + case GNUNET_YES: + tmit->state = GNUNET_PSYC_MESSAGE_STATE_END; + break; + + default: + LOG (GNUNET_ERROR_TYPE_ERROR, + "TransmitNotifyData callback returned error when requesting data.\n"); + + tmit->state = GNUNET_PSYC_MESSAGE_STATE_CANCEL; + msg->type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL); + msg->size = htons (sizeof (*msg)); + transmit_queue_insert (tmit, msg, GNUNET_YES); + tmit->in_transmit = GNUNET_NO; + return; + } + + if (0 < data_size) + { + GNUNET_assert (data_size <= GNUNET_PSYC_DATA_MAX_PAYLOAD); + msg->size = htons (sizeof (*msg) + data_size); + transmit_queue_insert (tmit, msg, !notify_ret); + } + + /* End of message. */ + if (GNUNET_YES == notify_ret) + { + msg->type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END); + msg->size = htons (sizeof (*msg)); + transmit_queue_insert (tmit, msg, GNUNET_YES); + /* FIXME: wait for ACK before setting in_transmit to no */ + tmit->in_transmit = GNUNET_NO; + } +} + + +/** + * Request a modifier from a client to transmit. + * + * @param tmit Transmission handle. + */ +static void +transmit_mod (struct GNUNET_PSYC_TransmitHandle *tmit) +{ + uint16_t max_data_size = 0; + uint16_t data_size = 0; + char data[GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD] = ""; + struct GNUNET_MessageHeader *msg = (struct GNUNET_MessageHeader *) data; + int notify_ret = GNUNET_YES; + + switch (tmit->state) + { + case GNUNET_PSYC_MESSAGE_STATE_MODIFIER: + { + struct GNUNET_PSYC_MessageModifier *mod + = (struct GNUNET_PSYC_MessageModifier *) msg; + msg->type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER); + msg->size = sizeof (struct GNUNET_PSYC_MessageModifier); + + if (NULL != tmit->notify_mod) + { + max_data_size = GNUNET_PSYC_MODIFIER_MAX_PAYLOAD; + data_size = max_data_size; + tmit->in_notify = GNUNET_YES; + notify_ret = tmit->notify_mod (tmit->notify_mod_cls, &data_size, &mod[1], + &mod->oper, &mod->value_size); + tmit->in_notify = GNUNET_NO; + } + + mod->name_size = strnlen ((char *) &mod[1], data_size) + 1; + LOG (GNUNET_ERROR_TYPE_DEBUG, + "transmit_mod (ret: %d, size: %u + %u): %.*s\n", + notify_ret, mod->name_size, mod->value_size, data_size, &mod[1]); + if (mod->name_size < data_size) + { + tmit->mod_value_remaining + = mod->value_size - (data_size - mod->name_size); + mod->value_size = htonl (mod->value_size); + mod->name_size = htons (mod->name_size); + } + else if (0 < data_size) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "Got invalid modifier name.\n"); + notify_ret = GNUNET_SYSERR; + } + break; + } + case GNUNET_PSYC_MESSAGE_STATE_MOD_CONT: + { + msg->type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT); + msg->size = sizeof (struct GNUNET_MessageHeader); + + if (NULL != tmit->notify_mod) + { + max_data_size = GNUNET_PSYC_MOD_CONT_MAX_PAYLOAD; + data_size = max_data_size; + tmit->in_notify = GNUNET_YES; + notify_ret = tmit->notify_mod (tmit->notify_mod_cls, + &data_size, &msg[1], NULL, NULL); + tmit->in_notify = GNUNET_NO; + } + tmit->mod_value_remaining -= data_size; + LOG (GNUNET_ERROR_TYPE_DEBUG, + "transmit_mod (ret: %d, size: %u): %.*s\n", + notify_ret, data_size, data_size, &msg[1]); + break; + } + default: + GNUNET_assert (0); + } + + switch (notify_ret) + { + case GNUNET_NO: + if (0 == data_size) + { /* Transmission paused, nothing to send. */ + tmit->paused = GNUNET_YES; + return; + } + tmit->state + = (0 == tmit->mod_value_remaining) + ? GNUNET_PSYC_MESSAGE_STATE_MODIFIER + : GNUNET_PSYC_MESSAGE_STATE_MOD_CONT; + break; + + case GNUNET_YES: /* End of modifiers. */ + GNUNET_assert (0 == tmit->mod_value_remaining); + break; + + default: + LOG (GNUNET_ERROR_TYPE_ERROR, + "TransmitNotifyModifier callback returned with error.\n"); + + tmit->state = GNUNET_PSYC_MESSAGE_STATE_CANCEL; + msg->type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL); + msg->size = htons (sizeof (*msg)); + transmit_queue_insert (tmit, msg, GNUNET_YES); + tmit->in_transmit = GNUNET_NO; + return; + } + + if (0 < data_size) + { + GNUNET_assert (data_size <= max_data_size); + msg->size = htons (msg->size + data_size); + transmit_queue_insert (tmit, msg, GNUNET_NO); + } + + if (GNUNET_YES == notify_ret) + { + tmit->state = GNUNET_PSYC_MESSAGE_STATE_DATA; + if (0 == tmit->acks_pending) + transmit_data (tmit); + } + else + { + transmit_mod (tmit); + } +} + + +int +transmit_notify_env (void *cls, uint16_t *data_size, void *data, uint8_t *oper, + uint32_t *full_value_size) + +{ + struct GNUNET_PSYC_TransmitHandle *tmit = cls; + uint16_t name_size = 0; + uint32_t value_size = 0; + const char *value = NULL; + + if (NULL != oper) + { /* New modifier */ + if (NULL != tmit->mod) + tmit->mod = tmit->mod->next; + if (NULL == tmit->mod) + { /* No more modifiers, continue with data */ + *data_size = 0; + return GNUNET_YES; + } + + GNUNET_assert (tmit->mod->value_size < UINT32_MAX); + *full_value_size = tmit->mod->value_size; + *oper = tmit->mod->oper; + name_size = strlen (tmit->mod->name) + 1; + + if (name_size + tmit->mod->value_size <= *data_size) + { + value_size = tmit->mod->value_size; + *data_size = name_size + value_size; + } + else /* full modifier does not fit in data, continuation needed */ + { + value_size = *data_size - name_size; + tmit->mod_value = tmit->mod->value + value_size; + } + + GNUNET_memcpy (data, tmit->mod->name, name_size); + GNUNET_memcpy ((char *)data + name_size, tmit->mod->value, value_size); + return GNUNET_NO; + } + else + { /* Modifier continuation */ + GNUNET_assert (NULL != tmit->mod_value && 0 < tmit->mod_value_remaining); + value = tmit->mod_value; + if (tmit->mod_value_remaining <= *data_size) + { + value_size = tmit->mod_value_remaining; + tmit->mod_value = NULL; + } + else + { + value_size = *data_size; + tmit->mod_value += value_size; + } + + if (*data_size < value_size) + { + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Value in environment larger than buffer: %u < %zu\n", + *data_size, value_size); + *data_size = 0; + return GNUNET_NO; + } + + *data_size = value_size; + GNUNET_memcpy (data, value, value_size); + return (NULL == tmit->mod_value) ? GNUNET_YES : GNUNET_NO; + } +} + + +/** + * Transmit a message. + * + * @param tmit + * Transmission handle. + * @param method_name + * Which method should be invoked. + * @param env + * Environment for the message. + * Should stay available until the first call to notify_data. + * Can be NULL if there are no modifiers or @a notify_mod is + * provided instead. + * @param notify_mod + * Function to call to obtain modifiers. + * Can be NULL if there are no modifiers or @a env is provided instead. + * @param notify_data + * Function to call to obtain fragments of the data. + * @param notify_cls + * Closure for @a notify_mod and @a notify_data. + * @param flags + * Flags for the message being transmitted. + * + * @return #GNUNET_OK if the transmission was started. + * #GNUNET_SYSERR if another transmission is already going on. + */ +int +GNUNET_PSYC_transmit_message (struct GNUNET_PSYC_TransmitHandle *tmit, + const char *method_name, + const struct GNUNET_PSYC_Environment *env, + GNUNET_PSYC_TransmitNotifyModifier notify_mod, + GNUNET_PSYC_TransmitNotifyData notify_data, + void *notify_cls, + uint32_t flags) +{ + if (GNUNET_NO != tmit->in_transmit) + return GNUNET_SYSERR; + tmit->in_transmit = GNUNET_YES; + + size_t size = strlen (method_name) + 1; + struct GNUNET_PSYC_MessageMethod *pmeth; + + tmit->env = GNUNET_MQ_msg_extra (tmit->msg, + GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD, + GNUNET_MESSAGE_TYPE_PSYC_MESSAGE); + /* store current message size in host byte order + * then later switch it to network byte order before sending */ + tmit->msg->size = sizeof (*tmit->msg) + sizeof (*pmeth) + size; + + if (NULL != notify_mod) + { + tmit->notify_mod = notify_mod; + tmit->notify_mod_cls = notify_cls; + } + else + { + tmit->notify_mod = &transmit_notify_env; + tmit->notify_mod_cls = tmit; + if (NULL != env) + { + struct GNUNET_PSYC_Modifier mod = {}; + mod.next = GNUNET_PSYC_env_head (env); + tmit->mod = &mod; + + struct GNUNET_PSYC_Modifier *m = tmit->mod; + while (NULL != (m = m->next)) + { + if (m->oper != GNUNET_PSYC_OP_SET) + flags |= GNUNET_PSYC_MASTER_TRANSMIT_STATE_MODIFY; + } + } + else + { + tmit->mod = NULL; + } + } + + pmeth = (struct GNUNET_PSYC_MessageMethod *) &tmit->msg[1]; + pmeth->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD); + pmeth->header.size = htons (sizeof (*pmeth) + size); + pmeth->flags = htonl (flags); + GNUNET_memcpy (&pmeth[1], method_name, size); + + tmit->state = GNUNET_PSYC_MESSAGE_STATE_MODIFIER; + tmit->notify_data = notify_data; + tmit->notify_data_cls = notify_cls; + + transmit_mod (tmit); + return GNUNET_OK; +} + + +/** + * Resume transmission. + * + * @param tmit Transmission handle. + */ +void +GNUNET_PSYC_transmit_resume (struct GNUNET_PSYC_TransmitHandle *tmit) +{ + if (GNUNET_YES != tmit->in_transmit || GNUNET_NO != tmit->in_notify) + return; + + if (0 == tmit->acks_pending) + { + tmit->paused = GNUNET_NO; + transmit_data (tmit); + } +} + + +/** + * Abort transmission request. + * + * @param tmit Transmission handle. + */ +void +GNUNET_PSYC_transmit_cancel (struct GNUNET_PSYC_TransmitHandle *tmit) +{ + if (GNUNET_NO == tmit->in_transmit) + return; + + tmit->state = GNUNET_PSYC_MESSAGE_STATE_CANCEL; + tmit->in_transmit = GNUNET_NO; + tmit->paused = GNUNET_NO; + + /* FIXME */ + struct GNUNET_MessageHeader msg; + msg.type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL); + msg.size = htons (sizeof (msg)); + transmit_queue_insert (tmit, &msg, GNUNET_YES); +} + + +/** + * Got acknowledgement of a transmitted message part, continue transmission. + * + * @param tmit Transmission handle. + */ +void +GNUNET_PSYC_transmit_got_ack (struct GNUNET_PSYC_TransmitHandle *tmit) +{ + if (0 == tmit->acks_pending) + { + LOG (GNUNET_ERROR_TYPE_WARNING, "Ignoring extraneous message ACK\n"); + GNUNET_break (0); + return; + } + tmit->acks_pending--; + + if (GNUNET_YES == tmit->paused) + return; + + switch (tmit->state) + { + case GNUNET_PSYC_MESSAGE_STATE_MODIFIER: + case GNUNET_PSYC_MESSAGE_STATE_MOD_CONT: + transmit_mod (tmit); + break; + + case GNUNET_PSYC_MESSAGE_STATE_DATA: + transmit_data (tmit); + break; + + case GNUNET_PSYC_MESSAGE_STATE_END: + case GNUNET_PSYC_MESSAGE_STATE_CANCEL: + break; + + default: + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Ignoring message ACK in state %u.\n", tmit->state); + } +} + + +/**** Receiving messages ****/ + + +/** + * Create handle for receiving messages. + */ +struct GNUNET_PSYC_ReceiveHandle * +GNUNET_PSYC_receive_create (GNUNET_PSYC_MessageCallback message_cb, + GNUNET_PSYC_MessagePartCallback message_part_cb, + void *cb_cls) +{ + struct GNUNET_PSYC_ReceiveHandle *recv = GNUNET_malloc (sizeof (*recv)); + recv->message_cb = message_cb; + recv->message_part_cb = message_part_cb; + recv->cb_cls = cb_cls; + return recv; +} + + +/** + * Destroy handle for receiving messages. + */ +void +GNUNET_PSYC_receive_destroy (struct GNUNET_PSYC_ReceiveHandle *recv) +{ + GNUNET_free (recv); +} + + +/** + * Reset stored data related to the last received message. + */ +void +GNUNET_PSYC_receive_reset (struct GNUNET_PSYC_ReceiveHandle *recv) +{ + recv->state = GNUNET_PSYC_MESSAGE_STATE_START; + recv->flags = 0; + recv->message_id = 0; + recv->mod_value_size = 0; + recv->mod_value_size_expected = 0; +} + + +static void +recv_error (struct GNUNET_PSYC_ReceiveHandle *recv) +{ + if (NULL != recv->message_part_cb) + recv->message_part_cb (recv->cb_cls, NULL, NULL); + + if (NULL != recv->message_cb) + recv->message_cb (recv->cb_cls, NULL); + + GNUNET_PSYC_receive_reset (recv); +} + + +/** + * Handle incoming PSYC message. + * + * @param recv Receive handle. + * @param msg The message. + * + * @return #GNUNET_OK on success, + * #GNUNET_SYSERR on receive error. + */ +int +GNUNET_PSYC_receive_message (struct GNUNET_PSYC_ReceiveHandle *recv, + const struct GNUNET_PSYC_MessageHeader *msg) +{ + uint16_t size = ntohs (msg->header.size); + uint32_t flags = ntohl (msg->flags); + + GNUNET_PSYC_log_message (GNUNET_ERROR_TYPE_DEBUG, + (struct GNUNET_MessageHeader *) msg); + + if (GNUNET_PSYC_MESSAGE_STATE_START == recv->state) + { + recv->message_id = GNUNET_ntohll (msg->message_id); + recv->flags = flags; + recv->slave_pub_key = msg->slave_pub_key; + recv->mod_value_size = 0; + recv->mod_value_size_expected = 0; + } + else if (GNUNET_ntohll (msg->message_id) != recv->message_id) + { + // FIXME + LOG (GNUNET_ERROR_TYPE_WARNING, + "Unexpected message ID. Got: %" PRIu64 ", expected: %" PRIu64 "\n", + GNUNET_ntohll (msg->message_id), recv->message_id); + GNUNET_break_op (0); + recv_error (recv); + return GNUNET_SYSERR; + } + else if (flags != recv->flags) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + "Unexpected message flags. Got: %lu, expected: %lu\n", + flags, recv->flags); + GNUNET_break_op (0); + recv_error (recv); + return GNUNET_SYSERR; + } + + uint16_t pos = 0, psize = 0, ptype, size_eq, size_min; + + for (pos = 0; sizeof (*msg) + pos < size; pos += psize) + { + const struct GNUNET_MessageHeader *pmsg + = (const struct GNUNET_MessageHeader *) ((char *) &msg[1] + pos); + psize = ntohs (pmsg->size); + ptype = ntohs (pmsg->type); + size_eq = size_min = 0; + + if (psize < sizeof (*pmsg) || sizeof (*msg) + pos + psize > size) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Dropping message of type %u with invalid size %u.\n", + ptype, psize); + recv_error (recv); + return GNUNET_SYSERR; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received message part of type %u and size %u from PSYC.\n", + ptype, psize); + GNUNET_PSYC_log_message (GNUNET_ERROR_TYPE_DEBUG, pmsg); + + switch (ptype) + { + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD: + size_min = sizeof (struct GNUNET_PSYC_MessageMethod); + break; + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER: + size_min = sizeof (struct GNUNET_PSYC_MessageModifier); + break; + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT: + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_DATA: + size_min = sizeof (struct GNUNET_MessageHeader); + break; + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END: + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL: + size_eq = sizeof (struct GNUNET_MessageHeader); + break; + default: + GNUNET_break_op (0); + recv_error (recv); + return GNUNET_SYSERR; + } + + if (! ((0 < size_eq && psize == size_eq) + || (0 < size_min && size_min <= psize))) + { + GNUNET_break_op (0); + recv_error (recv); + return GNUNET_SYSERR; + } + + switch (ptype) + { + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD: + { + struct GNUNET_PSYC_MessageMethod *meth + = (struct GNUNET_PSYC_MessageMethod *) pmsg; + + if (GNUNET_PSYC_MESSAGE_STATE_START != recv->state) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + "Dropping out of order message method (%u).\n", + recv->state); + /* It is normal to receive an incomplete message right after connecting, + * but should not happen later. + * FIXME: add a check for this condition. + */ + GNUNET_break_op (0); + recv_error (recv); + return GNUNET_SYSERR; + } + + if ('\0' != *((char *) meth + psize - 1)) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + "Dropping message with malformed method. " + "Message ID: %" PRIu64 "\n", recv->message_id); + GNUNET_break_op (0); + recv_error (recv); + return GNUNET_SYSERR; + } + recv->state = GNUNET_PSYC_MESSAGE_STATE_METHOD; + break; + } + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER: + { + if (!(GNUNET_PSYC_MESSAGE_STATE_METHOD == recv->state + || GNUNET_PSYC_MESSAGE_STATE_MODIFIER == recv->state + || GNUNET_PSYC_MESSAGE_STATE_MOD_CONT == recv->state)) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + "Dropping out of order message modifier (%u).\n", + recv->state); + GNUNET_break_op (0); + recv_error (recv); + return GNUNET_SYSERR; + } + + struct GNUNET_PSYC_MessageModifier *mod + = (struct GNUNET_PSYC_MessageModifier *) pmsg; + + uint16_t name_size = ntohs (mod->name_size); + recv->mod_value_size_expected = ntohl (mod->value_size); + recv->mod_value_size = psize - sizeof (*mod) - name_size; + + if (psize < sizeof (*mod) + name_size + || '\0' != *((char *) &mod[1] + name_size - 1) + || recv->mod_value_size_expected < recv->mod_value_size) + { + LOG (GNUNET_ERROR_TYPE_WARNING, "Dropping malformed modifier.\n"); + GNUNET_break_op (0); + recv_error (recv); + return GNUNET_SYSERR; + } + recv->state = GNUNET_PSYC_MESSAGE_STATE_MODIFIER; + break; + } + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT: + { + recv->mod_value_size += psize - sizeof (*pmsg); + + if (!(GNUNET_PSYC_MESSAGE_STATE_MODIFIER == recv->state + || GNUNET_PSYC_MESSAGE_STATE_MOD_CONT == recv->state) + || recv->mod_value_size_expected < recv->mod_value_size) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + "Dropping out of order message modifier continuation " + "!(%u == %u || %u == %u) || %lu < %lu.\n", + GNUNET_PSYC_MESSAGE_STATE_MODIFIER, recv->state, + GNUNET_PSYC_MESSAGE_STATE_MOD_CONT, recv->state, + recv->mod_value_size_expected, recv->mod_value_size); + GNUNET_break_op (0); + recv_error (recv); + return GNUNET_SYSERR; + } + break; + } + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_DATA: + { + if (recv->state < GNUNET_PSYC_MESSAGE_STATE_METHOD + || recv->mod_value_size_expected != recv->mod_value_size) + { + LOG (GNUNET_ERROR_TYPE_WARNING, + "Dropping out of order message data fragment " + "(%u < %u || %lu != %lu).\n", + recv->state, GNUNET_PSYC_MESSAGE_STATE_METHOD, + recv->mod_value_size_expected, recv->mod_value_size); + + GNUNET_break_op (0); + recv_error (recv); + return GNUNET_SYSERR; + } + recv->state = GNUNET_PSYC_MESSAGE_STATE_DATA; + break; + } + } + + if (NULL != recv->message_part_cb) + recv->message_part_cb (recv->cb_cls, msg, pmsg); + + switch (ptype) + { + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END: + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL: + GNUNET_PSYC_receive_reset (recv); + break; + } + } + + if (NULL != recv->message_cb) + recv->message_cb (recv->cb_cls, msg); + return GNUNET_OK; +} + + +/** + * Check if @a data contains a series of valid message parts. + * + * @param data_size Size of @a data. + * @param data Data. + * @param[out] first_ptype Type of first message part. + * @param[out] last_ptype Type of last message part. + * + * @return Number of message parts found in @a data. + * or GNUNET_SYSERR if the message contains invalid parts. + */ +int +GNUNET_PSYC_receive_check_parts (uint16_t data_size, const char *data, + uint16_t *first_ptype, uint16_t *last_ptype) +{ + const struct GNUNET_MessageHeader *pmsg; + uint16_t parts = 0, ptype = 0, psize = 0, pos = 0; + if (NULL != first_ptype) + *first_ptype = 0; + if (NULL != last_ptype) + *last_ptype = 0; + + for (pos = 0; pos < data_size; pos += psize, parts++) + { + pmsg = (const struct GNUNET_MessageHeader *) (data + pos); + GNUNET_PSYC_log_message (GNUNET_ERROR_TYPE_DEBUG, pmsg); + psize = ntohs (pmsg->size); + ptype = ntohs (pmsg->type); + if (0 == parts && NULL != first_ptype) + *first_ptype = ptype; + if (NULL != last_ptype + && *last_ptype < GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END) + *last_ptype = ptype; + if (psize < sizeof (*pmsg) + || pos + psize > data_size + || ptype < GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD + || GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL < ptype) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "Invalid message part of type %u and size %u.\n", + ptype, psize); + return GNUNET_SYSERR; + } + /** @todo FIXME: check message part order */ + } + return parts; +} + + +struct ParseMessageClosure +{ + struct GNUNET_PSYC_Environment *env; + const char **method_name; + const void **data; + uint16_t *data_size; + enum GNUNET_PSYC_MessageState msg_state; +}; + + +static void +parse_message_part_cb (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg) +{ + struct ParseMessageClosure *pmc = cls; + if (NULL == pmsg) + { + pmc->msg_state = GNUNET_PSYC_MESSAGE_STATE_ERROR; + return; + } + + switch (ntohs (pmsg->type)) + { + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD: + { + struct GNUNET_PSYC_MessageMethod * + pmeth = (struct GNUNET_PSYC_MessageMethod *) pmsg; + *pmc->method_name = (const char *) &pmeth[1]; + pmc->msg_state = GNUNET_PSYC_MESSAGE_STATE_METHOD; + break; + } + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER: + { + struct GNUNET_PSYC_MessageModifier * + pmod = (struct GNUNET_PSYC_MessageModifier *) pmsg; + + const char *name = (const char *) &pmod[1]; + const void *value = name + ntohs (pmod->name_size); + GNUNET_PSYC_env_add (pmc->env, pmod->oper, name, value, + ntohl (pmod->value_size)); + pmc->msg_state = GNUNET_PSYC_MESSAGE_STATE_MODIFIER; + break; + } + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_DATA: + *pmc->data = &pmsg[1]; + *pmc->data_size = ntohs (pmsg->size) - sizeof (*pmsg); + pmc->msg_state = GNUNET_PSYC_MESSAGE_STATE_DATA; + break; + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END: + pmc->msg_state = GNUNET_PSYC_MESSAGE_STATE_END; + break; + + default: + pmc->msg_state = GNUNET_PSYC_MESSAGE_STATE_ERROR; + } +} + + +/** + * Parse PSYC message. + * + * @param msg + * The PSYC message to parse. + * @param[out] method_name + * Pointer to the method name inside @a pmsg. + * @param env + * The environment for the message with a list of modifiers. + * @param[out] data + * Pointer to data inside @a msg. + * @param[out] data_size + * Size of @data is written here. + * + * @return #GNUNET_OK on success, + * #GNUNET_SYSERR on parse error. + */ +int +GNUNET_PSYC_message_parse (const struct GNUNET_PSYC_MessageHeader *msg, + const char **method_name, + struct GNUNET_PSYC_Environment *env, + const void **data, + uint16_t *data_size) +{ + struct ParseMessageClosure cls; + cls.env = env; + cls.method_name = method_name; + cls.data = data; + cls.data_size = data_size; + + struct GNUNET_PSYC_ReceiveHandle * + recv = GNUNET_PSYC_receive_create (NULL, parse_message_part_cb, &cls); + int ret = GNUNET_PSYC_receive_message (recv, msg); + GNUNET_PSYC_receive_destroy (recv); + + if (GNUNET_OK != ret) + return GNUNET_SYSERR; + + return (GNUNET_PSYC_MESSAGE_STATE_END == cls.msg_state) + ? GNUNET_OK + : GNUNET_NO; +} + + +/** + * Initialize PSYC message header. + */ +void +GNUNET_PSYC_message_header_init (struct GNUNET_PSYC_MessageHeader *pmsg, + const struct GNUNET_MULTICAST_MessageHeader *mmsg, + uint32_t flags) +{ + uint16_t size = ntohs (mmsg->header.size); + uint16_t psize = sizeof (*pmsg) + size - sizeof (*mmsg); + + pmsg->header.size = htons (psize); + pmsg->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE); + pmsg->message_id = mmsg->message_id; + pmsg->fragment_offset = mmsg->fragment_offset; + pmsg->flags = htonl (flags); + + GNUNET_memcpy (&pmsg[1], &mmsg[1], size - sizeof (*mmsg)); +} + + +/** + * Create a new PSYC message header from a multicast message. + */ +struct GNUNET_PSYC_MessageHeader * +GNUNET_PSYC_message_header_create (const struct GNUNET_MULTICAST_MessageHeader *mmsg, + uint32_t flags) +{ + struct GNUNET_PSYC_MessageHeader *pmsg; + uint16_t size = ntohs (mmsg->header.size); + uint16_t psize = sizeof (*pmsg) + size - sizeof (*mmsg); + + pmsg = GNUNET_malloc (psize); + GNUNET_PSYC_message_header_init (pmsg, mmsg, flags); + return pmsg; +} + + +/** + * Create a new PSYC message header from a PSYC message. + */ +struct GNUNET_PSYC_MessageHeader * +GNUNET_PSYC_message_header_create_from_psyc (const struct GNUNET_PSYC_Message *msg) +{ + uint16_t msg_size = ntohs (msg->header.size); + struct GNUNET_PSYC_MessageHeader * + pmsg = GNUNET_malloc (sizeof (*pmsg) + msg_size - sizeof (*msg)); + pmsg->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE); + pmsg->header.size = htons (sizeof (*pmsg) + msg_size - sizeof (*msg)); + GNUNET_memcpy (&pmsg[1], &msg[1], msg_size - sizeof (*msg)); + return pmsg; +} diff --git a/src/psycutil/psyc_slicer.c b/src/psycutil/psyc_slicer.c new file mode 100644 index 0000000..9b25d8a --- /dev/null +++ b/src/psycutil/psyc_slicer.c @@ -0,0 +1,711 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @author Gabor X Toth + * + * @file + * PSYC Slicer API + */ + +#include + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_psyc_util_lib.h" + +#define LOG(kind,...) GNUNET_log_from (kind, "psyc-util-slicer",__VA_ARGS__) + + +/** + * Handle for a try-and-slice instance. + */ +struct GNUNET_PSYC_Slicer +{ + /** + * Method handlers: H(method_name) -> SlicerMethodCallbacks + */ + struct GNUNET_CONTAINER_MultiHashMap *method_handlers; + + /** + * Modifier handlers: H(modifier_name) -> SlicerModifierCallbacks + */ + struct GNUNET_CONTAINER_MultiHashMap *modifier_handlers; + + /** + * Receive handle for incoming messages. + */ + struct GNUNET_PSYC_ReceiveHandle *recv; + + /** + * Currently being processed message. + */ + const struct GNUNET_PSYC_MessageHeader *msg; + + /** + * Currently being processed message part. + */ + const struct GNUNET_MessageHeader *pmsg; + + /** + * ID of currently being received message. + */ + uint64_t message_id; + + /** + * Fragment offset of currently being received message. + */ + uint64_t fragment_offset; + + /** + * Flags of currently being received message. + */ + uint32_t flags; + + /** + * Method name of currently being received message. + */ + char *method_name; + + /** + * Name of currently processed modifier. + */ + char *mod_name; + + /** + * Value of currently processed modifier. + */ + char *mod_value; + + /** + * Public key of the nym the current message originates from. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey nym_pub_key; + + /** + * Size of @a method_name (including terminating \0). + */ + uint16_t method_name_size; + + /** + * Size of @a modifier_name (including terminating \0). + */ + uint16_t mod_name_size; + + /** + * Size of modifier value fragment. + */ + uint16_t mod_value_size; + + /** + * Full size of modifier value. + */ + uint16_t mod_full_value_size; + + /** + * Remaining bytes from the value of the current modifier. + */ + uint16_t mod_value_remaining; + + /** + * Operator of currently processed modifier. + */ + uint8_t mod_oper; +}; + + +/** + * Callbacks for a slicer method handler. + */ +struct SlicerMethodCallbacks +{ + GNUNET_PSYC_MessageCallback msg_cb; + GNUNET_PSYC_MethodCallback method_cb; + GNUNET_PSYC_ModifierCallback modifier_cb; + GNUNET_PSYC_DataCallback data_cb; + GNUNET_PSYC_EndOfMessageCallback eom_cb; + void *cls; +}; + + +struct SlicerMethodRemoveClosure +{ + struct GNUNET_PSYC_Slicer *slicer; + struct SlicerMethodCallbacks rm_cbs; +}; + + +/** + * Callbacks for a slicer method handler. + */ +struct SlicerModifierCallbacks +{ + GNUNET_PSYC_ModifierCallback modifier_cb; + void *cls; +}; + + +struct SlicerModifierRemoveClosure +{ + struct GNUNET_PSYC_Slicer *slicer; + struct SlicerModifierCallbacks rm_cbs; +}; + + +/** + * Call a method handler for an incoming message part. + */ +static int +slicer_method_handler_notify (void *cls, const struct GNUNET_HashCode *key, + void *value) +{ + struct GNUNET_PSYC_Slicer *slicer = cls; + const struct GNUNET_MessageHeader *pmsg = slicer->pmsg; + struct SlicerMethodCallbacks *cbs = value; + + uint16_t ptype = ntohs (pmsg->type); + switch (ptype) + { + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD: + { + if (NULL != cbs->msg_cb) + cbs->msg_cb (cbs->cls, slicer->msg); + if (NULL == cbs->method_cb) + break; + struct GNUNET_PSYC_MessageMethod * + meth = (struct GNUNET_PSYC_MessageMethod *) pmsg; + cbs->method_cb (cbs->cls, slicer->msg, meth, slicer->message_id, + slicer->method_name); + break; + } + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER: + { + if (NULL == cbs->modifier_cb) + break; + struct GNUNET_PSYC_MessageModifier * + mod = (struct GNUNET_PSYC_MessageModifier *) pmsg; + cbs->modifier_cb (cbs->cls, slicer->msg, &mod->header, slicer->message_id, + mod->oper, (const char *) &mod[1], + (const void *) &mod[1] + ntohs (mod->name_size), + ntohs (mod->header.size) - sizeof (*mod) - ntohs (mod->name_size), + ntohs (mod->value_size)); + break; + } + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT: + { + if (NULL == cbs->modifier_cb) + break; + cbs->modifier_cb (cbs->cls, slicer->msg, pmsg, slicer->message_id, + slicer->mod_oper, slicer->mod_name, &pmsg[1], + ntohs (pmsg->size) - sizeof (*pmsg), + slicer->mod_full_value_size); + break; + } + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_DATA: + { + if (NULL == cbs->data_cb) + break; + cbs->data_cb (cbs->cls, slicer->msg, pmsg, slicer->message_id, + &pmsg[1], ntohs (pmsg->size) - sizeof (*pmsg)); + break; + } + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END: + if (NULL == cbs->eom_cb) + break; + cbs->eom_cb (cbs->cls, slicer->msg, pmsg, slicer->message_id, GNUNET_NO); + break; + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL: + if (NULL == cbs->eom_cb) + break; + cbs->eom_cb (cbs->cls, slicer->msg, pmsg, slicer->message_id, GNUNET_YES); + break; + } + return GNUNET_YES; +} + + +/** + * Call a method handler for an incoming message part. + */ +static int +slicer_modifier_handler_notify (void *cls, const struct GNUNET_HashCode *key, + void *value) +{ + struct GNUNET_PSYC_Slicer *slicer = cls; + struct SlicerModifierCallbacks *cbs = value; + + cbs->modifier_cb (cbs->cls, slicer->msg, slicer->pmsg, slicer->message_id, + slicer->mod_oper, slicer->mod_name, slicer->mod_value, + slicer->mod_value_size, slicer->mod_full_value_size); + return GNUNET_YES; +} + + +/** + * Process an incoming message and call matching handlers. + * + * @param slicer + * The slicer to use. + * @param msg + * The message as it arrived from the network. + */ +void +GNUNET_PSYC_slicer_message (struct GNUNET_PSYC_Slicer *slicer, + const struct GNUNET_PSYC_MessageHeader *msg) +{ + GNUNET_PSYC_receive_message (slicer->recv, msg); +} + + +/** + * Process an incoming message part and call matching handlers. + * + * @param cls + * Closure. + * @param message_id + * ID of the message. + * @param flags + * Flags for the message. + * @see enum GNUNET_PSYC_MessageFlags + * @param msg + * The message part. as it arrived from the network. + */ +void +GNUNET_PSYC_slicer_message_part (struct GNUNET_PSYC_Slicer *slicer, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg) +{ + slicer->msg = msg; + slicer->pmsg = pmsg; + + uint64_t message_id = GNUNET_ntohll (msg->message_id); + + uint16_t ptype = ntohs (pmsg->type); + if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD == ptype) + { + struct GNUNET_PSYC_MessageMethod * + meth = (struct GNUNET_PSYC_MessageMethod *) pmsg; + slicer->method_name_size = ntohs (meth->header.size) - sizeof (*meth); + slicer->method_name = GNUNET_malloc (slicer->method_name_size); + GNUNET_memcpy (slicer->method_name, &meth[1], slicer->method_name_size); + slicer->message_id = message_id; + } + else + { + GNUNET_assert (message_id == slicer->message_id); + } + + char *nym_str = GNUNET_CRYPTO_ecdsa_public_key_to_string (&msg->slave_pub_key); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Slicer received message of type %u and size %u, " + "with ID %" PRIu64 " and method %s from %s\n", + ptype, ntohs (pmsg->size), message_id, slicer->method_name, nym_str); + GNUNET_free (nym_str); + + /* try-and-slice modifier */ + + switch (ptype) + { + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER: + { + struct GNUNET_PSYC_MessageModifier * + mod = (struct GNUNET_PSYC_MessageModifier *) pmsg; + slicer->mod_oper = mod->oper; + slicer->mod_name_size = ntohs (mod->name_size); + slicer->mod_name = GNUNET_malloc (slicer->mod_name_size); + GNUNET_memcpy (slicer->mod_name, &mod[1], slicer->mod_name_size); + slicer->mod_value = (char *) &mod[1] + slicer->mod_name_size; + slicer->mod_full_value_size = ntohs (mod->value_size); + slicer->mod_value_remaining = slicer->mod_full_value_size; + slicer->mod_value_size + = ntohs (mod->header.size) - sizeof (*mod) - slicer->mod_name_size; + // fall through + } + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT: + if (ptype == GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT) + { + slicer->mod_value = (char *) &pmsg[1]; + slicer->mod_value_size = ntohs (pmsg->size) - sizeof (*pmsg); + } + slicer->mod_value_remaining -= slicer->mod_value_size; + char *name = GNUNET_malloc (slicer->mod_name_size); + GNUNET_memcpy (name, slicer->mod_name, slicer->mod_name_size); + do + { + struct GNUNET_HashCode key; + uint16_t name_len = strlen (name); + GNUNET_CRYPTO_hash (name, name_len, &key); + GNUNET_CONTAINER_multihashmap_get_multiple (slicer->modifier_handlers, &key, + slicer_modifier_handler_notify, + slicer); + char *p = strrchr (name, '_'); + if (NULL == p) + break; + *p = '\0'; + } while (1); + GNUNET_free (name); + } + + /* try-and-slice method */ + + char *name = GNUNET_malloc (slicer->method_name_size); + GNUNET_memcpy (name, slicer->method_name, slicer->method_name_size); + do + { + struct GNUNET_HashCode key; + uint16_t name_len = strlen (name); + GNUNET_CRYPTO_hash (name, name_len, &key); + GNUNET_CONTAINER_multihashmap_get_multiple (slicer->method_handlers, &key, + slicer_method_handler_notify, + slicer); + char *p = strrchr (name, '_'); + if (NULL == p) + break; + *p = '\0'; + } while (1); + GNUNET_free (name); + + if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END <= ptype) + GNUNET_free (slicer->method_name); + + if (0 == slicer->mod_value_remaining && NULL != slicer->mod_name) + { + GNUNET_free (slicer->mod_name); + slicer->mod_name = NULL; + slicer->mod_name_size = 0; + slicer->mod_value_size = 0; + slicer->mod_full_value_size = 0; + slicer->mod_oper = 0; + } + + slicer->msg = NULL; + slicer->pmsg = NULL; +} + + +/** + * Create a try-and-slice instance. + * + * A slicer processes incoming messages and notifies callbacks about matching + * methods or modifiers encountered. + * + * @return A new try-and-slice construct. + */ +struct GNUNET_PSYC_Slicer * +GNUNET_PSYC_slicer_create (void) +{ + struct GNUNET_PSYC_Slicer *slicer = GNUNET_malloc (sizeof (*slicer)); + slicer->method_handlers = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + slicer->modifier_handlers = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + slicer->recv = GNUNET_PSYC_receive_create (NULL, + (GNUNET_PSYC_MessagePartCallback) + GNUNET_PSYC_slicer_message_part, + slicer); + return slicer; +} + + +/** + * Add a method to the try-and-slice instance. + * + * The callbacks are called for messages with a matching @a method_name prefix. + * + * @param slicer + * The try-and-slice instance to extend. + * @param method_name + * Name of the given method, use empty string to match all. + * @param method_cb + * Method handler invoked upon a matching message. + * @param modifier_cb + * Modifier handler, invoked after @a method_cb + * for each modifier in the message. + * @param data_cb + * Data handler, invoked after @a modifier_cb for each data fragment. + * @param eom_cb + * Invoked upon reaching the end of a matching message. + * @param cls + * Closure for the callbacks. + */ +void +GNUNET_PSYC_slicer_method_add (struct GNUNET_PSYC_Slicer *slicer, + const char *method_name, + GNUNET_PSYC_MessageCallback msg_cb, + GNUNET_PSYC_MethodCallback method_cb, + GNUNET_PSYC_ModifierCallback modifier_cb, + GNUNET_PSYC_DataCallback data_cb, + GNUNET_PSYC_EndOfMessageCallback eom_cb, + void *cls) +{ + struct GNUNET_HashCode key; + GNUNET_CRYPTO_hash (method_name, strlen (method_name), &key); + + struct SlicerMethodCallbacks *cbs = GNUNET_malloc (sizeof (*cbs)); + cbs->msg_cb = msg_cb, + cbs->method_cb = method_cb; + cbs->modifier_cb = modifier_cb; + cbs->data_cb = data_cb; + cbs->eom_cb = eom_cb; + cbs->cls = cls; + + GNUNET_CONTAINER_multihashmap_put (slicer->method_handlers, &key, cbs, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); +} + + +static int +slicer_method_remove (void *cls, const struct GNUNET_HashCode *key, void *value) +{ + struct SlicerMethodRemoveClosure *rm_cls = cls; + struct GNUNET_PSYC_Slicer *slicer = rm_cls->slicer; + struct SlicerMethodCallbacks *rm_cbs = &rm_cls->rm_cbs; + struct SlicerMethodCallbacks *cbs = value; + + if ((NULL == rm_cbs->msg_cb || cbs->msg_cb == rm_cbs->msg_cb) + && (NULL == rm_cbs->method_cb || cbs->method_cb == rm_cbs->method_cb) + && (NULL == rm_cbs->modifier_cb || cbs->modifier_cb == rm_cbs->modifier_cb) + && (NULL == rm_cbs->data_cb || cbs->data_cb == rm_cbs->data_cb) + && (NULL == rm_cbs->eom_cb || cbs->eom_cb == rm_cbs->eom_cb)) + { + GNUNET_CONTAINER_multihashmap_remove (slicer->method_handlers, key, cbs); + GNUNET_free (cbs); + return GNUNET_NO; + } + return GNUNET_YES; +} + + +/** + * Remove a registered method from the try-and-slice instance. + * + * Removes one matching handler registered with the given + * @a method_name and callbacks. + * + * @param slicer + * The try-and-slice instance. + * @param method_name + * Name of the method to remove. + * @param method_cb + * Method handler. + * @param modifier_cb + * Modifier handler. + * @param data_cb + * Data handler. + * @param eom_cb + * End of message handler. + * + * @return #GNUNET_OK if a method handler was removed, + * #GNUNET_NO if no handler matched the given method name and callbacks. + */ +int +GNUNET_PSYC_slicer_method_remove (struct GNUNET_PSYC_Slicer *slicer, + const char *method_name, + GNUNET_PSYC_MessageCallback msg_cb, + GNUNET_PSYC_MethodCallback method_cb, + GNUNET_PSYC_ModifierCallback modifier_cb, + GNUNET_PSYC_DataCallback data_cb, + GNUNET_PSYC_EndOfMessageCallback eom_cb) +{ + struct GNUNET_HashCode key; + GNUNET_CRYPTO_hash (method_name, strlen (method_name), &key); + + struct SlicerMethodRemoveClosure rm_cls; + rm_cls.slicer = slicer; + struct SlicerMethodCallbacks *rm_cbs = &rm_cls.rm_cbs; + rm_cbs->msg_cb = msg_cb; + rm_cbs->method_cb = method_cb; + rm_cbs->modifier_cb = modifier_cb; + rm_cbs->data_cb = data_cb; + rm_cbs->eom_cb = eom_cb; + + return + (GNUNET_SYSERR + == GNUNET_CONTAINER_multihashmap_get_multiple (slicer->method_handlers, &key, + slicer_method_remove, + &rm_cls)) + ? GNUNET_NO + : GNUNET_OK; +} + + +/** + * Watch a place for changed objects. + * + * @param slicer + * The try-and-slice instance. + * @param object_filter + * Object prefix to match. + * @param modifier_cb + * Function to call when encountering a state modifier. + * @param cls + * Closure for callback. + */ +void +GNUNET_PSYC_slicer_modifier_add (struct GNUNET_PSYC_Slicer *slicer, + const char *object_filter, + GNUNET_PSYC_ModifierCallback modifier_cb, + void *cls) +{ + struct SlicerModifierCallbacks *cbs = GNUNET_malloc (sizeof *cbs); + cbs->modifier_cb = modifier_cb; + cbs->cls = cls; + + struct GNUNET_HashCode key; + GNUNET_CRYPTO_hash (object_filter, strlen (object_filter), &key); + GNUNET_CONTAINER_multihashmap_put (slicer->modifier_handlers, &key, cbs, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); +} + + +static int +slicer_modifier_remove (void *cls, const struct GNUNET_HashCode *key, void *value) +{ + struct SlicerModifierRemoveClosure *rm_cls = cls; + struct GNUNET_PSYC_Slicer *slicer = rm_cls->slicer; + struct SlicerModifierCallbacks *rm_cbs = &rm_cls->rm_cbs; + struct SlicerModifierCallbacks *cbs = value; + + if (cbs->modifier_cb == rm_cbs->modifier_cb) + { + GNUNET_CONTAINER_multihashmap_remove (slicer->modifier_handlers, key, cbs); + GNUNET_free (cbs); + return GNUNET_NO; + } + return GNUNET_YES; +} + + +/** + * Remove a registered modifier from the try-and-slice instance. + * + * Removes one matching handler registered with the given + * @a object_filter and @a modifier_cb. + * + * @param slicer + * The try-and-slice instance. + * @param object_filter + * Object prefix to match. + * @param modifier_cb + * Function to call when encountering a state modifier changes. + */ +int +GNUNET_PSYC_slicer_modifier_remove (struct GNUNET_PSYC_Slicer *slicer, + const char *object_filter, + GNUNET_PSYC_ModifierCallback modifier_cb) +{ + struct GNUNET_HashCode key; + GNUNET_CRYPTO_hash (object_filter, strlen (object_filter), &key); + + struct SlicerModifierRemoveClosure rm_cls; + rm_cls.slicer = slicer; + struct SlicerModifierCallbacks *rm_cbs = &rm_cls.rm_cbs; + rm_cbs->modifier_cb = modifier_cb; + + return + (GNUNET_SYSERR + == GNUNET_CONTAINER_multihashmap_get_multiple (slicer->modifier_handlers, &key, + slicer_modifier_remove, + &rm_cls)) + ? GNUNET_NO + : GNUNET_OK; + } + + +static int +slicer_method_free (void *cls, const struct GNUNET_HashCode *key, void *value) +{ + struct SlicerMethodCallbacks *cbs = value; + GNUNET_free (cbs); + return GNUNET_YES; +} + + +static int +slicer_modifier_free (void *cls, const struct GNUNET_HashCode *key, void *value) +{ + struct SlicerModifierCallbacks *cbs = value; + GNUNET_free (cbs); + return GNUNET_YES; +} + + +/** + * Remove all registered method handlers. + * + * @param slicer + * Slicer to clear. + */ +void +GNUNET_PSYC_slicer_method_clear (struct GNUNET_PSYC_Slicer *slicer) +{ + GNUNET_CONTAINER_multihashmap_iterate (slicer->method_handlers, + slicer_method_free, NULL); + GNUNET_CONTAINER_multihashmap_clear (slicer->method_handlers); +} + + +/** + * Remove all registered modifier handlers. + * + * @param slicer + * Slicer to clear. + */ +void +GNUNET_PSYC_slicer_modifier_clear (struct GNUNET_PSYC_Slicer *slicer) +{ + GNUNET_CONTAINER_multihashmap_iterate (slicer->modifier_handlers, + slicer_modifier_free, NULL); + GNUNET_CONTAINER_multihashmap_clear (slicer->modifier_handlers); +} + + +/** + * Remove all registered method & modifier handlers. + * + * @param slicer + * Slicer to clear. + */ +void +GNUNET_PSYC_slicer_clear (struct GNUNET_PSYC_Slicer *slicer) +{ + GNUNET_PSYC_slicer_method_clear (slicer); + GNUNET_PSYC_slicer_modifier_clear (slicer); +} + + +/** + * Destroy a given try-and-slice instance. + * + * @param slicer + * Slicer to destroy + */ +void +GNUNET_PSYC_slicer_destroy (struct GNUNET_PSYC_Slicer *slicer) +{ + GNUNET_PSYC_slicer_clear (slicer); + GNUNET_CONTAINER_multihashmap_destroy (slicer->method_handlers); + GNUNET_CONTAINER_multihashmap_destroy (slicer->modifier_handlers); + GNUNET_PSYC_receive_destroy (slicer->recv); + GNUNET_free (slicer); +} diff --git a/src/psycutil/test_psyc_env.c b/src/psycutil/test_psyc_env.c new file mode 100644 index 0000000..432e155 --- /dev/null +++ b/src/psycutil/test_psyc_env.c @@ -0,0 +1,96 @@ +/* + * This file is part of GNUnet. + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @author Gabor X Toth + * + * @file + * Tests for the environment library. + */ + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_testing_lib.h" +#include "gnunet_psyc_util_lib.h" + +struct GNUNET_PSYC_Modifier mods[] = { + { .oper = GNUNET_PSYC_OP_SET, + .name = "_foo", .value = "foo", .value_size = 3 }, + + { .oper = GNUNET_PSYC_OP_ASSIGN, + .name = "_foo_bar", .value = "foo bar", .value_size = 7 }, + + { .oper = GNUNET_PSYC_OP_AUGMENT, + .name = "_foo_bar_baz", .value = "foo bar baz", .value_size = 11 } +}; + +struct ItCls +{ + size_t n; +}; + +int +iterator (void *cls, enum GNUNET_PSYC_Operator oper, + const char *name, const char *value, uint32_t value_size) +{ + struct ItCls *it_cls = cls; + struct GNUNET_PSYC_Modifier *m = &mods[it_cls->n++]; + + GNUNET_assert (oper == m->oper); + GNUNET_assert (value_size == m->value_size); + GNUNET_assert (0 == memcmp (name, m->name, strlen (m->name))); + GNUNET_assert (0 == memcmp (value, m->value, m->value_size)); + + return GNUNET_YES; +} + +int +main (int argc, char *argv[]) +{ + GNUNET_log_setup ("test-env", "WARNING", NULL); + + struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create (); + GNUNET_assert (NULL != env); + int i, len = 3; + + for (i = 0; i < len; i++) + { + GNUNET_PSYC_env_add (env, mods[i].oper, mods[i].name, + mods[i].value, mods[i].value_size); + } + + struct ItCls it_cls = { .n = 0 }; + GNUNET_PSYC_env_iterate (env, iterator, &it_cls); + GNUNET_assert (len == it_cls.n); + + for (i = 0; i < len; i++) + { + enum GNUNET_PSYC_Operator oper; + const char *name; + const void *value; + size_t value_size; + GNUNET_PSYC_env_shift (env, &oper, &name, &value, &value_size); + GNUNET_assert (len - i - 1 == GNUNET_PSYC_env_get_count (env)); + } + + GNUNET_PSYC_env_destroy (env); + + return 0; +} diff --git a/src/social/.gitignore b/src/social/.gitignore new file mode 100644 index 0000000..875aa11 --- /dev/null +++ b/src/social/.gitignore @@ -0,0 +1,3 @@ +gnunet-social +gnunet-service-social +test_social diff --git a/src/social/Makefile.am b/src/social/Makefile.am new file mode 100644 index 0000000..94a9ba1 --- /dev/null +++ b/src/social/Makefile.am @@ -0,0 +1,79 @@ +# This Makefile.am is in the public domain +AM_CPPFLAGS = -I$(top_srcdir)/src/include + +pkgcfgdir= $(pkgdatadir)/config.d/ + +libexecdir= $(pkglibdir)/libexec/ + +pkgcfg_DATA = \ + social.conf + + +if MINGW + WINFLAGS = -Wl,--no-undefined -Wl,--export-all-symbols +endif + +if USE_COVERAGE + AM_CFLAGS = --coverage -O0 + XLIB = -lgcov +endif + +lib_LTLIBRARIES = libgnunetsocial.la + +libgnunetsocial_la_SOURCES = \ + social_api.c social.h +libgnunetsocial_la_LIBADD = \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(top_builddir)/src/psycutil/libgnunetpsycutil.la \ + $(GN_LIBINTL) $(XLIB) +libgnunetsocial_la_LDFLAGS = \ + $(GN_LIB_LDFLAGS) $(WINFLAGS) \ + -version-info 0:0:0 + +bin_PROGRAMS = \ + gnunet-social + +libexec_PROGRAMS = \ + gnunet-service-social + +gnunet_social_SOURCES = \ + gnunet-social.c +gnunet_social_LDADD = \ + libgnunetsocial.la \ + $(top_builddir)/src/psycutil/libgnunetpsycutil.la \ + $(top_builddir)/src/util/libgnunetutil.la + +gnunet_service_social_SOURCES = \ + gnunet-service-social.c +gnunet_service_social_LDADD = \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(top_builddir)/src/statistics/libgnunetstatistics.la \ + $(top_builddir)/src/psycutil/libgnunetpsycutil.la \ + $(top_builddir)/src/psyc/libgnunetpsyc.la \ + $(top_builddir)/src/identity/libgnunetidentity.la \ + $(top_builddir)/src/gns/libgnunetgns.la \ + $(top_builddir)/src/namestore/libgnunetnamestore.la \ + $(GN_LIBINTL) + + +if HAVE_TESTING +check_PROGRAMS = \ + test_social +endif + +if ENABLE_TEST_RUN +AM_TESTS_ENVIRONMENT=export GNUNET_PREFIX=$${GNUNET_PREFIX:-@libdir@};export PATH=$${GNUNET_PREFIX:-@prefix@}/bin:$$PATH;unset XDG_DATA_HOME;unset XDG_CONFIG_HOME; +TESTS = $(check_PROGRAMS) +endif + +test_social_SOURCES = \ + test_social.c +test_social_LDADD = \ + libgnunetsocial.la \ + $(top_builddir)/src/testing/libgnunettesting.la \ + $(top_builddir)/src/util/libgnunetutil.la \ + $(top_builddir)/src/psycutil/libgnunetpsycutil.la \ + $(top_builddir)/src/identity/libgnunetidentity.la + +EXTRA_DIST = \ + test_social.conf diff --git a/src/social/gnunet-service-social.c b/src/social/gnunet-service-social.c new file mode 100644 index 0000000..33fabae --- /dev/null +++ b/src/social/gnunet-service-social.c @@ -0,0 +1,3760 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file social/gnunet-service-social.c + * @brief Social service + * @author Gabor X Toth + */ + +#include +#include + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_constants.h" +#include "gnunet_protocols.h" +#include "gnunet_identity_service.h" +#include "gnunet_namestore_service.h" +#include "gnunet_gns_service.h" +#include "gnunet_statistics_service.h" +#include "gnunet_psyc_service.h" +#include "gnunet_psyc_util_lib.h" +#include "gnunet_social_service.h" +#include "social.h" + + +/** + * Handle to our current configuration. + */ +static const struct GNUNET_CONFIGURATION_Handle *cfg; + +/** + * Service handle. + */ +static struct GNUNET_SERVICE_Handle *service; + +/* Handles to other services */ +static struct GNUNET_IDENTITY_Handle *id; +static struct GNUNET_GNS_Handle *gns; +static struct GNUNET_NAMESTORE_Handle *namestore; +static struct GNUNET_STATISTICS_Handle *stats; + +/** + * ID of this peer. + */ +static struct GNUNET_PeerIdentity this_peer; + +/** + * All connected hosts. + * H(place_pub_key) -> struct Host + */ +static struct GNUNET_CONTAINER_MultiHashMap *hosts; + +/** + * All connected guests. + * H(place_pub_key) -> struct Guest + */ +static struct GNUNET_CONTAINER_MultiHashMap *guests; + +/** + * Connected guests per place. + * H(place_pub_key) -> ego_pub_key -> struct Guest + */ +static struct GNUNET_CONTAINER_MultiHashMap *place_guests; + +/** + * Places entered as host or guest. + * H(place_pub_key) -> struct HostEnterRequest OR struct GuestEnterRequest + */ +static struct GNUNET_CONTAINER_MultiHashMap *places; + +/** + * Places entered per application. + * H(app_id) -> H(place_pub_key) -> NULL + */ +static struct GNUNET_CONTAINER_MultiHashMap *apps_places; + +/** + * Application subscriptions per place. + * H(place_pub_key) -> H(app_id) + */ +//static struct GNUNET_CONTAINER_MultiHashMap *places_apps; + +/** + * Connected applications. + * H(app_id) -> struct Application + */ +static struct GNUNET_CONTAINER_MultiHashMap *apps; + +/** + * All egos. + * H(ego_pub_key) -> struct Ego + */ +static struct GNUNET_CONTAINER_MultiHashMap *egos; + +/** + * Directory for storing social data. + * Default: $GNUNET_DATA_HOME/social + */ +static char *dir_social; + +/** + * Directory for storing place data. + * $dir_social/places + */ +static char *dir_places; + +/** + * Directory for storing app data. + * $dir_social/apps + */ +static char *dir_apps; + + +/** + * Message fragment transmission queue. + */ +struct FragmentTransmitQueue +{ + struct FragmentTransmitQueue *prev; + struct FragmentTransmitQueue *next; + + struct GNUNET_SERVICE_Client *client; + + /** + * Pointer to the next message part inside the data after this struct. + */ + struct GNUNET_MessageHeader *next_part; + + /** + * Size of message. + */ + uint16_t size; + + /** + * @see enum GNUNET_PSYC_MessageState + */ + uint8_t state; + + /* Followed by one or more message parts. */ +}; + + +/** + * Message transmission queue. + */ +struct MessageTransmitQueue +{ + struct MessageTransmitQueue *prev; + struct MessageTransmitQueue *next; + + struct FragmentTransmitQueue *frags_head; + struct FragmentTransmitQueue *frags_tail; + + struct GNUNET_SERVICE_Client *client; +}; + +/** + * List of connected clients. + */ +struct ClientListItem +{ + struct ClientListItem *prev; + struct ClientListItem *next; + + struct GNUNET_SERVICE_Client *client; +}; + + +/** + * Common part of the client context for both a host and guest. + */ +struct Place +{ + struct ClientListItem *clients_head; + struct ClientListItem *clients_tail; + + struct MessageTransmitQueue *tmit_msgs_head; + struct MessageTransmitQueue *tmit_msgs_tail; + + struct GNUNET_PSYC_Channel *channel; + + /** + * Private key of home in case of a host. + */ + struct GNUNET_CRYPTO_EddsaPublicKey key; + + /** + * Public key of place. + */ + struct GNUNET_CRYPTO_EddsaPublicKey pub_key; + + /** + * Hash of @a pub_key. + */ + struct GNUNET_HashCode pub_key_hash; + + /** + * Private key of ego. + */ + struct GNUNET_CRYPTO_EcdsaPrivateKey ego_key; + + /** + * Public key of ego. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key; + + /** + * Hash of @a ego_pub_key. + */ + struct GNUNET_HashCode ego_pub_hash; + + /** + * Slicer for processing incoming messages. + */ + struct GNUNET_PSYC_Slicer *slicer; + + /** + * Last message ID received for the place. + * 0 if there is no such message. + */ + uint64_t max_message_id; + + /** + * Offset where the file is currently being written. + */ + uint64_t file_offset; + + /** + * Whether or not to save the file (#GNUNET_YES or #GNUNET_NO) + */ + uint8_t file_save; + + /** + * Is this place ready to receive messages from client? + * #GNUNET_YES or #GNUNET_NO + */ + uint8_t is_ready; + + /** + * Is the client disconnecting? + * #GNUNET_YES or #GNUNET_NO + */ + uint8_t is_disconnecting; + + /** + * Is this a host (#GNUNET_YES), or guest (#GNUNET_NO)? + */ + uint8_t is_host; + + union { + struct Host *host; + struct Guest *guest; + }; +}; + + +/** + * Client context for a host. + */ +struct Host +{ + /** + * Place struct common for Host and Guest + */ + struct Place place; + + /** + * Handle for the multicast origin. + */ + struct GNUNET_PSYC_Master *master; + + /** + * Transmit handle for multicast. + */ + struct GNUNET_PSYC_MasterTransmitHandle *tmit_handle; + + /** + * Incoming join requests. + * guest_key -> struct GNUNET_PSYC_JoinHandle * + */ + struct GNUNET_CONTAINER_MultiHashMap *join_reqs; + + /** + * Messages being relayed. + */ + struct GNUNET_CONTAINER_MultiHashMap *relay_msgs; + + /** + * @see enum GNUNET_PSYC_Policy + */ + enum GNUNET_PSYC_Policy policy; +}; + + +/** + * Client context for a guest. + */ +struct Guest +{ + /** + * Place struct common for Host and Guest. + */ + struct Place place; + + /** + * Handle for the PSYC slave. + */ + struct GNUNET_PSYC_Slave *slave; + + /** + * Transmit handle for multicast. + */ + struct GNUNET_PSYC_SlaveTransmitHandle *tmit_handle; + + /** + * Peer identity of the origin. + */ + struct GNUNET_PeerIdentity origin; + + /** + * Number of items in @a relays. + */ + uint32_t relay_count; + + /** + * Relays that multicast can use to connect. + */ + struct GNUNET_PeerIdentity *relays; + + /** + * Join request to be transmitted to the master on join. + */ + struct GNUNET_MessageHeader *join_req; // FIXME: not used! + + /** + * Join decision received from PSYC. + */ + struct GNUNET_PSYC_JoinDecisionMessage *join_dcsn; + + /** + * Join flags for the PSYC service. + */ + enum GNUNET_PSYC_SlaveJoinFlags join_flags; +}; + + +/** + * Context for a client. + */ +struct Client +{ + /** + * Client handle. + */ + struct GNUNET_SERVICE_Client *client; + + /** + * Place where the client entered. + */ + struct Place *place; + + /** + * Message queue for the message currently being transmitted + * by this client. + */ + struct MessageTransmitQueue *tmit_msg; + + /** + * ID for application clients. + */ + char *app_id; +}; + + +struct Application +{ + struct ClientListItem *clients_head; + struct ClientListItem *clients_tail; +}; + + +struct Ego { + struct GNUNET_CRYPTO_EcdsaPrivateKey key; + char *name; +}; + + +struct OperationClosure +{ + struct Client *client; + uint64_t op_id; + uint32_t flags; +}; + + +static int +psyc_transmit_message (struct Place *plc); + + +/** + * Clean up place data structures after a client disconnected. + * + * @param cls the `struct Place` to clean up + */ +static void +cleanup_place (void *cls); + + +static struct MessageTransmitQueue * +psyc_transmit_queue_message (struct Place *plc, + struct GNUNET_SERVICE_Client *client, + size_t data_size, + const void *data, + uint16_t first_ptype, uint16_t last_ptype, + struct MessageTransmitQueue *tmit_msg); + + +static int +place_entry_cleanup (void *cls, + const struct GNUNET_HashCode *key, + void *value) +{ + struct Place *plc = value; + + cleanup_place (plc); + return GNUNET_YES; +} + + +/** + * Task run during shutdown. + * + * @param cls unused + */ +static void +shutdown_task (void *cls) +{ + GNUNET_CONTAINER_multihashmap_iterate (hosts, place_entry_cleanup, NULL); + GNUNET_CONTAINER_multihashmap_iterate (guests, place_entry_cleanup, NULL); + + if (NULL != id) + { + GNUNET_IDENTITY_disconnect (id); + id = NULL; + } + if (NULL != namestore) + { + GNUNET_NAMESTORE_disconnect (namestore); + namestore = NULL; + } + if (NULL != gns) + { + GNUNET_GNS_disconnect (gns); + gns = NULL; + } + if (NULL != stats) + { + GNUNET_STATISTICS_destroy (stats, GNUNET_YES); + stats = NULL; + } +} + + +/** + * Clean up host data structures after a client disconnected. + */ +static void +cleanup_host (struct Host *hst) +{ + struct Place *plc = &hst->place; + + GNUNET_CONTAINER_multihashmap_destroy (hst->join_reqs); + GNUNET_CONTAINER_multihashmap_destroy (hst->relay_msgs); + GNUNET_CONTAINER_multihashmap_remove (hosts, &plc->pub_key_hash, plc); +} + + +/** + * Clean up guest data structures after a client disconnected. + */ +static void +cleanup_guest (struct Guest *gst) +{ + struct Place *plc = &gst->place; + struct GNUNET_CONTAINER_MultiHashMap * + plc_gst = GNUNET_CONTAINER_multihashmap_get (place_guests, + &plc->pub_key_hash); + if (NULL != plc_gst) + { + GNUNET_CONTAINER_multihashmap_remove (plc_gst, &plc->ego_pub_hash, gst); + + if (0 == GNUNET_CONTAINER_multihashmap_size (plc_gst)) + { + GNUNET_CONTAINER_multihashmap_remove (place_guests, &plc->pub_key_hash, + plc_gst); + GNUNET_CONTAINER_multihashmap_destroy (plc_gst); + } + } + GNUNET_CONTAINER_multihashmap_remove (guests, &plc->pub_key_hash, gst); + if (NULL != gst->join_req) + GNUNET_free (gst->join_req); + if (NULL != gst->relays) + GNUNET_free (gst->relays); + GNUNET_CONTAINER_multihashmap_remove (guests, &plc->pub_key_hash, plc); +} + + +/** + * Clean up place data structures after a client disconnected. + * + * @param cls the `struct Place` to clean up + */ +static void +cleanup_place (void *cls) +{ + struct Place *plc = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "cleaning up place %s\n", + GNUNET_h2s (&plc->pub_key_hash)); + + (GNUNET_YES == plc->is_host) + ? cleanup_host ((struct Host *) plc) + : cleanup_guest ((struct Guest *) plc); + + GNUNET_PSYC_slicer_destroy (plc->slicer); + GNUNET_free (plc); +} + + +/** + * Called whenever a client is disconnected. + * Frees our resources associated with that client. + * + * @param cls closure + * @param client identification of the client + * @param app_ctx must match @a client + */ +static void +client_notify_disconnect (void *cls, + struct GNUNET_SERVICE_Client *client, + void *app_ctx) +{ + struct Client *c = app_ctx; + struct Place *plc = c->place; + + if (NULL != c->app_id) + GNUNET_free (c->app_id); + + GNUNET_free (c); + + if (NULL == plc) + return; // application client, nothing to do + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Client (%s) disconnected from place %s\n", + plc, (GNUNET_YES == plc->is_host) ? "host" : "guest", + GNUNET_h2s (&plc->pub_key_hash)); + + struct ClientListItem *cli = plc->clients_head; + while (NULL != cli) + { + if (cli->client == client) + { + GNUNET_CONTAINER_DLL_remove (plc->clients_head, + plc->clients_tail, + cli); + GNUNET_free (cli); + break; + } + cli = cli->next; + } + if (GNUNET_YES == plc->is_disconnecting) + { + GNUNET_PSYC_slicer_destroy (plc->slicer); + GNUNET_free (plc); + } +} + + +/** + * A new client connected. + * + * @param cls NULL + * @param client client to add + * @param mq message queue for @a client + * @return @a client + */ +static void * +client_notify_connect (void *cls, + struct GNUNET_SERVICE_Client *client, + struct GNUNET_MQ_Handle *mq) +{ + struct Client *c = GNUNET_new (struct Client); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Client %p connected with queue %p\n", + client, + mq); + c->client = client; + return c; +} + + +/** + * Send message to all clients connected to a place and + * takes care of freeing @env. + */ +static void +place_send_msg (const struct Place *plc, + struct GNUNET_MQ_Envelope *env) +{ + struct ClientListItem *cli = plc->clients_head; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Sending message to clients of place.\n", plc); + while (NULL != cli) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Sending message to client %p\n", + cli); + GNUNET_MQ_send_copy (GNUNET_SERVICE_client_get_mq (cli->client), + env); + cli = cli->next; + } + GNUNET_MQ_discard (env); +} + + +static void +place_send_leave_ack (struct Place *plc) +{ + struct GNUNET_MQ_Envelope *env; + + for (struct ClientListItem *cli = plc->clients_head; + NULL != cli; + cli = cli->next) + { + env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_SOCIAL_PLACE_LEAVE_ACK); + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (cli->client), + env); + } +} + + +/** + * Send a result code back to the client. + * + * @param client + * Client that should receive the result code. + * @param result_code + * Code to transmit. + * @param op_id + * Operation ID in network byte order. + * @param data + * Data payload or NULL. + * @param data_size + * Size of @a data. + */ +static void +client_send_result (struct GNUNET_SERVICE_Client *client, uint64_t op_id, + int64_t result_code, const void *data, uint16_t data_size) +{ + struct GNUNET_MQ_Envelope *env; + struct GNUNET_OperationResultMessage *res; + + env = GNUNET_MQ_msg_extra (res, + data_size, + GNUNET_MESSAGE_TYPE_PSYC_RESULT_CODE); + res->result_code = GNUNET_htonll (result_code); + res->op_id = op_id; + if (0 < data_size) + GNUNET_memcpy (&res[1], data, data_size); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Sending result to client for operation #%" PRIu64 ": " + "%" PRId64 " (size: %u)\n", + client, GNUNET_ntohll (op_id), result_code, data_size); + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), env); +} + + +static void +client_send_host_enter_ack (struct GNUNET_SERVICE_Client *client, + struct Host *hst, uint32_t result) +{ + struct GNUNET_MQ_Envelope *env; + struct HostEnterAck *hack; + struct Place *plc = &hst->place; + + env = GNUNET_MQ_msg (hack, + GNUNET_MESSAGE_TYPE_SOCIAL_HOST_ENTER_ACK); + hack->result_code = htonl (result); + hack->max_message_id = GNUNET_htonll (plc->max_message_id); + hack->place_pub_key = plc->pub_key; + + if (NULL != client) + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), + env); + else + place_send_msg (plc, env); +} + + +/** + * Called after a PSYC master is started. + */ +static void +psyc_master_started (void *cls, int result, uint64_t max_message_id) +{ + struct Host *hst = cls; + struct Place *plc = &hst->place; + plc->max_message_id = max_message_id; + plc->is_ready = GNUNET_YES; + + client_send_host_enter_ack (NULL, hst, result); +} + + +/** + * Called when a PSYC master receives a join request. + */ +static void +psyc_recv_join_request (void *cls, + const struct GNUNET_PSYC_JoinRequestMessage *req, + const struct GNUNET_CRYPTO_EcdsaPublicKey *slave_key, + const struct GNUNET_PSYC_Message *join_msg, + struct GNUNET_PSYC_JoinHandle *jh) +{ + struct Host *hst = cls; + struct GNUNET_HashCode slave_key_hash; + GNUNET_CRYPTO_hash (slave_key, sizeof (*slave_key), &slave_key_hash); + GNUNET_CONTAINER_multihashmap_put (hst->join_reqs, &slave_key_hash, jh, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); + place_send_msg (&hst->place, + GNUNET_MQ_msg_copy (&req->header)); +} + + +/** + * Called after a PSYC slave is connected. + */ +static void +psyc_slave_connected (void *cls, int result, uint64_t max_message_id) +{ + struct GNUNET_PSYC_CountersResultMessage *res; + struct GNUNET_MQ_Envelope *env; + struct Guest *gst = cls; + struct Place *plc = &gst->place; + + plc->max_message_id = max_message_id; + plc->is_ready = GNUNET_YES; + env = GNUNET_MQ_msg (res, + GNUNET_MESSAGE_TYPE_SOCIAL_GUEST_ENTER_ACK); + res->result_code = + (result != GNUNET_SYSERR) ? htonl (GNUNET_OK) : htonl (GNUNET_SYSERR); + res->max_message_id = GNUNET_htonll (plc->max_message_id); + place_send_msg (plc, env); +} + + +static void +slave_parted_after_join_decision (void *cls) +{ + struct Guest *gst = cls; + + GNUNET_assert (NULL != gst->join_dcsn); + place_send_msg (&gst->place, GNUNET_MQ_msg_copy (&gst->join_dcsn->header)); +} + + +/** + * Called when a PSYC slave receives a join decision. + */ +static void +psyc_recv_join_dcsn (void *cls, + const struct GNUNET_PSYC_JoinDecisionMessage *dcsn, + int is_admitted, + const struct GNUNET_PSYC_Message *join_msg) +{ + struct Guest *gst = cls; + + gst->join_dcsn = GNUNET_malloc (dcsn->header.size); + GNUNET_memcpy (gst->join_dcsn, + dcsn, + dcsn->header.size); + if (GNUNET_NO == is_admitted) + { + GNUNET_PSYC_slave_part (gst->slave, + GNUNET_NO, + &slave_parted_after_join_decision, + gst); + gst->slave = NULL; + return; + } + place_send_msg (&gst->place, GNUNET_MQ_msg_copy (&gst->join_dcsn->header)); +} + + +/** + * Called when a PSYC master or slave receives a message. + */ +static void +psyc_recv_message (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg) +{ + struct Place *plc = cls; + + char *str = GNUNET_CRYPTO_ecdsa_public_key_to_string (&msg->slave_pub_key); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Received PSYC message of size %u from %s.\n", + plc, ntohs (msg->header.size), str); + GNUNET_free (str); + + GNUNET_PSYC_slicer_message (plc->slicer, msg); + + place_send_msg (plc, GNUNET_MQ_msg_copy (&msg->header)); +} + + +/** + * Relay a message part received from a guest to the the place. + * + * @param hst + * Host. + * @param pmsg + * Message part. + * @param nym_pub_key + * Nym the message is received from. + */ +static void +host_relay_message_part (struct Host *hst, + const struct GNUNET_MessageHeader *pmsg, + const struct GNUNET_CRYPTO_EcdsaPublicKey *nym_pub_key) +{ + /* separate queue per nym */ + struct GNUNET_HashCode nym_pub_hash; + GNUNET_CRYPTO_hash (nym_pub_key, sizeof (*nym_pub_key), &nym_pub_hash); + + struct MessageTransmitQueue * + tmit_msg = GNUNET_CONTAINER_multihashmap_get (hst->relay_msgs, &nym_pub_hash); + + uint16_t ptype = ntohs (pmsg->type); + + if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD == ptype) + { + /* FIXME: last message was unfinished, cancel & remove from queue */ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "FIXME: last message was unfinished.\n"); + } + + tmit_msg = psyc_transmit_queue_message (&hst->place, NULL, ntohs (pmsg->size), + pmsg, ptype, ptype, tmit_msg); + + switch (ptype) + { + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD: + GNUNET_assert (GNUNET_YES == GNUNET_CONTAINER_multihashmap_put + (hst->relay_msgs, &nym_pub_hash, tmit_msg, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_ONLY)); + break; + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END: + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL: + GNUNET_assert (GNUNET_YES == GNUNET_CONTAINER_multihashmap_remove + (hst->relay_msgs, &nym_pub_hash, tmit_msg)); + break; + } +} + + +/** + * Received a method to be relayed from a guest. + */ +static void +place_recv_relay_method (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_PSYC_MessageMethod *meth, + uint64_t message_id, + const char *method_name) +{ + struct Place *plc = cls; + + if (GNUNET_PSYC_MESSAGE_REQUEST & ntohs (msg->flags) + && GNUNET_YES == plc->is_host) + { + struct Host *hst = cls; + host_relay_message_part (hst, &meth->header, &msg->slave_pub_key); + } +} + + +/** + * Received a modifier to be relayed from a guest. + */ +static void +place_recv_relay_modifier (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + enum GNUNET_PSYC_Operator oper, + const char *name, + const void *value, + uint16_t value_size, + uint16_t full_value_size) +{ + struct Place *plc = cls; + + if (GNUNET_PSYC_MESSAGE_REQUEST & ntohs (msg->flags) + && GNUNET_YES == plc->is_host) + { + struct Host *hst = cls; + host_relay_message_part (hst, pmsg, &msg->slave_pub_key); + } +} + +/** + * Received a data fragment to be relayed from a guest. + */ +static void +place_recv_relay_data (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + const void *data, + uint16_t data_size) +{ + struct Place *plc = cls; + + if (GNUNET_PSYC_MESSAGE_REQUEST & ntohs (msg->flags) + && GNUNET_YES == plc->is_host) + { + struct Host *hst = cls; + host_relay_message_part (hst, pmsg, &msg->slave_pub_key); + } +} + + +/** + * Received end of message to be relayed from a guest. + */ +static void +place_recv_relay_eom (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + uint8_t is_cancelled) +{ + struct Place *plc = cls; + + if (GNUNET_PSYC_MESSAGE_REQUEST & ntohs (msg->flags) + && GNUNET_YES == plc->is_host) + { + struct Host *hst = cls; + host_relay_message_part (hst, pmsg, &msg->slave_pub_key); + } +} + + +/** + * Received a method to be saved to disk. + * + * Create a new file for writing the data part of the message into, + * if the file does not yet exist. + */ +static void +place_recv_save_method (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_PSYC_MessageMethod *meth, + uint64_t message_id, + const char *method_name) +{ + struct Place *plc = cls; + plc->file_offset = 0; + plc->file_save = GNUNET_NO; + + char *place_pub_str = GNUNET_CRYPTO_eddsa_public_key_to_string (&plc->pub_key); + char *filename = NULL; + GNUNET_asprintf (&filename, "%s%c" "%s%c" "%s%c" "%" PRIu64 ".part", + dir_social, DIR_SEPARATOR, + "files", DIR_SEPARATOR, + place_pub_str, DIR_SEPARATOR, + GNUNET_ntohll (msg->message_id)); + GNUNET_free (place_pub_str); + + /* save if does not already exist */ + if (GNUNET_YES != GNUNET_DISK_file_test (filename)) + { + if (0 == GNUNET_DISK_fn_write (filename, NULL, 0, + GNUNET_DISK_PERM_USER_READ + | GNUNET_DISK_PERM_USER_WRITE)) + { + plc->file_save = GNUNET_YES; + } + else + { + GNUNET_break (0); + } + } + GNUNET_free (filename); +} + + +/** + * Received a data fragment to be saved to disk. + * + * Append data fragment to the file. + */ +static void +place_recv_save_data (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + const void *data, + uint16_t data_size) +{ + struct Place *plc = cls; + if (GNUNET_YES != plc->file_save) + return; + + char *place_pub_str = GNUNET_CRYPTO_eddsa_public_key_to_string (&plc->pub_key); + char *filename = NULL; + GNUNET_asprintf (&filename, "%s%c" "%s%c" "%s%c" "%" PRIu64 ".part", + dir_social, DIR_SEPARATOR, + "files", DIR_SEPARATOR, + place_pub_str, DIR_SEPARATOR, + GNUNET_ntohll (msg->message_id)); + GNUNET_free (place_pub_str); + if (GNUNET_SYSERR == GNUNET_DISK_directory_create_for_file (filename)) + { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "create", filename); + GNUNET_free (filename); + return; + } + + struct GNUNET_DISK_FileHandle * + fh = GNUNET_DISK_file_open (filename, GNUNET_DISK_OPEN_WRITE, + GNUNET_DISK_PERM_NONE); + if (NULL != fh) + { + if (plc->file_offset != GNUNET_DISK_file_seek + (fh, plc->file_offset, GNUNET_DISK_SEEK_SET)) { + GNUNET_log_strerror_file (GNUNET_ERROR_TYPE_ERROR, "seek", filename); + GNUNET_DISK_file_close (fh); + GNUNET_free (filename); + return; + } + GNUNET_DISK_file_write (fh, data, data_size); + GNUNET_DISK_file_close (fh); + GNUNET_free (filename); + } + else + { + GNUNET_free (filename); + GNUNET_break (0); + } + plc->file_offset += data_size; +} + + +/** + * Received end of message to be saved to disk. + * + * Remove .part ending from the filename. + */ +static void +place_recv_save_eom (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + uint8_t is_cancelled) +{ + struct Place *plc = cls; + if (GNUNET_YES != plc->file_save) + return; + + char *place_pub_str = GNUNET_CRYPTO_eddsa_public_key_to_string (&plc->pub_key); + char *fn = NULL; + GNUNET_asprintf (&fn, "%s%c%s%c%s%c%" PRIu64, + dir_social, DIR_SEPARATOR, + "files", DIR_SEPARATOR, + place_pub_str, DIR_SEPARATOR, + GNUNET_ntohll (msg->message_id)); + GNUNET_free (place_pub_str); + char *fn_part = NULL; + GNUNET_asprintf (&fn_part, "%s.part", fn); + + if (rename (fn_part, fn)) { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Failed to rename %s into %s: %s (%d)\n", + fn_part, fn, strerror (errno), errno); + } + + GNUNET_free (fn); + GNUNET_free (fn_part); +} + + +/** + * Initialize place data structure. + */ +static void +place_init (struct Place *plc) +{ + plc->slicer = GNUNET_PSYC_slicer_create (); +} + + +/** + * Add a place to the @e places hash map. + * + * @param ereq + * Entry request. + * + * @return #GNUNET_OK if the place was added + * #GNUNET_NO if the place already exists in the hash map + * #GNUNET_SYSERR on error + */ +static int +place_add (const struct PlaceEnterRequest *ereq) +{ + struct EgoPlacePublicKey ego_place_pub_key = { + .ego_pub_key = ereq->ego_pub_key, + .place_pub_key = ereq->place_pub_key, + }; + struct GNUNET_HashCode ego_place_pub_hash; + GNUNET_CRYPTO_hash (&ego_place_pub_key, sizeof (ego_place_pub_key), &ego_place_pub_hash); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + " ego_place_pub_hash = %s\n", GNUNET_h2s (&ego_place_pub_hash)); + + struct GNUNET_MessageHeader * + place_msg = GNUNET_CONTAINER_multihashmap_get (places, &ego_place_pub_hash); + if (NULL != place_msg) + return GNUNET_NO; + + place_msg = GNUNET_copy_message (&ereq->header); + if (GNUNET_OK != GNUNET_CONTAINER_multihashmap_put (places, &ego_place_pub_hash, place_msg, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST)) + { + GNUNET_break (0); + GNUNET_free (place_msg); + return GNUNET_SYSERR; + } + + return GNUNET_OK; +} + +/** + * Add a place to the @e app_places hash map. + * + * @param app_id + * Application ID. + * @param ereq + * Entry request. + * + * @return #GNUNET_OK if the place was added + * #GNUNET_NO if the place already exists in the hash map + * #GNUNET_SYSERR on error + */ +static int +app_place_add (const char *app_id, + const struct PlaceEnterRequest *ereq) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Adding app place to hashmap:\n"); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + " app_id = %s\n", app_id); + + struct GNUNET_HashCode app_id_hash; + GNUNET_CRYPTO_hash (app_id, strlen (app_id) + 1, &app_id_hash); + + struct EgoPlacePublicKey ego_place_pub_key = { + .ego_pub_key = ereq->ego_pub_key, + .place_pub_key = ereq->place_pub_key, + }; + struct GNUNET_HashCode ego_place_pub_hash; + GNUNET_CRYPTO_hash (&ego_place_pub_key, sizeof (ego_place_pub_key), &ego_place_pub_hash); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + " ego_place_pub_hash = %s\n", GNUNET_h2s (&ego_place_pub_hash)); + + struct GNUNET_CONTAINER_MultiHashMap * + app_places = GNUNET_CONTAINER_multihashmap_get (apps_places, &app_id_hash); + if (NULL == app_places) + { + app_places = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + GNUNET_CONTAINER_multihashmap_put (apps_places, &app_id_hash, app_places, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); + } + + if (GNUNET_YES == GNUNET_CONTAINER_multihashmap_contains (app_places, &ego_place_pub_hash)) + return GNUNET_NO; + + if (GNUNET_SYSERR == place_add (ereq)) + { + return GNUNET_SYSERR; + } + + if (GNUNET_OK != GNUNET_CONTAINER_multihashmap_put (app_places, &ego_place_pub_hash, NULL, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST)) + { + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +/** + * Save place entry message to disk. + * + * @param app_id + * Application ID. + * @param ereq + * Entry request message. + */ +static int +app_place_save (const char *app_id, + const struct PlaceEnterRequest *ereq) +{ + if (GNUNET_SYSERR == app_place_add (app_id, ereq)) + { + GNUNET_assert (0); + } + + if (NULL == dir_places) + return GNUNET_SYSERR; + + char *ego_pub_str = GNUNET_CRYPTO_ecdsa_public_key_to_string (&ereq->ego_pub_key); + char *place_pub_str = GNUNET_CRYPTO_eddsa_public_key_to_string (&ereq->place_pub_key); + char *filename = NULL; + GNUNET_asprintf (&filename, "%s%c" "%s%c" "%s%c" "%s", + dir_social, DIR_SEPARATOR, + "places", DIR_SEPARATOR, + ego_pub_str, DIR_SEPARATOR, + place_pub_str); + int ret = GNUNET_DISK_directory_create_for_file (filename); + if (GNUNET_OK != ret + || 0 > GNUNET_DISK_fn_write (filename, ereq, ntohs (ereq->header.size), + GNUNET_DISK_PERM_USER_READ + | GNUNET_DISK_PERM_USER_WRITE)) + { + GNUNET_break (0); + ret = GNUNET_SYSERR; + } + GNUNET_free (filename); + + if (ret == GNUNET_OK) + { + GNUNET_asprintf (&filename, "%s%c" "%s%c" "%s%c" "%s%c" "%s", + dir_social, DIR_SEPARATOR, + "apps", DIR_SEPARATOR, + app_id, DIR_SEPARATOR, + ego_pub_str, DIR_SEPARATOR, + place_pub_str); + ret = GNUNET_DISK_directory_create_for_file (filename); + if (GNUNET_OK != ret + || 0 > GNUNET_DISK_fn_write (filename, "", 0, + GNUNET_DISK_PERM_USER_READ + | GNUNET_DISK_PERM_USER_WRITE)) + { + GNUNET_break (0); + ret = GNUNET_SYSERR; + } + GNUNET_free (filename); + } + GNUNET_free (ego_pub_str); + GNUNET_free (place_pub_str); + return ret; +} + + +int +app_place_remove (const char *app_id, + const struct GNUNET_CRYPTO_EcdsaPublicKey *ego_pub_key, + const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key) +{ + struct GNUNET_HashCode ego_pub_hash; + struct GNUNET_HashCode place_pub_hash; + GNUNET_CRYPTO_hash (ego_pub_key, sizeof (*ego_pub_key), &ego_pub_hash); + GNUNET_CRYPTO_hash (place_pub_key, sizeof (*place_pub_key), &place_pub_hash); + + char *ego_pub_str = GNUNET_CRYPTO_ecdsa_public_key_to_string (ego_pub_key); + char *place_pub_str = GNUNET_CRYPTO_eddsa_public_key_to_string (place_pub_key); + char *app_place_filename = NULL; + GNUNET_asprintf (&app_place_filename, + "%s%c" "%s%c" "%s%c" "%s%c" "%s", + dir_social, DIR_SEPARATOR, + "apps", DIR_SEPARATOR, + app_id, DIR_SEPARATOR, + ego_pub_str, DIR_SEPARATOR, + place_pub_str); + GNUNET_free (ego_pub_str); + GNUNET_free (place_pub_str); + + struct GNUNET_HashCode app_id_hash; + GNUNET_CRYPTO_hash (app_id, strlen (app_id) + 1, &app_id_hash); + + struct GNUNET_CONTAINER_MultiHashMap * + app_places = GNUNET_CONTAINER_multihashmap_get (apps_places, &app_id_hash); + + if (NULL != app_places) + GNUNET_CONTAINER_multihashmap_remove (app_places, &place_pub_hash, NULL); + + int ret = GNUNET_OK; + + if (0 != unlink (app_place_filename)) + { + GNUNET_break (0); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Error removing app place file: %s: %s (%d)\n", + app_place_filename, strerror (errno), errno); + ret = GNUNET_SYSERR; + } + GNUNET_free (app_place_filename); + + return ret; +} + + +/** + * Enter place as host. + * + * @param hreq + * Host entry request. + * @param[out] ret_hst + * Returned Host struct. + * + * @return #GNUNET_YES if the host entered the place just now, + * #GNUNET_NO if the place is already entered, + * #GNUNET_SYSERR if place_pub_key was set + * but its private key was not found + */ +static int +host_enter (const struct HostEnterRequest *hreq, struct Host **ret_hst) +{ + int ret = GNUNET_NO; + struct GNUNET_HashCode place_pub_hash; + GNUNET_CRYPTO_hash (&hreq->place_pub_key, sizeof (hreq->place_pub_key), + &place_pub_hash); + struct Host *hst = GNUNET_CONTAINER_multihashmap_get (hosts, &place_pub_hash); + + if (NULL == hst) + { + hst = GNUNET_new (struct Host); + hst->policy = hreq->policy; + hst->join_reqs = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + hst->relay_msgs = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + + struct Place *plc = &hst->place; + place_init (plc); + plc->is_host = GNUNET_YES; + plc->pub_key = hreq->place_pub_key; + plc->pub_key_hash = place_pub_hash; + + GNUNET_CONTAINER_multihashmap_put (hosts, &plc->pub_key_hash, plc, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); + hst->master = GNUNET_PSYC_master_start (cfg, &hreq->place_key, hst->policy, + &psyc_master_started, + &psyc_recv_join_request, + &psyc_recv_message, NULL, hst); + plc->channel = GNUNET_PSYC_master_get_channel (hst->master); + ret = GNUNET_YES; + } + + if (NULL != ret_hst) + *ret_hst = hst; + return ret; +} + + +static int +msg_proc_parse (const struct MsgProcRequest *mpreq, + uint32_t *flags, + const char **method_prefix, + struct GNUNET_HashCode *method_hash) +{ + ssize_t method_size = ntohs (mpreq->header.size) - sizeof (*mpreq); + uint16_t offset; + + if (method_size < 0) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "MsgProcRequest has invalid size\n"); + return GNUNET_SYSERR; + } + + offset = GNUNET_STRINGS_buffer_tokenize ((const char *) &mpreq[1], + method_size, + 1, + method_prefix); + if (0 == offset || offset != method_size || *method_prefix == NULL) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "MsgProcRequest contains invalid method\n"); + return GNUNET_SYSERR; + } + GNUNET_CRYPTO_hash (*method_prefix, (size_t) method_size, method_hash); + *flags = ntohl (mpreq->flags); + return GNUNET_OK; +} + + +void +app_notify_place (const struct GNUNET_MessageHeader *msg, + struct GNUNET_SERVICE_Client *client) +{ + struct AppPlaceMessage *amsg; + struct GNUNET_MQ_Envelope *env; + uint16_t msg_size = ntohs (msg->size); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Sending place notification of type %u to client.\n", + client, ntohs (msg->type)); + switch (ntohs (msg->type)) + { + case GNUNET_MESSAGE_TYPE_SOCIAL_HOST_ENTER: + { + struct HostEnterRequest *hreq = (struct HostEnterRequest *) msg; + if (msg_size < sizeof (struct HostEnterRequest)) + return; + env = GNUNET_MQ_msg (amsg, + GNUNET_MESSAGE_TYPE_SOCIAL_APP_PLACE); + // FIXME: also notify about not entered places + amsg->place_state = GNUNET_SOCIAL_PLACE_STATE_ENTERED; + amsg->is_host = GNUNET_YES; + amsg->ego_pub_key = hreq->ego_pub_key; + amsg->place_pub_key = hreq->place_pub_key; + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), + env); + break; + } + case GNUNET_MESSAGE_TYPE_SOCIAL_GUEST_ENTER: + { + if (msg_size < sizeof (struct GuestEnterRequest)) + return; + struct GuestEnterRequest *greq = (struct GuestEnterRequest *) msg; + env = GNUNET_MQ_msg (amsg, + GNUNET_MESSAGE_TYPE_SOCIAL_APP_PLACE); + // FIXME: also notify about not entered places + amsg->place_state = GNUNET_SOCIAL_PLACE_STATE_ENTERED; + amsg->is_host = GNUNET_NO; + amsg->ego_pub_key = greq->ego_pub_key; + amsg->place_pub_key = greq->place_pub_key; + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), + env); + break; + } + default: + return; + } +} + + +void +app_notify_place_end (struct GNUNET_SERVICE_Client *client) +{ + struct GNUNET_MQ_Envelope *env; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Sending end of place list notification to client\n", + client); + env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_SOCIAL_APP_PLACE_END); + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), + env); +} + + +void +app_notify_ego (struct Ego *ego, struct GNUNET_SERVICE_Client *client) +{ + struct AppEgoMessage *emsg; + struct GNUNET_MQ_Envelope *env; + size_t name_size = strlen (ego->name) + 1; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Sending ego notification to client: %s\n", + client, ego->name); + env = GNUNET_MQ_msg_extra (emsg, + name_size, + GNUNET_MESSAGE_TYPE_SOCIAL_APP_EGO); + GNUNET_CRYPTO_ecdsa_key_get_public (&ego->key, &emsg->ego_pub_key); + GNUNET_memcpy (&emsg[1], ego->name, name_size); + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), + env); +} + + +void +app_notify_ego_end (struct GNUNET_SERVICE_Client *client) +{ + struct GNUNET_MQ_Envelope *env; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Sending end of ego list notification to client\n", + client); + env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_SOCIAL_APP_EGO_END); + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), + env); +} + + +int +app_place_entry_notify (void *cls, const struct GNUNET_HashCode *key, void *value) +{ + struct GNUNET_MessageHeader * + msg = GNUNET_CONTAINER_multihashmap_get (places, key); + if (NULL != msg) + app_notify_place (msg, cls); + return GNUNET_YES; +} + + +int +ego_entry (void *cls, const struct GNUNET_HashCode *key, void *value) +{ + app_notify_ego (value, cls); + return GNUNET_YES; +} + + +static int +check_client_msg_proc_set (void *cls, + const struct MsgProcRequest *mpreq) +{ + return GNUNET_OK; +} + + +/** + * Handle a client setting message proccesing flags for a method prefix. + */ +static void +handle_client_msg_proc_set (void *cls, + const struct MsgProcRequest *mpreq) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Place *plc = c->place; + if (NULL == plc) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + const char *method_prefix = NULL; + uint32_t flags = 0; + struct GNUNET_HashCode method_hash; + + if (GNUNET_OK != + msg_proc_parse (mpreq, &flags, &method_prefix, &method_hash)) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } +#if 0 + GNUNET_PSYC_slicer_method_remove (plc->slicer, method_prefix, + place_recv_relay_method, + place_recv_relay_modifier, + place_recv_relay_data, + place_recv_relay_eom); + GNUNET_PSYC_slicer_method_remove (plc->slicer, method_prefix, + place_recv_save_method, + NULL, + place_recv_save_data, + place_recv_save_eom); +#endif + if (flags & GNUNET_SOCIAL_MSG_PROC_RELAY) + { + GNUNET_PSYC_slicer_method_add (plc->slicer, method_prefix, NULL, + place_recv_relay_method, + place_recv_relay_modifier, + place_recv_relay_data, + place_recv_relay_eom, + plc); + } + if (flags & GNUNET_SOCIAL_MSG_PROC_SAVE) + { + GNUNET_PSYC_slicer_method_add (plc->slicer, method_prefix, NULL, + place_recv_save_method, + NULL, + place_recv_save_data, + place_recv_save_eom, + plc); + } + + /** @todo Save flags to be able to resume relaying/saving after restart */ + + GNUNET_SERVICE_client_continue (client); +} + + +/** + * Handle a connecting client requesting to clear all relay rules. + */ +static void +handle_client_msg_proc_clear (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Place *plc = c->place; + if (NULL == plc) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + GNUNET_PSYC_slicer_clear (plc->slicer); + + GNUNET_SERVICE_client_continue (client); +} + + +static int +check_client_host_enter (void *cls, + const struct HostEnterRequest *hr) +{ + return GNUNET_OK; +} + + +/** + * Handle a connecting client entering a place as host. + */ +static void +handle_client_host_enter (void *cls, + const struct HostEnterRequest *hr) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct HostEnterRequest * + hreq = (struct HostEnterRequest *) GNUNET_copy_message (&hr->header); + + uint8_t app_id_size = ntohs (hreq->header.size) - sizeof (*hreq); + const char *app_id = NULL; + uint16_t offset = GNUNET_STRINGS_buffer_tokenize ((const char *) &hreq[1], + app_id_size, 1, &app_id); + if (0 == offset || offset != app_id_size || app_id == NULL) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "offset = %u, app_id_size = %u, app_id = %s\n", + offset, app_id_size, app_id); + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + struct Host *hst = NULL; + struct Place *plc = NULL; + int ret = GNUNET_OK; + + struct GNUNET_CRYPTO_EddsaPublicKey empty_pub_key; + memset (&empty_pub_key, 0, sizeof (empty_pub_key)); + + if (0 == memcmp (&hreq->place_pub_key, &empty_pub_key, sizeof (empty_pub_key))) + { // no public key set: create new private key & save the place + struct GNUNET_CRYPTO_EddsaPrivateKey * + place_key = GNUNET_CRYPTO_eddsa_key_create (); + hreq->place_key = *place_key; + GNUNET_CRYPTO_eddsa_key_get_public (place_key, &hreq->place_pub_key); + GNUNET_CRYPTO_eddsa_key_clear (place_key); + GNUNET_free (place_key); + + app_place_save (app_id, (const struct PlaceEnterRequest *) hreq); + } + + switch (host_enter (hreq, &hst)) + { + case GNUNET_YES: + plc = c->place = &hst->place; + plc->host = hst; + break; + + case GNUNET_NO: + { + plc = c->place = &hst->place; + plc->host = hst; + client_send_host_enter_ack (client, hst, GNUNET_OK); + break; + } + case GNUNET_SYSERR: + ret = GNUNET_SYSERR; + } + + if (ret != GNUNET_SYSERR) + { + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Client connected as host to place %s.\n", + hst, GNUNET_h2s (&plc->pub_key_hash)); + + struct ClientListItem *cli = GNUNET_new (struct ClientListItem); + cli->client = client; + GNUNET_CONTAINER_DLL_insert (plc->clients_head, plc->clients_tail, cli); + c->place = plc; + app_notify_place (&hreq->header, client); + } + + GNUNET_CRYPTO_eddsa_key_clear (&hreq->place_key); + GNUNET_free (hreq); + + if (GNUNET_OK == ret) + GNUNET_SERVICE_client_continue (client); + else + GNUNET_SERVICE_client_drop (client); +} + + +/** + * Enter place as guest. + * + * @param greq + * Guest entry request. + * @param[out] ret_gst + * Returned Guest struct. + * + * @return #GNUNET_YES if the guest entered the place just now, + * #GNUNET_NO if the place is already entered, + * #GNUNET_SYSERR on error. + */ +static int +guest_enter (const struct GuestEnterRequest *greq, struct Guest **ret_gst) +{ + int ret = GNUNET_NO; + uint16_t greq_size = ntohs (greq->header.size); + + struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key = greq->ego_pub_key; + struct GNUNET_HashCode ego_pub_hash; + GNUNET_CRYPTO_hash (&ego_pub_key, sizeof (ego_pub_key), &ego_pub_hash); + struct Ego *ego = GNUNET_CONTAINER_multihashmap_get (egos, &ego_pub_hash); + + if (NULL == ego) + { + return GNUNET_SYSERR; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "entering as guest\n"); + struct GNUNET_HashCode place_pub_hash; + GNUNET_CRYPTO_hash (&greq->place_pub_key, sizeof (greq->place_pub_key), + &place_pub_hash); + + struct GNUNET_CONTAINER_MultiHashMap * + plc_gst = GNUNET_CONTAINER_multihashmap_get (place_guests, &place_pub_hash); + struct Guest *gst = NULL; + int new_guest; + + if (NULL != plc_gst) + gst = GNUNET_CONTAINER_multihashmap_get (plc_gst, &ego_pub_hash); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "plc_gst = %p, gst = %p\n", + plc_gst, + gst); + + if (NULL == gst) + { + gst = GNUNET_new (struct Guest); + new_guest = GNUNET_YES; + } + else new_guest = GNUNET_NO; + + if (NULL == gst->slave) + { + gst->origin = greq->origin; + gst->relay_count = ntohl (greq->relay_count); + + uint16_t len; + uint16_t remaining = ntohs (greq->header.size) - sizeof (*greq); + const char *app_id = (const char *) &greq[1]; + const char *p = app_id; + + len = strnlen (app_id, remaining); + if (len == remaining) + { + GNUNET_free (gst); + GNUNET_break (0); + return GNUNET_SYSERR; + } + p += len + 1; + remaining -= len + 1; + + const struct GNUNET_PeerIdentity *relays = NULL; + uint16_t relay_size = gst->relay_count * sizeof (*relays); + if (remaining < relay_size) + { + GNUNET_free (gst); + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (0 < relay_size) + relays = (const struct GNUNET_PeerIdentity *) p; + p += relay_size; + remaining -= relay_size; + + struct GNUNET_PSYC_Message *join_msg = NULL; + uint16_t join_msg_size = 0; + + if (sizeof (struct GNUNET_MessageHeader) <= remaining) + { + join_msg = (struct GNUNET_PSYC_Message *) p; + join_msg_size = ntohs (join_msg->header.size); + p += join_msg_size; + remaining -= join_msg_size; + } + if (0 != remaining) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%zu + %u + %u != %u\n", + sizeof (*greq), relay_size, join_msg_size, greq_size); + GNUNET_free (gst); + GNUNET_break (0); + return GNUNET_SYSERR; + } + if (0 < relay_size) + { + gst->relays = GNUNET_malloc (relay_size); + GNUNET_memcpy (gst->relays, relays, relay_size); + } + + gst->join_flags = ntohl (greq->flags); + + struct Place *plc = &gst->place; + place_init (plc); + plc->is_host = GNUNET_NO; + plc->pub_key = greq->place_pub_key; + plc->pub_key_hash = place_pub_hash; + plc->ego_pub_key = ego_pub_key; + plc->ego_pub_hash = ego_pub_hash; + plc->ego_key = ego->key; + + if (NULL == plc_gst) + { + plc_gst = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES); + (void) GNUNET_CONTAINER_multihashmap_put (place_guests, &plc->pub_key_hash, plc_gst, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); + } + if (GNUNET_YES == new_guest) + { + (void) GNUNET_CONTAINER_multihashmap_put (plc_gst, &plc->ego_pub_hash, gst, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); + (void) GNUNET_CONTAINER_multihashmap_put (guests, &plc->pub_key_hash, gst, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_MULTIPLE); + + } + gst->slave + = GNUNET_PSYC_slave_join (cfg, &plc->pub_key, &plc->ego_key, + gst->join_flags, &gst->origin, + gst->relay_count, gst->relays, + &psyc_recv_message, NULL, + &psyc_slave_connected, + &psyc_recv_join_dcsn, + gst, join_msg); + plc->channel = GNUNET_PSYC_slave_get_channel (gst->slave); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "slave entered channel %p\n", + plc->channel); + ret = GNUNET_YES; + } + + // TODO: explain to automatic code scanners why free(gst) not necessary + if (NULL != ret_gst) + *ret_gst = gst; + return ret; +} + + +static int +client_guest_enter (struct Client *c, + const struct GuestEnterRequest *greq) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "client_guest_enter\n"); + struct GNUNET_PSYC_CountersResultMessage *result_msg; + struct GNUNET_MQ_Envelope *env; + struct GNUNET_SERVICE_Client *client = c->client; + uint16_t remaining = ntohs (greq->header.size) - sizeof (*greq); + const char *app_id = NULL; + uint16_t offset = GNUNET_STRINGS_buffer_tokenize ((const char *) &greq[1], + remaining, 1, &app_id); + struct Guest *gst = NULL; + struct Place *plc = NULL; + + if (0 == offset) + { + return GNUNET_SYSERR; + } + switch (guest_enter (greq, &gst)) + { + case GNUNET_YES: + { + plc = c->place = &gst->place; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "guest entered successfully to local place %s\n", + GNUNET_h2s (&plc->pub_key_hash)); + plc->guest = gst; + app_place_save (app_id, (const struct PlaceEnterRequest *) greq); + app_notify_place (&greq->header, client); + break; + } + case GNUNET_NO: + { + plc = c->place = &gst->place; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "guest re-entered successfully to local place %s\n", + GNUNET_h2s (&plc->pub_key_hash)); + plc->guest = gst; + env = GNUNET_MQ_msg (result_msg, + GNUNET_MESSAGE_TYPE_SOCIAL_GUEST_ENTER_ACK); + result_msg->result_code = htonl (GNUNET_OK); + result_msg->max_message_id = GNUNET_htonll (plc->max_message_id); + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), + env); + if (NULL != gst->join_dcsn) + { + env = GNUNET_MQ_msg_copy (&gst->join_dcsn->header); + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), + env); + } + break; + } + case GNUNET_SYSERR: + { + return GNUNET_SYSERR; + } + } + + struct ClientListItem *cli = GNUNET_new (struct ClientListItem); + cli->client = client; + GNUNET_CONTAINER_DLL_insert (plc->clients_head, plc->clients_tail, cli); + return GNUNET_OK; +} + + +static int +check_client_guest_enter (void *cls, + const struct GuestEnterRequest *greq) +{ + return GNUNET_OK; +} + + +/** + * Handle a connecting client entering a place as guest. + */ +static void +handle_client_guest_enter (void *cls, + const struct GuestEnterRequest *greq) +{ + struct Client *c = cls; + + if (GNUNET_SYSERR == client_guest_enter (c, greq)) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (c->client); + return; + } + GNUNET_SERVICE_client_continue (c->client); +} + + +struct GuestEnterByNameClosure +{ + struct Client *client; + char *app_id; + char *password; + struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key; + struct GNUNET_MessageHeader *join_msg; +}; + + +/** + * Result of a GNS name lookup for entering a place. + * + * @see GNUNET_SOCIAL_guest_enter_by_name + */ +static void +gns_result_guest_enter (void *cls, uint32_t rd_count, + const struct GNUNET_GNSRECORD_Data *rd) +{ + struct GuestEnterByNameClosure *gcls = cls; + struct Client *c = gcls->client; + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p GNS result: %u records.\n", + c, rd_count); + + const struct GNUNET_GNSRECORD_PlaceData * + rec = (const struct GNUNET_GNSRECORD_PlaceData *) rd->data; + + if (0 == rd_count || rd->data_size < sizeof (*rec)) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (c->client); + return; + } + + uint16_t relay_count = ntohl (rec->relay_count); + struct GNUNET_PeerIdentity *relays = NULL; + + if (0 < relay_count) + { + if (rd->data_size == sizeof (*rec) + relay_count * sizeof (struct GNUNET_PeerIdentity)) + { + relays = (struct GNUNET_PeerIdentity *) &rec[1]; + } + else + { + relay_count = 0; + GNUNET_break_op (0); + } + } + + uint16_t app_id_size = strlen (gcls->app_id) + 1; + uint16_t relay_size = relay_count * sizeof (*relays); + uint16_t join_msg_size = 0; + if (NULL != gcls->join_msg) + join_msg_size = ntohs (gcls->join_msg->size); + uint16_t greq_size = sizeof (struct GuestEnterRequest) + + app_id_size + relay_size + join_msg_size; + struct GuestEnterRequest *greq = GNUNET_malloc (greq_size); + greq->header.size = htons (greq_size); + greq->header.type = htons (GNUNET_MESSAGE_TYPE_SOCIAL_GUEST_ENTER); + greq->ego_pub_key = gcls->ego_pub_key; + greq->place_pub_key = rec->place_pub_key; + greq->origin = rec->origin; + greq->relay_count = rec->relay_count; + + void *p = &greq[1]; + GNUNET_memcpy (p, gcls->app_id, app_id_size); + p += app_id_size; + GNUNET_memcpy (p, relays, relay_size); + p += relay_size; + GNUNET_memcpy (p, gcls->join_msg, join_msg_size); + + client_guest_enter (c, greq); + + GNUNET_free (gcls->app_id); + if (NULL != gcls->password) + GNUNET_free (gcls->password); + if (NULL != gcls->join_msg) + GNUNET_free (gcls->join_msg); + GNUNET_free (gcls); + GNUNET_free (greq); +} + + +static int +check_client_guest_enter_by_name (void *cls, + const struct GuestEnterByNameRequest *greq) +{ + return GNUNET_OK; +} + + +/** + * Handle a connecting client entering a place as guest using a GNS address. + * + * Look up GNS address and generate a GuestEnterRequest from that. + */ +static void +handle_client_guest_enter_by_name (void *cls, + const struct GuestEnterByNameRequest *greq) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + + struct GuestEnterByNameClosure *gcls = GNUNET_malloc (sizeof (*gcls)); + gcls->client = c; + gcls->ego_pub_key = greq->ego_pub_key; + + const char *p = (const char *) &greq[1]; + const char *app_id = NULL, *password = NULL, *gns_name = NULL; + uint16_t remaining = ntohs (greq->header.size) - sizeof (*greq); + uint16_t offset = GNUNET_STRINGS_buffer_tokenize (p, remaining, 3, + &app_id, + &gns_name, + &password); + p += offset; + remaining -= offset; + + if (0 != offset && sizeof (*gcls->join_msg) <= remaining) + { + gcls->join_msg = GNUNET_copy_message ((struct GNUNET_MessageHeader *) p); + remaining -= ntohs (gcls->join_msg->size); + } + + if (0 == offset || 0 != remaining) + { + if (NULL != gcls->join_msg) + GNUNET_free (gcls->join_msg); + GNUNET_free (gcls); + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + uint16_t app_id_size = strlen (app_id) + 1; + gcls->app_id = GNUNET_malloc (app_id_size); + GNUNET_memcpy (gcls->app_id, app_id, app_id_size); + + uint16_t password_size = strlen (password); + if (0 < password_size++) + { + gcls->password = GNUNET_malloc (password_size); + GNUNET_memcpy (gcls->password, password, password_size); + } + + GNUNET_GNS_lookup (gns, gns_name, + &greq->ego_pub_key, + GNUNET_GNSRECORD_TYPE_PLACE, + GNUNET_GNS_LO_DEFAULT, + &gns_result_guest_enter, gcls); + GNUNET_SERVICE_client_continue (client); +} + + +static int +check_client_app_connect (void *cls, + const struct AppConnectRequest *creq) +{ + return GNUNET_OK; +} + + +/** + * Handle application connection. + */ +static void +handle_client_app_connect (void *cls, + const struct AppConnectRequest *creq) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + ssize_t app_id_size = ntohs (creq->header.size) - sizeof (*creq); + const char *app_id = NULL; + uint16_t offset; + + if (app_id_size < 0) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "AppConnectRequest has invalid size\n"); + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + offset = GNUNET_STRINGS_buffer_tokenize ((const char *) &creq[1], + (size_t) app_id_size, + 1, + &app_id); + if (0 == offset || offset != app_id_size) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "AppConnectRequest contains invalid app ID\n"); + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + struct GNUNET_HashCode app_id_hash; + GNUNET_CRYPTO_hash (app_id, (size_t) app_id_size, &app_id_hash); + + GNUNET_CONTAINER_multihashmap_iterate (egos, ego_entry, client); + app_notify_ego_end (client); + + struct GNUNET_CONTAINER_MultiHashMap * + app_places = GNUNET_CONTAINER_multihashmap_get (apps_places, &app_id_hash); + if (NULL != app_places) + GNUNET_CONTAINER_multihashmap_iterate (app_places, app_place_entry_notify, client); + app_notify_place_end (client); + + struct ClientListItem *cli = GNUNET_new (struct ClientListItem); + cli->client = client; + struct Application *app = GNUNET_CONTAINER_multihashmap_get (apps, + &app_id_hash); + if (NULL == app) { + app = GNUNET_malloc (sizeof (*app)); + (void) GNUNET_CONTAINER_multihashmap_put (apps, &app_id_hash, app, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); + } + GNUNET_CONTAINER_DLL_insert (app->clients_head, app->clients_tail, cli); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Application %s connected.\n", app, app_id); + + c->app_id = GNUNET_malloc ((size_t) app_id_size); + GNUNET_memcpy (c->app_id, app_id, (size_t) app_id_size); + + GNUNET_SERVICE_client_continue (client); +} + + +/** + * Handle application detach request. + */ +static void +handle_client_app_detach (void *cls, + const struct AppDetachRequest *req) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + + int ret = app_place_remove (c->app_id, &req->ego_pub_key, &req->place_pub_key); + client_send_result (client, req->op_id, ret, NULL, 0); + + GNUNET_SERVICE_client_continue (client); +} + + +static void +place_leave_cb (void *cls) +{ + struct Place *plc = cls; + + place_send_leave_ack (plc); + (GNUNET_YES == plc->is_host) + ? cleanup_host ((struct Host *) plc) + : cleanup_guest ((struct Guest *) plc); +} + + +/** + * Handle application leave request. + */ +static void +handle_client_place_leave (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Place *plc = c->place; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "got leave request from %s for place %s", + plc->is_host? "host" : "slave", + GNUNET_h2s (&plc->pub_key_hash)); + if (NULL == plc) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + if (GNUNET_YES != plc->is_disconnecting) + { + plc->is_disconnecting = GNUNET_YES; + if (plc->is_host) + { + struct Host *host = plc->host; + GNUNET_assert (NULL != host); + GNUNET_PSYC_master_stop (host->master, GNUNET_NO, &place_leave_cb, plc); + } + else + { + struct Guest *guest = plc->guest; + GNUNET_assert (NULL != guest); + GNUNET_PSYC_slave_part (guest->slave, GNUNET_NO, &place_leave_cb, plc); + } + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "got leave request but place is already leaving\n"); + } + GNUNET_SERVICE_client_continue (client); +} + + +struct JoinDecisionClosure +{ + int32_t is_admitted; + struct GNUNET_PSYC_Message *msg; +}; + + +/** + * Iterator callback for responding to join requests. + */ +static int +psyc_send_join_decision (void *cls, const struct GNUNET_HashCode *pub_key_hash, + void *value) +{ + struct JoinDecisionClosure *jcls = cls; + struct GNUNET_PSYC_JoinHandle *jh = value; + // FIXME: add relays + GNUNET_PSYC_join_decision (jh, jcls->is_admitted, 0, NULL, jcls->msg); + return GNUNET_YES; +} + + +static int +check_client_join_decision (void *cls, + const struct GNUNET_PSYC_JoinDecisionMessage *dcsn) +{ + return GNUNET_OK; +} + + +/** + * Handle an entry decision from a host client. + */ +static void +handle_client_join_decision (void *cls, + const struct GNUNET_PSYC_JoinDecisionMessage *dcsn) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Place *plc = c->place; + if (NULL == plc || GNUNET_YES != plc->is_host) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + struct Host *hst = plc->host; + + struct JoinDecisionClosure jcls; + jcls.is_admitted = ntohl (dcsn->is_admitted); + jcls.msg + = (sizeof (*dcsn) + sizeof (*jcls.msg) <= ntohs (dcsn->header.size)) + ? (struct GNUNET_PSYC_Message *) &dcsn[1] + : NULL; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "jcls.msg = %p\n", + jcls.msg); + struct GNUNET_HashCode slave_pub_hash; + GNUNET_CRYPTO_hash (&dcsn->slave_pub_key, sizeof (dcsn->slave_pub_key), + &slave_pub_hash); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Got join decision (%d) from client for place %s..\n", + hst, jcls.is_admitted, GNUNET_h2s (&plc->pub_key_hash)); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p ..and slave %s.\n", + hst, GNUNET_h2s (&slave_pub_hash)); + + GNUNET_CONTAINER_multihashmap_get_multiple (hst->join_reqs, &slave_pub_hash, + &psyc_send_join_decision, &jcls); + GNUNET_CONTAINER_multihashmap_remove_all (hst->join_reqs, &slave_pub_hash); + + GNUNET_SERVICE_client_continue (client); +} + + +/** + * Send acknowledgement to a client. + * + * Sent after a message fragment has been passed on to multicast. + * + * @param plc The place struct for the client. + */ +static void +send_message_ack (struct Place *plc, struct GNUNET_SERVICE_Client *client) +{ + struct GNUNET_MQ_Envelope *env; + + env = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_ACK); + GNUNET_MQ_send (GNUNET_SERVICE_client_get_mq (client), + env); +} + + +/** + * Proceed to the next message part in the transmission queue. + * + * @param plc + * Place where the transmission is going on. + * @param tmit_msg + * Currently transmitted message. + * @param tmit_frag + * Currently transmitted message fragment. + * + * @return @a tmit_frag, or NULL if reached the end of fragment. + */ +static struct FragmentTransmitQueue * +psyc_transmit_queue_next_part (struct Place *plc, + struct MessageTransmitQueue *tmit_msg, + struct FragmentTransmitQueue *tmit_frag) +{ + uint16_t psize = ntohs (tmit_frag->next_part->size); + if ((char *) tmit_frag->next_part + psize - ((char *) &tmit_frag[1]) + < tmit_frag->size) + { + tmit_frag->next_part + = (struct GNUNET_MessageHeader *) ((char *) tmit_frag->next_part + psize); + } + else /* Reached end of current fragment. */ + { + if (NULL != tmit_frag->client) + send_message_ack (plc, tmit_frag->client); + GNUNET_CONTAINER_DLL_remove (tmit_msg->frags_head, tmit_msg->frags_tail, tmit_frag); + GNUNET_free (tmit_frag); + tmit_frag = NULL; + } + return tmit_frag; +} + + +/** + * Proceed to next message in transmission queue. + * + * @param plc + * Place where the transmission is going on. + * @param tmit_msg + * Currently transmitted message. + * + * @return The next message in queue, or NULL if queue is empty. + */ +static struct MessageTransmitQueue * +psyc_transmit_queue_next_msg (struct Place *plc, + struct MessageTransmitQueue *tmit_msg) +{ + GNUNET_CONTAINER_DLL_remove (plc->tmit_msgs_head, plc->tmit_msgs_tail, tmit_msg); + GNUNET_free (tmit_msg); + return plc->tmit_msgs_head; +} + + +/** + * Callback for data transmission to PSYC. + */ +static int +psyc_transmit_notify_data (void *cls, uint16_t *data_size, void *data) +{ + struct Place *plc = cls; + struct MessageTransmitQueue *tmit_msg = plc->tmit_msgs_head; + GNUNET_assert (NULL != tmit_msg); + struct FragmentTransmitQueue *tmit_frag = tmit_msg->frags_head; + if (NULL == tmit_frag) + { /* Rest of the message have not arrived yet, pause transmission */ + *data_size = 0; + return GNUNET_NO; + } + struct GNUNET_MessageHeader *pmsg = tmit_frag->next_part; + if (NULL == pmsg) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p psyc_transmit_notify_data: nothing to send.\n", plc); + *data_size = 0; + return GNUNET_NO; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p psyc_transmit_notify_data()\n", plc); + GNUNET_PSYC_log_message (GNUNET_ERROR_TYPE_DEBUG, pmsg); + + uint16_t ptype = ntohs (pmsg->type); + uint16_t pdata_size = ntohs (pmsg->size) - sizeof (*pmsg); + int ret; + + switch (ptype) + { + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_DATA: + if (*data_size < pdata_size) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p psyc_transmit_notify_data: buffer size too small for data.\n", plc); + *data_size = 0; + return GNUNET_NO; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p psyc_transmit_notify_data: sending %u bytes.\n", + plc, pdata_size); + + *data_size = pdata_size; + GNUNET_memcpy (data, &pmsg[1], *data_size); + ret = GNUNET_NO; + break; + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END: + *data_size = 0; + ret = GNUNET_YES; + break; + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL: + *data_size = 0; + ret = GNUNET_SYSERR; + break; + + default: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "%p psyc_transmit_notify_data: unexpected message part of type %u.\n", + plc, ptype); + ret = GNUNET_SYSERR; + } + + if (GNUNET_SYSERR == ret && GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL != ptype) + { + *data_size = 0; + tmit_msg = psyc_transmit_queue_next_msg (plc, tmit_msg); + GNUNET_SERVICE_client_drop (tmit_frag->client); + GNUNET_SCHEDULER_add_now (&cleanup_place, plc); + return ret; + } + else + { + tmit_frag = psyc_transmit_queue_next_part (plc, tmit_msg, tmit_frag); + if (NULL != tmit_frag) + { + struct GNUNET_MessageHeader *pmsg = tmit_frag->next_part; + ptype = ntohs (pmsg->type); + switch (ptype) + { + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END: + ret = GNUNET_YES; + break; + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL: + ret = GNUNET_SYSERR; + break; + } + switch (ptype) + { + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END: + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL: + tmit_frag = psyc_transmit_queue_next_part (plc, tmit_msg, tmit_frag); + } + } + + if (NULL == tmit_msg->frags_head + && GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END <= ptype) + { /* Reached end of current message. */ + tmit_msg = psyc_transmit_queue_next_msg (plc, tmit_msg); + } + } + + if (ret != GNUNET_NO) + { + if (NULL != tmit_msg) + { + psyc_transmit_message (plc); + } + /* FIXME: handle partial message (when still in_transmit) */ + } + return ret; +} + + +/** + * Callback for modifier transmission to PSYC. + */ +static int +psyc_transmit_notify_mod (void *cls, uint16_t *data_size, void *data, + uint8_t *oper, uint32_t *full_value_size) +{ + struct Place *plc = cls; + struct MessageTransmitQueue *tmit_msg = plc->tmit_msgs_head; + GNUNET_assert (NULL != tmit_msg); + struct FragmentTransmitQueue *tmit_frag = tmit_msg->frags_head; + if (NULL == tmit_frag) + { /* Rest of the message have not arrived yet, pause transmission */ + *data_size = 0; + return GNUNET_NO; + } + struct GNUNET_MessageHeader *pmsg = tmit_frag->next_part; + if (NULL == pmsg) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p psyc_transmit_notify_mod: nothing to send.\n", plc); + *data_size = 0; + return GNUNET_NO; + } + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p psyc_transmit_notify_mod()\n", plc); + GNUNET_PSYC_log_message (GNUNET_ERROR_TYPE_DEBUG, pmsg); + + uint16_t ptype = ntohs (pmsg->type); + int ret; + + switch (ptype) + { + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER: + { + if (NULL == oper) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%p psyc_transmit_notify_mod: oper is NULL.\n", plc); + ret = GNUNET_SYSERR; + break; + } + struct GNUNET_PSYC_MessageModifier * + pmod = (struct GNUNET_PSYC_MessageModifier *) tmit_frag->next_part; + uint16_t mod_size = ntohs (pmod->header.size) - sizeof (*pmod); + + if (*data_size < mod_size) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p psyc_transmit_notify_mod: buffer size too small for data.\n", plc); + *data_size = 0; + return GNUNET_NO; + } + + *full_value_size = ntohl (pmod->value_size); + *oper = pmod->oper; + *data_size = mod_size; + GNUNET_memcpy (data, &pmod[1], mod_size); + ret = GNUNET_NO; + break; + } + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT: + { + if (NULL != oper) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%p psyc_transmit_notify_mod: oper is not NULL.\n", plc); + ret = GNUNET_SYSERR; + break; + } + uint16_t mod_size = ntohs (pmsg->size) - sizeof (*pmsg); + if (*data_size < mod_size) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p psyc_transmit_notify_mod: buffer size too small for data.\n", plc); + *data_size = 0; + return GNUNET_NO; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p psyc_transmit_notify_mod: sending %u bytes.\n", plc, mod_size); + + *data_size = mod_size; + GNUNET_memcpy (data, &pmsg[1], *data_size); + ret = GNUNET_NO; + break; + } + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_DATA: + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END: + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL: + *data_size = 0; + ret = GNUNET_YES; + break; + + default: + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "%p psyc_transmit_notify_mod: unexpected message part of type %u.\n", + plc, ptype); + ret = GNUNET_SYSERR; + } + + if (GNUNET_SYSERR == ret) + { + *data_size = 0; + ret = GNUNET_SYSERR; + tmit_msg = psyc_transmit_queue_next_msg (plc, tmit_msg); + GNUNET_SERVICE_client_drop (tmit_frag->client); + GNUNET_SCHEDULER_add_now (&cleanup_place, plc); + } + else + { + if (GNUNET_YES != ret) + psyc_transmit_queue_next_part (plc, tmit_msg, tmit_frag); + + if (NULL == tmit_msg->frags_head + && GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END <= ptype) + { /* Reached end of current message. */ + tmit_msg = psyc_transmit_queue_next_msg (plc, tmit_msg); + } + } + return ret; +} + +/** + * Callback for data transmission from a host to PSYC. + */ +static int +host_transmit_notify_data (void *cls, uint16_t *data_size, void *data) +{ + int ret = psyc_transmit_notify_data (cls, data_size, data); + + if (GNUNET_NO != ret) + { + struct Host *hst = cls; + hst->tmit_handle = NULL; + } + return ret; +} + + +/** + * Callback for the transmit functions of multicast. + */ +static int +guest_transmit_notify_data (void *cls, uint16_t *data_size, void *data) +{ + int ret = psyc_transmit_notify_data (cls, data_size, data); + + if (GNUNET_NO != ret) + { + struct Guest *gst = cls; + gst->tmit_handle = NULL; + } + return ret; +} + + +/** + * Callback for modifier transmission from a host to PSYC. + */ +static int +host_transmit_notify_mod (void *cls, uint16_t *data_size, void *data, + uint8_t *oper, uint32_t *full_value_size) +{ + int ret = psyc_transmit_notify_mod (cls, data_size, data, + oper, full_value_size); + if (GNUNET_SYSERR == ret) + { + struct Host *hst = cls; + hst->tmit_handle = NULL; + } + return ret; +} + + +/** + * Callback for modifier transmission from a guest to PSYC. + */ +static int +guest_transmit_notify_mod (void *cls, uint16_t *data_size, void *data, + uint8_t *oper, uint32_t *full_value_size) +{ + int ret = psyc_transmit_notify_mod (cls, data_size, data, + oper, full_value_size); + if (GNUNET_SYSERR == ret) + { + struct Guest *gst = cls; + gst->tmit_handle = NULL; + } + return ret; +} + + +/** + * Get method part of next message from transmission queue. + * + * @param plc + * Place + * + * @return #GNUNET_OK on success + * #GNUNET_NO if there are no more messages in queue. + * #GNUNET_SYSERR if the next message is malformed. + */ +static struct GNUNET_PSYC_MessageMethod * +psyc_transmit_queue_next_method (struct Place *plc) +{ + struct MessageTransmitQueue *tmit_msg = plc->tmit_msgs_head; + if (NULL == tmit_msg) + return GNUNET_NO; + + struct FragmentTransmitQueue *tmit_frag = tmit_msg->frags_head; + if (NULL == tmit_frag) + { + GNUNET_break (0); + return GNUNET_NO; + } + + struct GNUNET_MessageHeader *pmsg = tmit_frag->next_part; + if (NULL == pmsg + || GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD != ntohs (pmsg->type)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "%p psyc_transmit_queue_next_method: unexpected message part of type %u.\n", + plc, NULL != pmsg ? ntohs (pmsg->type) : 0); + GNUNET_break (0); + return NULL; + } + + uint16_t psize = ntohs (pmsg->size); + struct GNUNET_PSYC_MessageMethod * + pmeth = (struct GNUNET_PSYC_MessageMethod *) GNUNET_copy_message (pmsg); + + if (psize < sizeof (*pmeth) + 1 || '\0' != *((char *) pmeth + psize - 1)) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "%p psyc_transmit_queue_next_method: invalid method name.\n", + plc); + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "%zu <= %u || NUL != %u\n", + sizeof (*pmeth), psize, *((char *) pmeth + psize - 1)); + GNUNET_break (0); + GNUNET_free (pmeth); + return NULL; + } + + psyc_transmit_queue_next_part (plc, tmit_msg, tmit_frag); + return pmeth; +} + + +/** + * Transmit the next message in queue from the host to the PSYC channel. + */ +static int +psyc_master_transmit_message (struct Host *hst) +{ + struct Place *plc = &hst->place; + + if (NULL == hst->tmit_handle) + { + struct GNUNET_PSYC_MessageMethod * + pmeth = psyc_transmit_queue_next_method (plc); + if (NULL == pmeth) + return GNUNET_SYSERR; + + hst->tmit_handle = (void *) &hst->tmit_handle; + struct GNUNET_PSYC_MasterTransmitHandle * + tmit_handle = GNUNET_PSYC_master_transmit (hst->master, (const char *) &pmeth[1], + &host_transmit_notify_mod, + &host_transmit_notify_data, hst, + pmeth->flags); + if (NULL != hst->tmit_handle) + hst->tmit_handle = tmit_handle; + GNUNET_free (pmeth); + } + else + { + GNUNET_PSYC_master_transmit_resume (hst->tmit_handle); + } + return GNUNET_OK; +} + + +/** + * Transmit the next message in queue from a guest to the PSYC channel. + */ +static int +psyc_slave_transmit_message (struct Guest *gst) +{ + struct Place *plc = &gst->place; + + if (NULL == gst->tmit_handle) + { + struct GNUNET_PSYC_MessageMethod * + pmeth = psyc_transmit_queue_next_method (plc); + if (NULL == pmeth) + return GNUNET_SYSERR; + + gst->tmit_handle = (void *) &gst->tmit_handle; + struct GNUNET_PSYC_SlaveTransmitHandle * + tmit_handle = GNUNET_PSYC_slave_transmit (gst->slave, (const char *) &pmeth[1], + &guest_transmit_notify_mod, + &guest_transmit_notify_data, gst, + pmeth->flags); + if (NULL != gst->tmit_handle) + gst->tmit_handle = tmit_handle; + GNUNET_free (pmeth); + } + else + { + GNUNET_PSYC_slave_transmit_resume (gst->tmit_handle); + } + return GNUNET_OK; +} + + +/** + * Transmit a message to PSYC. + */ +static int +psyc_transmit_message (struct Place *plc) +{ + return + (plc->is_host) + ? psyc_master_transmit_message ((struct Host *) plc) + : psyc_slave_transmit_message ((struct Guest *) plc); +} + + +/** + * Queue message parts for sending to PSYC. + * + * @param plc Place to send to. + * @param client Client the message originates from. + * @param data_size Size of @a data. + * @param data Concatenated message parts. + * @param first_ptype First message part type in @a data. + * @param last_ptype Last message part type in @a data. + */ +static struct MessageTransmitQueue * +psyc_transmit_queue_message (struct Place *plc, + struct GNUNET_SERVICE_Client *client, + size_t data_size, + const void *data, + uint16_t first_ptype, uint16_t last_ptype, + struct MessageTransmitQueue *tmit_msg) +{ + if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_METHOD == first_ptype) + { + tmit_msg = GNUNET_malloc (sizeof (*tmit_msg)); + GNUNET_CONTAINER_DLL_insert_tail (plc->tmit_msgs_head, plc->tmit_msgs_tail, tmit_msg); + } + else if (NULL == tmit_msg) + { + return NULL; + } + + struct FragmentTransmitQueue * + tmit_frag = GNUNET_malloc (sizeof (*tmit_frag) + data_size); + GNUNET_memcpy (&tmit_frag[1], data, data_size); + tmit_frag->next_part = (struct GNUNET_MessageHeader *) &tmit_frag[1]; + tmit_frag->client = client; + tmit_frag->size = data_size; + + GNUNET_CONTAINER_DLL_insert_tail (tmit_msg->frags_head, tmit_msg->frags_tail, tmit_frag); + tmit_msg->client = client; + return tmit_msg; +} + + +///** +// * Cancel transmission of current message to PSYC. +// * +// * @param plc Place to send to. +// * @param client Client the message originates from. +// */ +//static void +//psyc_transmit_cancel (struct Place *plc, struct GNUNET_SERVICE_Client *client) +//{ +// uint16_t type = GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_CANCEL; +// +// struct GNUNET_MessageHeader msg; +// msg.size = htons (sizeof (msg)); +// msg.type = htons (type); +// +// psyc_transmit_queue_message (plc, client, sizeof (msg), &msg, type, type, NULL); +// psyc_transmit_message (plc); +// +// /* FIXME: cleanup */ +//} + + +static int +check_client_psyc_message (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + return GNUNET_OK; +} + + +/** + * Handle an incoming message from a client, to be transmitted to the place. + */ +static void +handle_client_psyc_message (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Place *plc = c->place; + int ret; + + if (NULL == plc) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "received PSYC message for non-existing client %p\n", + client); + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Received message of type %d from client.\n", plc, ntohs (msg->type)); + GNUNET_PSYC_log_message (GNUNET_ERROR_TYPE_DEBUG, msg); + + if (GNUNET_YES != plc->is_ready) + { + GNUNET_log (GNUNET_ERROR_TYPE_WARNING, + "%p Place is not ready yet, disconnecting client.\n", plc); + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + uint16_t size = ntohs (msg->size); + uint16_t psize = size - sizeof (*msg); + if (psize < sizeof (struct GNUNET_MessageHeader) + || GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD < psize) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%p Received message with invalid payload size (%u) from client.\n", + plc, psize); + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + uint16_t first_ptype = 0; + uint16_t last_ptype = 0; + if (GNUNET_SYSERR == + GNUNET_PSYC_receive_check_parts (psize, (const char *) &msg[1], + &first_ptype, &last_ptype)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%p Received invalid message part from client.\n", plc); + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Received message with first part type %u and last part type %u.\n", + plc, first_ptype, last_ptype); + + c->tmit_msg + = psyc_transmit_queue_message (plc, client, psize, &msg[1], + first_ptype, last_ptype, c->tmit_msg); + if (NULL != c->tmit_msg) + { + if (GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_END <= last_ptype) + c->tmit_msg = NULL; + ret = psyc_transmit_message (plc); + } + else + { + ret = GNUNET_SYSERR; + } + if (GNUNET_OK != ret) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%p Received invalid message part from client.\n", plc); + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + GNUNET_SERVICE_client_continue (client); +} + + +/** + * A historic message arrived from PSYC. + */ +static void +psyc_recv_history_message (void *cls, const struct GNUNET_PSYC_MessageHeader *msg) +{ + struct OperationClosure *opcls = cls; + struct Client *c = opcls->client; + struct Place *plc = c->place; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Received historic message #%" PRId64 " (flags: %x)\n", + plc, GNUNET_ntohll (msg->message_id), ntohl (msg->flags)); + + uint16_t size = ntohs (msg->header.size); + + struct GNUNET_OperationResultMessage * + res = GNUNET_malloc (sizeof (*res) + size); + res->header.size = htons (sizeof (*res) + size); + res->header.type = htons (GNUNET_MESSAGE_TYPE_PSYC_HISTORY_RESULT); + res->op_id = opcls->op_id; + res->result_code = GNUNET_htonll (GNUNET_OK); + + GNUNET_memcpy (&res[1], msg, size); + + /** @todo FIXME: send only to requesting client */ + place_send_msg (plc, GNUNET_MQ_msg_copy (&res->header)); + + GNUNET_free (res); +} + + +/** + * Result of message history replay from PSYC. + */ +static void +psyc_recv_history_result (void *cls, int64_t result, + const void *err_msg, uint16_t err_msg_size) +{ + struct OperationClosure *opcls = cls; + struct Client *c = opcls->client; + struct Place *plc = c->place; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p History replay #%" PRIu64 ": " + "PSYCstore returned %" PRId64 " (%.*s)\n", + plc, GNUNET_ntohll (opcls->op_id), result, + err_msg_size, (const char *) err_msg); + + // FIXME: place might have been destroyed + client_send_result (c->client, opcls->op_id, result, err_msg, err_msg_size); +} + + +static int +check_client_history_replay (void *cls, + const struct GNUNET_PSYC_HistoryRequestMessage *req) +{ + return GNUNET_OK; +} + + +/** + * Client requests channel history. + */ +static void +handle_client_history_replay (void *cls, + const struct GNUNET_PSYC_HistoryRequestMessage *req) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Place *plc = c->place; + if (NULL == plc) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + uint16_t size = ntohs (req->header.size); + const char *method_prefix = (const char *) &req[1]; + + if (size < sizeof (*req) + 1 + || '\0' != method_prefix[size - sizeof (*req) - 1]) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%p History replay #%" PRIu64 ": " + "invalid method prefix. size: %u < %zu?\n", + plc, GNUNET_ntohll (req->op_id), size, sizeof (*req) + 1); + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + struct OperationClosure *opcls = GNUNET_malloc (sizeof (*opcls)); + opcls->client = c; + opcls->op_id = req->op_id; + opcls->flags = ntohl (req->flags); + + if (0 == req->message_limit) + GNUNET_PSYC_channel_history_replay (plc->channel, + GNUNET_ntohll (req->start_message_id), + GNUNET_ntohll (req->end_message_id), + method_prefix, opcls->flags, + psyc_recv_history_message, NULL, + psyc_recv_history_result, opcls); + else + GNUNET_PSYC_channel_history_replay_latest (plc->channel, + GNUNET_ntohll (req->message_limit), + method_prefix, opcls->flags, + psyc_recv_history_message, NULL, + psyc_recv_history_result, opcls); + + GNUNET_SERVICE_client_continue (client); +} + + +/** + * A state variable part arrived from PSYC. + */ +void +psyc_recv_state_var (void *cls, + const struct GNUNET_MessageHeader *mod, + const char *name, + const void *value, + uint32_t value_size, + uint32_t full_value_size) +{ + struct GNUNET_OperationResultMessage *result_msg; + struct GNUNET_MQ_Envelope *env; + struct OperationClosure *opcls = cls; + struct Client *c = opcls->client; + struct Place *plc = c->place; + uint16_t size = ntohs (mod->size); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p Received state variable %s from PSYC\n", + plc, name); + env = GNUNET_MQ_msg_extra (result_msg, + size, + GNUNET_MESSAGE_TYPE_PSYC_STATE_RESULT); + result_msg->op_id = opcls->op_id; + result_msg->result_code = GNUNET_htonll (GNUNET_OK); + GNUNET_memcpy (&result_msg[1], mod, size); + /** @todo FIXME: send only to requesting client */ + place_send_msg (plc, env); +} + + +/** + * Result of retrieving state variable from PSYC. + */ +static void +psyc_recv_state_result (void *cls, int64_t result, + const void *err_msg, uint16_t err_msg_size) +{ + struct OperationClosure *opcls = cls; + struct Client *c = opcls->client; + struct Place *plc = c->place; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p State get #%" PRIu64 ": " + "PSYCstore returned %" PRId64 " (%.*s)\n", + plc, GNUNET_ntohll (opcls->op_id), result, + err_msg_size, (const char *) err_msg); + + // FIXME: place might have been destroyed + client_send_result (c->client, opcls->op_id, result, err_msg, err_msg_size); +} + + +static int +check_client_state_get (void *cls, + const struct GNUNET_PSYC_StateRequestMessage *req) +{ + return GNUNET_OK; +} + + +/** + * Client requests channel history. + */ +static void +handle_client_state_get (void *cls, + const struct GNUNET_PSYC_StateRequestMessage *req) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + struct Place *plc = c->place; + if (NULL == plc) + { + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + uint16_t size = ntohs (req->header.size); + const char *name = (const char *) &req[1]; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%p State get #%" PRIu64 ": %s\n", + plc, GNUNET_ntohll (req->op_id), name); + + if (size < sizeof (*req) + 1 + || '\0' != name[size - sizeof (*req) - 1]) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "%p State get #%" PRIu64 ": " + "invalid name. size: %u < %zu?\n", + plc, GNUNET_ntohll (req->op_id), size, sizeof (*req) + 1); + GNUNET_break (0); + GNUNET_SERVICE_client_drop (client); + return; + } + + struct OperationClosure *opcls = GNUNET_malloc (sizeof (*opcls)); + opcls->client = c; + opcls->op_id = req->op_id; + + switch (ntohs (req->header.type)) + { + case GNUNET_MESSAGE_TYPE_PSYC_STATE_GET: + GNUNET_PSYC_channel_state_get (plc->channel, name, + psyc_recv_state_var, + psyc_recv_state_result, opcls); + break; + + case GNUNET_MESSAGE_TYPE_PSYC_STATE_GET_PREFIX: + GNUNET_PSYC_channel_state_get_prefix (plc->channel, name, + psyc_recv_state_var, + psyc_recv_state_result, opcls); + break; + + default: + GNUNET_assert (0); + } + + GNUNET_SERVICE_client_continue (client); +} + + +#define check_client_state_get_prefix check_client_state_get +#define handle_client_state_get_prefix handle_client_state_get + + +static void +namestore_recv_records_store_result (void *cls, int32_t result, + const char *err_msg) +{ + struct OperationClosure *opcls = cls; + struct Client *c = opcls->client; + + // FIXME: client might have been disconnected + client_send_result (c->client, opcls->op_id, result, err_msg, + (NULL != err_msg) ? strlen (err_msg) : 0); + GNUNET_free (opcls); +} + + +static int +check_client_zone_add_place (void *cls, + const struct ZoneAddPlaceRequest *preq) +{ + return GNUNET_OK; +} + + +/** + * Handle request to add PLACE record to GNS zone. + */ +static void +handle_client_zone_add_place (void *cls, + const struct ZoneAddPlaceRequest *preq) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + + uint16_t remaining = ntohs (preq->header.size) - sizeof (*preq); + const char *p = (const char *) &preq[1]; + const char *name = NULL, *password = NULL; + uint16_t offset = GNUNET_STRINGS_buffer_tokenize (p, remaining, 2, + &name, &password); + remaining -= offset; + p += offset; + const struct GNUNET_PeerIdentity * + relays = (const struct GNUNET_PeerIdentity *) p; + uint16_t relay_size = ntohl (preq->relay_count) * sizeof (*relays); + + if (0 == offset || remaining != relay_size) + { + GNUNET_break (0); + client_send_result (client, preq->op_id, GNUNET_SYSERR, NULL, 0); + GNUNET_SERVICE_client_drop (client); + return; + } + + struct GNUNET_GNSRECORD_Data rd = { }; + rd.record_type = GNUNET_GNSRECORD_TYPE_PLACE; + rd.flags = GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION; + rd.expiration_time = GNUNET_ntohll (preq->expiration_time); + + struct GNUNET_GNSRECORD_PlaceData * + rec = GNUNET_malloc (sizeof (*rec) + relay_size); + rec->place_pub_key = preq->place_pub_key; + rec->origin = this_peer; + rec->relay_count = preq->relay_count; + GNUNET_memcpy (&rec[1], relays, relay_size); + + rd.data = rec; + rd.data_size = sizeof (*rec) + relay_size; + + struct GNUNET_HashCode ego_pub_hash; + GNUNET_CRYPTO_hash (&preq->ego_pub_key, sizeof (preq->ego_pub_key), &ego_pub_hash); + struct Ego *ego = GNUNET_CONTAINER_multihashmap_get (egos, &ego_pub_hash); + if (NULL == ego) + { + client_send_result (client, preq->op_id, GNUNET_SYSERR, NULL, 0); + } + else + { + struct OperationClosure *opcls = GNUNET_malloc (sizeof (*opcls)); + opcls->client = c; + opcls->op_id = preq->op_id; + GNUNET_NAMESTORE_records_store (namestore, &ego->key, + name, 1, &rd, + namestore_recv_records_store_result, opcls); + /** @todo refresh stored records later */ + } + GNUNET_SERVICE_client_continue (client); +} + + +static int +check_client_zone_add_nym (void *cls, + const struct ZoneAddNymRequest *nreq) +{ + return GNUNET_OK; +} + + +/** + * Handle request to add PLACE record to GNS zone. + */ +static void +handle_client_zone_add_nym (void *cls, + const struct ZoneAddNymRequest *nreq) +{ + struct Client *c = cls; + struct GNUNET_SERVICE_Client *client = c->client; + + uint16_t name_size = ntohs (nreq->header.size) - sizeof (*nreq); + const char *name = NULL; + uint16_t offset = GNUNET_STRINGS_buffer_tokenize ((const char *) &nreq[1], + name_size, 1, &name); + if (0 == offset || offset != name_size) + { + GNUNET_break (0); + client_send_result (client, nreq->op_id, GNUNET_SYSERR, NULL, 0); + GNUNET_SERVICE_client_continue (client); + return; + } + + struct GNUNET_GNSRECORD_Data rd = { }; + rd.record_type = GNUNET_GNSRECORD_TYPE_PKEY; + rd.flags = GNUNET_GNSRECORD_RF_RELATIVE_EXPIRATION; + rd.expiration_time = GNUNET_ntohll (nreq->expiration_time); + rd.data = &nreq->nym_pub_key; + rd.data_size = sizeof (nreq->nym_pub_key); + + struct GNUNET_HashCode ego_pub_hash; + GNUNET_CRYPTO_hash (&nreq->ego_pub_key, sizeof (nreq->ego_pub_key), &ego_pub_hash); + struct Ego *ego = GNUNET_CONTAINER_multihashmap_get (egos, &ego_pub_hash); + if (NULL == ego) + { + client_send_result (client, nreq->op_id, GNUNET_SYSERR, NULL, 0); + } + else + { + struct OperationClosure *opcls = GNUNET_malloc (sizeof (*opcls)); + opcls->client = c; + opcls->op_id = nreq->op_id; + GNUNET_NAMESTORE_records_store (namestore, &ego->key, + name, 1, &rd, + namestore_recv_records_store_result, opcls); + /** @todo refresh stored records later */ + } + GNUNET_SERVICE_client_continue (client); +} + + +const char * +path_basename (const char *path) +{ + const char *basename = strrchr (path, DIR_SEPARATOR); + if (NULL != basename) + basename++; + + if (NULL == basename || '\0' == *basename) + return NULL; + + return basename; +} + + +struct PlaceLoadClosure +{ + const char *app_id; + const char *ego_pub_str; +}; + + +/** Load a place file */ +int +file_place_load (void *cls, const char *place_filename) +{ + struct PlaceLoadClosure *plcls = cls; + + const char *place_pub_str = path_basename (place_filename); + if (NULL == place_pub_str) + { + GNUNET_break (0); + return GNUNET_OK; + } + + char *filename = NULL; + GNUNET_asprintf (&filename, "%s%c" "%s%c" "%s%c" "%s", + dir_social, DIR_SEPARATOR, + "places", DIR_SEPARATOR, + plcls->ego_pub_str, DIR_SEPARATOR, + place_pub_str); + + uint64_t file_size = 0; + if (GNUNET_OK != + GNUNET_DISK_file_size (filename, &file_size, GNUNET_YES, GNUNET_YES) + || file_size < sizeof (struct PlaceEnterRequest)) + { + GNUNET_free (filename); + return GNUNET_OK; + } + + struct PlaceEnterRequest *ereq = GNUNET_malloc (file_size); + ssize_t read_size = GNUNET_DISK_fn_read (filename, ereq, file_size); + GNUNET_free (filename); + if (read_size < 0 || read_size < sizeof (*ereq)) + { + GNUNET_free (ereq); + return GNUNET_OK; + } + + uint16_t ereq_size = ntohs (ereq->header.size); + if (read_size != ereq_size) + { + GNUNET_free (ereq); + return GNUNET_OK; + } + + switch (ntohs (ereq->header.type)) + { + case GNUNET_MESSAGE_TYPE_SOCIAL_HOST_ENTER: + if (ereq_size < sizeof (struct HostEnterRequest)) + { + GNUNET_free (ereq); + return GNUNET_OK; + } + struct HostEnterRequest *hreq = (struct HostEnterRequest *) ereq; + host_enter (hreq, NULL); + break; + + case GNUNET_MESSAGE_TYPE_SOCIAL_GUEST_ENTER: + if (ereq_size < sizeof (struct GuestEnterRequest)) + { + GNUNET_free (ereq); + return GNUNET_OK; + } + struct GuestEnterRequest *greq = (struct GuestEnterRequest *) ereq; + guest_enter (greq, NULL); + break; + + default: + GNUNET_free (ereq); + return GNUNET_OK; + } + + if (GNUNET_SYSERR == app_place_add (plcls->app_id, ereq)) + { + GNUNET_assert (0); + } + GNUNET_free (ereq); + return GNUNET_OK; +} + + +/** + * Read @e place_pub_str entries in @a dir_ego + * + * @param dir_ego + * Data directory of an application ego. + * $GNUNET_DATA_HOME/social/apps/$app_id/$ego_pub_str/ + */ +int +scan_app_ego_dir (void *cls, const char *dir_ego) +{ + struct PlaceLoadClosure *plcls = cls; + plcls->ego_pub_str = path_basename (dir_ego); + + if (NULL != plcls->ego_pub_str) + GNUNET_DISK_directory_scan (dir_ego, file_place_load, plcls); + + return GNUNET_OK; +} + +/** + * Read @e ego_pub_str entries in @a dir_app + * + * @param dir_app + * Data directory of an application. + * $GNUNET_DATA_HOME/social/apps/$app_id/ + */ +int +scan_app_dir (void *cls, const char *dir_app) +{ + if (GNUNET_YES != GNUNET_DISK_directory_test (dir_app, GNUNET_YES)) + return GNUNET_OK; + + struct PlaceLoadClosure plcls; + plcls.app_id = path_basename (dir_app); + + if (NULL != plcls.app_id) + GNUNET_DISK_directory_scan (dir_app, scan_app_ego_dir, &plcls); + + return GNUNET_OK; +} + + +static void +identity_recv_ego (void *cls, struct GNUNET_IDENTITY_Ego *id_ego, + void **ctx, const char *name) +{ + if (NULL == id_ego) // end of initial list of egos + return; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "social service received ego %s\n", + name); + + struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key; + GNUNET_IDENTITY_ego_get_public_key (id_ego, &ego_pub_key); + + struct GNUNET_HashCode ego_pub_hash; + GNUNET_CRYPTO_hash (&ego_pub_key, sizeof (ego_pub_key), &ego_pub_hash); + + struct Ego *ego = GNUNET_CONTAINER_multihashmap_get (egos, &ego_pub_hash); + if (NULL == ego && NULL == name) + { + // an ego that is none of our business has been deleted + return; + } + if (NULL != ego) + { + // one of our egos has been changed + GNUNET_free (ego->name); + if (NULL == name) + { + // one of our egos has been deleted + GNUNET_CONTAINER_multihashmap_remove (egos, &ego_pub_hash, ego); + GNUNET_free (ego); + return; + } + } + else + { + ego = GNUNET_malloc (sizeof (*ego)); + } + ego->key = *(GNUNET_IDENTITY_ego_get_private_key (id_ego)); + size_t name_size = strlen (name) + 1; + ego->name = GNUNET_malloc (name_size); + GNUNET_memcpy (ego->name, name, name_size); + + GNUNET_CONTAINER_multihashmap_put (egos, &ego_pub_hash, ego, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE); + + // FIXME: notify clients about changed ego +} + + +/** + * Initialize the PSYC service. + * + * @param cls Closure. + * @param server The initialized server. + * @param c Configuration to use. + */ +static void +run (void *cls, + const struct GNUNET_CONFIGURATION_Handle *c, + struct GNUNET_SERVICE_Handle *svc) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "starting social service\n"); + + cfg = c; + service = svc; + GNUNET_CRYPTO_get_peer_identity (cfg, &this_peer); + + hosts = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES); + guests = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES); + place_guests = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + + egos = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + apps = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + places = GNUNET_CONTAINER_multihashmap_create(1, GNUNET_NO); + apps_places = GNUNET_CONTAINER_multihashmap_create(1, GNUNET_NO); + //places_apps = GNUNET_CONTAINER_multihashmap_create(1, GNUNET_NO); + + id = GNUNET_IDENTITY_connect (cfg, &identity_recv_ego, NULL); + gns = GNUNET_GNS_connect (cfg); + namestore = GNUNET_NAMESTORE_connect (cfg); + stats = GNUNET_STATISTICS_create ("social", cfg); + + if (GNUNET_OK != + GNUNET_CONFIGURATION_get_value_filename (cfg, "social", "DATA_HOME", + &dir_social)) + { + GNUNET_log_config_missing (GNUNET_ERROR_TYPE_ERROR, + "social", "DATA_HOME"); + GNUNET_break (0); + return; + } + GNUNET_asprintf (&dir_places, "%s%c%s", + dir_social, DIR_SEPARATOR, "places"); + GNUNET_asprintf (&dir_apps, "%s%c%s", + dir_social, DIR_SEPARATOR, "apps"); + + GNUNET_DISK_directory_scan (dir_apps, scan_app_dir, NULL); + + GNUNET_SCHEDULER_add_shutdown (shutdown_task, NULL); +} + + +/** + * Define "main" method using service macro. + */ +GNUNET_SERVICE_MAIN +("social", + GNUNET_SERVICE_OPTION_NONE, + run, + client_notify_connect, + client_notify_disconnect, + NULL, + GNUNET_MQ_hd_var_size (client_host_enter, + GNUNET_MESSAGE_TYPE_SOCIAL_HOST_ENTER, + struct HostEnterRequest, + NULL), + GNUNET_MQ_hd_var_size (client_guest_enter, + GNUNET_MESSAGE_TYPE_SOCIAL_GUEST_ENTER, + struct GuestEnterRequest, + NULL), + GNUNET_MQ_hd_var_size (client_guest_enter_by_name, + GNUNET_MESSAGE_TYPE_SOCIAL_GUEST_ENTER_BY_NAME, + struct GuestEnterByNameRequest, + NULL), + GNUNET_MQ_hd_var_size (client_join_decision, + GNUNET_MESSAGE_TYPE_PSYC_JOIN_DECISION, + struct GNUNET_PSYC_JoinDecisionMessage, + NULL), + GNUNET_MQ_hd_var_size (client_psyc_message, + GNUNET_MESSAGE_TYPE_PSYC_MESSAGE, + struct GNUNET_MessageHeader, + NULL), + GNUNET_MQ_hd_var_size (client_history_replay, + GNUNET_MESSAGE_TYPE_PSYC_HISTORY_REPLAY, + struct GNUNET_PSYC_HistoryRequestMessage, + NULL), + GNUNET_MQ_hd_var_size (client_state_get, + GNUNET_MESSAGE_TYPE_PSYC_STATE_GET, + struct GNUNET_PSYC_StateRequestMessage, + NULL), + GNUNET_MQ_hd_var_size (client_state_get_prefix, + GNUNET_MESSAGE_TYPE_PSYC_STATE_GET_PREFIX, + struct GNUNET_PSYC_StateRequestMessage, + NULL), + GNUNET_MQ_hd_var_size (client_zone_add_place, + GNUNET_MESSAGE_TYPE_SOCIAL_ZONE_ADD_PLACE, + struct ZoneAddPlaceRequest, + NULL), + GNUNET_MQ_hd_var_size (client_zone_add_nym, + GNUNET_MESSAGE_TYPE_SOCIAL_ZONE_ADD_NYM, + struct ZoneAddNymRequest, + NULL), + GNUNET_MQ_hd_var_size (client_app_connect, + GNUNET_MESSAGE_TYPE_SOCIAL_APP_CONNECT, + struct AppConnectRequest, + NULL), + GNUNET_MQ_hd_fixed_size (client_app_detach, + GNUNET_MESSAGE_TYPE_SOCIAL_APP_DETACH, + struct AppDetachRequest, + NULL), + GNUNET_MQ_hd_fixed_size (client_place_leave, + GNUNET_MESSAGE_TYPE_SOCIAL_PLACE_LEAVE, + struct GNUNET_MessageHeader, + NULL), + GNUNET_MQ_hd_var_size (client_msg_proc_set, + GNUNET_MESSAGE_TYPE_SOCIAL_MSG_PROC_SET, + struct MsgProcRequest, + NULL), + GNUNET_MQ_hd_fixed_size (client_msg_proc_clear, + GNUNET_MESSAGE_TYPE_SOCIAL_MSG_PROC_CLEAR, + struct GNUNET_MessageHeader, + NULL)); + +/* end of gnunet-service-social.c */ diff --git a/src/social/gnunet-social.c b/src/social/gnunet-social.c new file mode 100644 index 0000000..14701bf --- /dev/null +++ b/src/social/gnunet-social.c @@ -0,0 +1,1411 @@ +/* + This file is part of GNUnet. + Copyright (C) 2016 GNUnet e.V. + + GNUnet is free software: you can redistribute it and/or modify it + under the terms of the GNU Affero General Public License as published + by the Free Software Foundation, either version 3 of the License, + or (at your option) any later version. + + GNUnet is distributed in the hope that it will be useful, but + WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Affero General Public License for more details. + + You should have received a copy of the GNU Affero General Public License + along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later +*/ + +/** + * CLI tool to interact with the social service. + * + * @author Gabor X Toth + */ + +#include + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_social_service.h" + +#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30) + +#define DATA2ARG(data) data, sizeof (data) + +/* operations corresponding to API calls */ + +/** --status */ +static int op_status; + +/** --host-enter */ +static int op_host_enter; + +/** --host-reconnect */ +static int op_host_reconnect; + +/** --host-leave */ +static int op_host_leave; + +/** --host-announce */ +static int op_host_announce; + +/** --host-assign */ +static int op_host_assign; + +/** --guest-enter */ +static int op_guest_enter; + +/** --guest-reconnect */ +static int op_guest_reconnect; + +/** --guest-leave */ +static int op_guest_leave; + +/** --guest-talk */ +static int op_guest_talk; + +/** --replay */ +static int op_replay; + +/** --replay-latest */ +static int op_replay_latest; + +/** --look-at */ +static int op_look_at; + +/** --look-for */ +static int op_look_for; + + +/* options */ + +/** --app */ +static char *opt_app = "cli"; + +/** --place */ +static char *opt_place; + +/** --ego */ +static char *opt_ego; + +/** --gns */ +static char *opt_gns; + +/** --peer */ +static char *opt_peer; + +/** --follow */ +static int opt_follow; + +/** --welcome */ +static int opt_welcome; + +/** --deny */ +static int opt_deny; + +/** --method */ +static char *opt_method; + +/** --data */ +// FIXME: should come from STDIN +static char *opt_data; + +/** --name */ +static char *opt_name; + +/** --start */ +static unsigned long long opt_start; + +/** --until */ +static unsigned long long opt_until; + +/** --limit */ +static unsigned long long opt_limit; + + +/* global vars */ + +/** exit code */ +static int ret = 1; + +/** are we waiting for service to close our connection */ +static char is_disconnecting = 0; + +/** Task handle for timeout termination. */ +struct GNUNET_SCHEDULER_Task *timeout_task; + +const struct GNUNET_CONFIGURATION_Handle *cfg; + +struct GNUNET_PeerIdentity peer, this_peer; + +struct GNUNET_SOCIAL_App *app; + +/** public key of connected place */ +struct GNUNET_CRYPTO_EddsaPublicKey place_pub_key; + +struct GNUNET_PSYC_Slicer *slicer; + +struct GNUNET_SOCIAL_Ego *ego; +struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key; + +struct GNUNET_SOCIAL_Host *hst; +struct GNUNET_SOCIAL_Guest *gst; +struct GNUNET_SOCIAL_Place *plc; + +const char *method_received; + + +/* DISCONNECT */ + + +/** + * Callback called after the host or guest place disconnected. + */ +static void +disconnected (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "disconnected()\n"); + GNUNET_SCHEDULER_shutdown (); +} + + +/** + * Callback called after the application disconnected. + */ +static void +app_disconnected (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "app_disconnected()\n"); + if (hst || gst) + { + if (hst) + { + GNUNET_SOCIAL_host_disconnect (hst, disconnected, NULL); + } + if (gst) + { + GNUNET_SOCIAL_guest_disconnect (gst, disconnected, NULL); + } + } + else + { + GNUNET_SCHEDULER_shutdown (); + } +} + + +/** + * Disconnect from connected GNUnet services. + */ +static void +disconnect () +{ + // handle that we get called several times from several places, but should we? + if (!is_disconnecting++) { + GNUNET_SOCIAL_app_disconnect (app, app_disconnected, NULL); + } + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "disconnect() called for the #%d time\n", is_disconnecting); +} + + +static void +scheduler_shutdown (void *cls) +{ + disconnect (); +} + + +/** + * Callback called when the program failed to finish the requested operation in time. + */ +static void +timeout (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "timeout()\n"); + disconnect (); +} + +static void +schedule_success (void *cls) +{ + ret = 0; + disconnect (); +} + + +static void +schedule_fail (void *cls) +{ + disconnect (); +} + + +/** + * Schedule exit with success result. + */ +static void +exit_success () +{ + if (timeout_task != NULL) + { + GNUNET_SCHEDULER_cancel (timeout_task); + timeout_task = NULL; + } + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, schedule_success, NULL); +} + + +/** + * Schedule exit with failure result. + */ +static void +exit_fail () +{ + if (timeout_task != NULL) + { + GNUNET_SCHEDULER_cancel (timeout_task); + timeout_task = NULL; + } + GNUNET_SCHEDULER_add_delayed (GNUNET_TIME_UNIT_SECONDS, schedule_fail, NULL); +} + + +/* LEAVE */ + + +/** + * Callback notifying about the host has left and stopped hosting the place. + * + * This also indicates the end of the connection to the service. + */ +static void +host_left (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "The host has left the place.\n"); + exit_success (); +} + + +/** + * Leave a place permanently and stop hosting a place. + */ +static void +host_leave () +{ + GNUNET_SOCIAL_host_leave (hst, NULL, host_left, NULL); + hst = NULL; + plc = NULL; +} + + +/** + * Callback notifying about the guest has left the place. + * + * This also indicates the end of the connection to the service. + */ +static void +guest_left (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Guest has left the place.\n"); +} + + +/** + * Leave a place permanently as guest. + */ +static void +guest_leave () +{ + struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create (); + // FIXME: wrong use of vars + GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_SET, + "_message", DATA2ARG ("Leaving.")); + GNUNET_SOCIAL_guest_leave (gst, env, guest_left, NULL); + GNUNET_PSYC_env_destroy (env); + gst = NULL; + plc = NULL; +} + + +/* ANNOUNCE / ASSIGN / TALK */ + + +struct TransmitClosure +{ + const char *data; + size_t size; +} tmit; + + +/** + * Callback notifying about available buffer space to write message data + * when transmitting messages using host_announce() or guest_talk() + */ +static int +notify_data (void *cls, uint16_t *data_size, void *data) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Transmit notify data: %u bytes available\n", + *data_size); + + struct TransmitClosure *tmit = cls; + uint16_t size = tmit->size < *data_size ? tmit->size : *data_size; + *data_size = size; + GNUNET_memcpy (data, tmit->data, size); + + tmit->size -= size; + tmit->data += size; + + if (0 == tmit->size) + { + if ((op_host_announce || op_host_assign || op_guest_talk) && !opt_follow) + { + exit_success (); + } + return GNUNET_YES; + } + else + { + return GNUNET_NO; + } +} + + +/** + * Host announcement - send a message to the place. + */ +static void +host_announce (const char *method, const char *data, size_t data_size) +{ + struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create (); + GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_SET, + "_foo", DATA2ARG ("bar baz")); + + tmit = (struct TransmitClosure) {}; + tmit.data = data; + tmit.size = data_size; + + GNUNET_SOCIAL_host_announce (hst, method, env, + notify_data, &tmit, + GNUNET_SOCIAL_ANNOUNCE_NONE); + GNUNET_PSYC_env_destroy (env); +} + + +/** + * Assign a state var of @a name to the value of @a data. + */ +static void +host_assign (const char *name, const char *data, size_t data_size) +{ + struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create (); + GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_ASSIGN, + name, data, data_size); + + tmit = (struct TransmitClosure) {}; + GNUNET_SOCIAL_host_announce (hst, "_assign", env, + notify_data, &tmit, + GNUNET_SOCIAL_ANNOUNCE_NONE); + GNUNET_PSYC_env_destroy (env); +} + + +/** + * Guest talk request to host. + */ +static void +guest_talk (const char *method, + const char *data, size_t data_size) +{ + struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create (); + GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_SET, + "_foo", DATA2ARG ("bar baz")); + + tmit = (struct TransmitClosure) {}; + tmit.data = data; + tmit.size = data_size; + + GNUNET_SOCIAL_guest_talk (gst, method, env, + notify_data, &tmit, + GNUNET_SOCIAL_TALK_NONE); + GNUNET_PSYC_env_destroy (env); +} + + +/* HISTORY REPLAY */ + + +/** + * Callback notifying about the end of history replay results. + */ +static void +recv_history_replay_result (void *cls, int64_t result, + const void *data, uint16_t data_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received history replay result: %" PRId64 "\n" + "%.*s\n", + result, data_size, (const char *) data); + + if (op_replay || op_replay_latest) + { + exit_success (); + } +} + + +/** + * Replay history between a given @a start and @a end message IDs, + * optionally filtered by a method @a prefix. + */ +static void +history_replay (uint64_t start, uint64_t end, const char *prefix) +{ + GNUNET_SOCIAL_place_history_replay (plc, start, end, prefix, + GNUNET_PSYC_HISTORY_REPLAY_LOCAL, + slicer, + recv_history_replay_result, + NULL); +} + + +/** + * Replay latest @a limit messages. + */ +static void +history_replay_latest (uint64_t limit, const char *prefix) +{ + GNUNET_SOCIAL_place_history_replay_latest (plc, limit, prefix, + GNUNET_PSYC_HISTORY_REPLAY_LOCAL, + slicer, + recv_history_replay_result, + NULL); +} + + +/* LOOK AT/FOR */ + + +/** + * Callback notifying about the end of state var results. + */ +static void +look_result (void *cls, int64_t result_code, + const void *data, uint16_t data_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Received look result: %" PRId64 "\n", result_code); + + if (op_look_at || op_look_for) + { + exit_success (); + } +} + + +/** + * Callback notifying about a state var result. + */ +static void +look_var (void *cls, + const struct GNUNET_MessageHeader *mod, + const char *name, + const void *value, + uint32_t value_size, + uint32_t full_value_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Received var: %s\n%.*s\n", + name, value_size, (const char *) value); +} + + +/** + * Look for a state var using exact match of the name. + */ +static void +look_at (const char *full_name) +{ + GNUNET_SOCIAL_place_look_at (plc, full_name, look_var, look_result, NULL); +} + + +/** + * Look for state vars by name prefix. + */ +static void +look_for (const char *name_prefix) +{ + GNUNET_SOCIAL_place_look_for (plc, name_prefix, look_var, look_result, NULL); +} + + +/* SLICER */ + + +/** + * Callback notifying about the start of a new incoming message. + */ +static void +slicer_recv_method (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_PSYC_MessageMethod *meth, + uint64_t message_id, + const char *method_name) +{ + method_received = method_name; + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Received method for message ID %" PRIu64 ":\n" + "%s (flags: %x)\n", + message_id, method_name, ntohl (meth->flags)); + /* routing header is missing, so we just print double newline */ + printf("\n"); + /* we output . instead of | to indicate that this is not proper PSYC syntax */ + /* FIXME: use libpsyc here */ +} + + +/** + * Callback notifying about an incoming modifier. + */ +static void +slicer_recv_modifier (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + enum GNUNET_PSYC_Operator oper, + const char *name, + const void *value, + uint16_t value_size, + uint16_t full_value_size) +{ +#if 0 + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Received modifier for message ID %" PRIu64 ":\n" + "%c%s: %.*s (size: %u)\n", + message_id, oper, name, value_size, (const char *) value, value_size); +#else + /* obviously not binary safe */ + printf("%c%s\t%.*s\n", + oper, name, value_size, (const char *) value); +#endif +} + + +/** + * Callback notifying about an incoming data fragment. + */ +static void +slicer_recv_data (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + const void *data, + uint16_t data_size) +{ +#if 0 + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Received data for message ID %" PRIu64 ":\n" + "%.*s\n", + message_id, data_size, (const char *) data); +#else + /* obviously not binary safe */ + printf("%s\n%.*s\n", + method_received, data_size, (const char *) data); +#endif +} + + +/** + * Callback notifying about the end of a message. + */ +static void +slicer_recv_eom (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + uint8_t is_cancelled) +{ + printf(".\n"); + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Received end of message ID %" PRIu64 + ", cancelled: %u\n", + message_id, is_cancelled); +} + + +/** + * Create a slicer for receiving message parts. + */ +static struct GNUNET_PSYC_Slicer * +slicer_create () +{ + slicer = GNUNET_PSYC_slicer_create (); + + /* register slicer to receive incoming messages with any method name */ + GNUNET_PSYC_slicer_method_add (slicer, "", NULL, + slicer_recv_method, slicer_recv_modifier, + slicer_recv_data, slicer_recv_eom, NULL); + return slicer; +} + + +/* GUEST ENTER */ + + +/** + * Callback called when the guest receives an entry decision from the host. + * + * It is called once after using guest_enter() or guest_enter_by_name(), + * in case of a reconnection only the local enter callback is called. + */ +static void +guest_recv_entry_decision (void *cls, + int is_admitted, + const struct GNUNET_PSYC_Message *entry_msg) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Guest received entry decision %d\n", + is_admitted); + + if (NULL != entry_msg) + { + struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create (); + const char *method_name = NULL; + const void *data = NULL; + uint16_t data_size = 0; + struct GNUNET_PSYC_MessageHeader * + pmsg = GNUNET_PSYC_message_header_create_from_psyc (entry_msg); + GNUNET_PSYC_message_parse (pmsg, &method_name, env, &data, &data_size); + GNUNET_free (pmsg); + + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "%s\n%.*s\n", + method_name, data_size, (const char *) data); + } + + if (op_guest_enter && !opt_follow) + { + exit_success (); + } +} + + +/** + * Callback called after a guest connection is established to the local service. + */ +static void +guest_recv_local_enter (void *cls, int result, + const struct GNUNET_CRYPTO_EddsaPublicKey *pub_key, + uint64_t max_message_id) +{ + char *pub_str = GNUNET_CRYPTO_eddsa_public_key_to_string (pub_key); + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Guest entered local place: %s, max_message_id: %" PRIu64 "\n", + pub_str, max_message_id); + GNUNET_free (pub_str); + GNUNET_assert (0 <= result); + + if (op_guest_enter && !opt_follow) + { + exit_success (); + } +} + + +/** + * Create entry request message. + */ +static struct GNUNET_PSYC_Message * +guest_enter_msg_create () +{ + const char *method_name = "_request_enter"; + struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create (); + GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_SET, + "_foo", DATA2ARG ("bar")); + void *data = "let me in"; + uint16_t data_size = strlen (data) + 1; + + return GNUNET_PSYC_message_create (method_name, env, data, data_size); +} + + +/** + * Enter a place as guest, using its public key and peer ID. + */ +static void +guest_enter (const struct GNUNET_CRYPTO_EddsaPublicKey *pub_key, + const struct GNUNET_PeerIdentity *peer) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Entering to place as guest.\n"); + + if (NULL == ego) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "--ego missing or invalid\n"); + exit_fail (); + return; + } + + struct GNUNET_PSYC_Message *join_msg = guest_enter_msg_create (); + gst = GNUNET_SOCIAL_guest_enter (app, ego, pub_key, + GNUNET_PSYC_SLAVE_JOIN_NONE, + peer, 0, NULL, join_msg, slicer_create (), + guest_recv_local_enter, + guest_recv_entry_decision, NULL); + GNUNET_free (join_msg); + plc = GNUNET_SOCIAL_guest_get_place (gst); +} + + +/** + * Enter a place as guest using its GNS address. + */ +static void +guest_enter_by_name (const char *gns_name) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Entering to place by name as guest.\n"); + + struct GNUNET_PSYC_Message *join_msg = guest_enter_msg_create (); + gst = GNUNET_SOCIAL_guest_enter_by_name (app, ego, gns_name, NULL, + join_msg, slicer, + guest_recv_local_enter, + guest_recv_entry_decision, NULL); + GNUNET_free (join_msg); + plc = GNUNET_SOCIAL_guest_get_place (gst); +} + + +/* HOST ENTER */ + + +/** + * Callback called when a @a nym wants to enter the place. + * + * The request needs to be replied with an entry decision. + */ +static void +host_answer_door (void *cls, + struct GNUNET_SOCIAL_Nym *nym, + const char *method_name, + struct GNUNET_PSYC_Environment *env, + const void *data, + size_t data_size) +{ + const struct GNUNET_CRYPTO_EcdsaPublicKey * + nym_key = GNUNET_SOCIAL_nym_get_pub_key (nym); + char * + nym_str = GNUNET_CRYPTO_ecdsa_public_key_to_string (nym_key); + + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Entry request: %s\n", nym_str); + GNUNET_free (nym_str); + + if (opt_welcome) + { + struct GNUNET_PSYC_Message * + resp = GNUNET_PSYC_message_create ("_notice_place_admit", env, + DATA2ARG ("Welcome, nym!")); + GNUNET_SOCIAL_host_entry_decision (hst, nym, GNUNET_YES, resp); + GNUNET_free (resp); + } + else if (opt_deny) + { + struct GNUNET_PSYC_Message * + resp = GNUNET_PSYC_message_create ("_notice_place_refuse", NULL, + DATA2ARG ("Go away!")); + GNUNET_SOCIAL_host_entry_decision (hst, nym, GNUNET_NO, resp); + GNUNET_free (resp); + } + + +} + + +/** + * Callback called when a @a nym has left the place. + */ +static void +host_farewell (void *cls, + const struct GNUNET_SOCIAL_Nym *nym, + struct GNUNET_PSYC_Environment *env) +{ + const struct GNUNET_CRYPTO_EcdsaPublicKey * + nym_key = GNUNET_SOCIAL_nym_get_pub_key (nym); + char * + nym_str = GNUNET_CRYPTO_ecdsa_public_key_to_string (nym_key); + + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Farewell: %s\n", nym_str); + GNUNET_free (nym_str); +} + + +/** + * Callback called after the host entered the place. + */ +static void +host_entered (void *cls, int result, + const struct GNUNET_CRYPTO_EddsaPublicKey *pub_key, + uint64_t max_message_id) +{ + place_pub_key = *pub_key; + char *pub_str = GNUNET_CRYPTO_eddsa_public_key_to_string (pub_key); + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Host entered: %s, max_message_id: %" PRIu64 "\n", + pub_str, max_message_id); + GNUNET_free (pub_str); + + if (op_host_enter && !opt_follow) + { + exit_success (); + } +} + + +/** + * Enter and start hosting a place. + */ +static void +host_enter () +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, "host_enter()\n"); + + if (NULL == ego) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "--ego missing or invalid\n"); + exit_fail (); + return; + } + + hst = GNUNET_SOCIAL_host_enter (app, ego, + GNUNET_PSYC_CHANNEL_PRIVATE, + slicer_create (), host_entered, + host_answer_door, host_farewell, NULL); + plc = GNUNET_SOCIAL_host_get_place (hst); +} + + +/* PLACE RECONNECT */ + + +/** + * Perform operations common to both host & guest places. + */ +static void +place_reconnected () +{ + static int first_run = GNUNET_YES; + if (GNUNET_NO == first_run) + return; + first_run = GNUNET_NO; + + if (op_replay) { + history_replay (opt_start, opt_until, opt_method); + } + else if (op_replay_latest) { + history_replay_latest (opt_limit, opt_method); + } + else if (op_look_at) { + look_at (opt_name); + } + else if (op_look_for) { + look_for (opt_name); + } +} + + +/** + * Callback called after reconnecting to a host place. + */ +static void +host_reconnected (void *cls, int result, + const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key, + uint64_t max_message_id) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Host reconnected.\n"); + + if (op_host_leave) { + host_leave (); + } + else if (op_host_announce) { + host_announce (opt_method, opt_data, strlen (opt_data)); + } + else if (op_host_assign) { + host_assign (opt_name, opt_data, strlen (opt_data) + 1); + } + else { + place_reconnected (); + } +} + + +/** + * Callback called after reconnecting to a guest place. + */ +static void +guest_reconnected (void *cls, int result, + const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key, + uint64_t max_message_id) +{ + char *place_pub_str = GNUNET_CRYPTO_eddsa_public_key_to_string (place_pub_key); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Guest reconnected to place %s.\n", place_pub_str); + GNUNET_free (place_pub_str); + + if (op_guest_leave) { + guest_leave (); + } + else if (op_guest_talk) { + guest_talk (opt_method, opt_data, strlen (opt_data)); + } + else { + place_reconnected (); + } +} + + +/* APP */ + + +/** + * Callback called after the ego and place callbacks. + */ +static void +app_connected (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "App connected: %p\n", cls); + + if (op_status) + { + exit_success (); + } + else if (op_host_enter) + { + host_enter (); + } + else if (op_guest_enter) + { + if (opt_gns) + { + guest_enter_by_name (opt_gns); + } + else + { + if (opt_peer) + { + if (GNUNET_OK != GNUNET_CRYPTO_eddsa_public_key_from_string (opt_peer, + strlen (opt_peer), + &peer.public_key)) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "--peer invalid"); + exit_fail (); + return; + } + } + else + { + peer = this_peer; + } + guest_enter (&place_pub_key, &peer); + } + } + printf(".\n"); +} + + +/** + * Callback notifying about a host place available for reconnection. + */ +static void +app_recv_host (void *cls, + struct GNUNET_SOCIAL_HostConnection *hconn, + struct GNUNET_SOCIAL_Ego *ego, + const struct GNUNET_CRYPTO_EddsaPublicKey *host_pub_key, + enum GNUNET_SOCIAL_AppPlaceState place_state) +{ + char *host_pub_str = GNUNET_CRYPTO_eddsa_public_key_to_string (host_pub_key); + printf ("Host\t%s\n", host_pub_str); + GNUNET_free (host_pub_str); + + if ((op_host_reconnect || op_host_leave || op_host_announce || op_host_assign + || op_replay || op_replay_latest + || op_look_at || op_look_for) + && 0 == memcmp (&place_pub_key, host_pub_key, sizeof (*host_pub_key))) + { + hst = GNUNET_SOCIAL_host_enter_reconnect (hconn, slicer_create (), host_reconnected, + host_answer_door, host_farewell, NULL); + plc = GNUNET_SOCIAL_host_get_place (hst); + } +} + + +/** + * Callback notifying about a guest place available for reconnection. + */ +static void +app_recv_guest (void *cls, + struct GNUNET_SOCIAL_GuestConnection *gconn, + struct GNUNET_SOCIAL_Ego *ego, + const struct GNUNET_CRYPTO_EddsaPublicKey *guest_pub_key, + enum GNUNET_SOCIAL_AppPlaceState place_state) +{ + char *guest_pub_str = GNUNET_CRYPTO_eddsa_public_key_to_string (guest_pub_key); + printf ("Guest\t%s\n", guest_pub_str); + GNUNET_free (guest_pub_str); + + if ((op_guest_reconnect || op_guest_leave || op_guest_talk + || op_replay || op_replay_latest + || op_look_at || op_look_for) + && 0 == memcmp (&place_pub_key, guest_pub_key, sizeof (*guest_pub_key))) + { + gst = GNUNET_SOCIAL_guest_enter_reconnect (gconn, GNUNET_PSYC_SLAVE_JOIN_NONE, + slicer_create (), guest_reconnected, NULL); + plc = GNUNET_SOCIAL_guest_get_place (gst); + } +} + + +/** + * Callback notifying about an available ego. + */ +static void +app_recv_ego (void *cls, + struct GNUNET_SOCIAL_Ego *e, + const struct GNUNET_CRYPTO_EcdsaPublicKey *pub_key, + const char *name) +{ + char *s = GNUNET_CRYPTO_ecdsa_public_key_to_string (pub_key); + printf ("Ego\t%s\t%s\n", s, name); + GNUNET_free (s); + + if (0 == memcmp (&ego_pub_key, pub_key, sizeof (*pub_key)) + || (NULL != opt_ego && 0 == strcmp (opt_ego, name))) + { + ego = e; + } + +} + + + +/** + * Establish application connection to receive available egos and places. + */ +static void +app_connect (void *cls) +{ + app = GNUNET_SOCIAL_app_connect (cfg, opt_app, + app_recv_ego, + app_recv_host, + app_recv_guest, + app_connected, + NULL); +} + + +/** + * Main function run by the scheduler. + * + * @param cls closure + * @param args remaining command-line arguments + * @param cfgfile name of the configuration file used (for saving, can be NULL!) + * @param c configuration + */ +static void +run (void *cls, char *const *args, const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *c) +{ + cfg = c; + GNUNET_CRYPTO_get_peer_identity (cfg, &this_peer); + + if (!opt_method) + opt_method = "message"; + if (!opt_data) + opt_data = ""; + if (!opt_name) + opt_name = ""; + + if (! (op_status + || op_host_enter || op_host_reconnect || op_host_leave + || op_host_announce || op_host_assign + || op_guest_enter || op_guest_reconnect + || op_guest_leave || op_guest_talk + || op_replay || op_replay_latest + || op_look_at || op_look_for)) + { + op_status = 1; + fputs("Caution: This tool does not produce correct binary safe PSYC syntax.\n\n", stderr); + } + + GNUNET_SCHEDULER_add_shutdown (scheduler_shutdown, NULL); + if (!opt_follow) + { + timeout_task = GNUNET_SCHEDULER_add_delayed (TIMEOUT, timeout, NULL); + } + + if ((op_host_reconnect || op_host_leave || op_host_announce || op_host_assign + || op_guest_reconnect || (op_guest_enter && !opt_gns) + || op_guest_leave || op_guest_talk + || op_replay || op_replay_latest + || op_look_at || op_look_for) + && (!opt_place + || GNUNET_OK != GNUNET_CRYPTO_eddsa_public_key_from_string (opt_place, + strlen (opt_place), + &place_pub_key))) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + _("--place missing or invalid.\n")); + /* FIXME: why does it segfault here? */ + exit_fail (); + return; + } + + if (opt_ego) + { + if (GNUNET_OK != + GNUNET_CRYPTO_ecdsa_public_key_from_string (opt_ego, + strlen (opt_ego), + &ego_pub_key)) + { + FPRINTF (stderr, + _("Public key `%s' malformed\n"), + opt_ego); + exit_fail (); + return; + } + } + + GNUNET_SCHEDULER_add_now (app_connect, NULL); +} + + +/** + * The main function to obtain peer information. + * + * @param argc number of arguments from the command line + * @param argv command line arguments + * @return 0 ok, 1 on error + */ +int +main (int argc, char *const *argv) +{ + int res; + struct GNUNET_GETOPT_CommandLineOption options[] = { + /* + * gnunet program options in addition to the ones below: + * + * -c, --config=FILENAME + * -l, --logfile=LOGFILE + * -L, --log=LOGLEVEL + * -h, --help + * -v, --version + */ + + /* operations */ + + GNUNET_GETOPT_option_flag ('A', + "host-assign", + gettext_noop ("assign --name in state to --data"), + &op_host_assign), + + GNUNET_GETOPT_option_flag ('B', + "guest-leave", + gettext_noop ("say good-bye and leave somebody else's place"), + &op_guest_leave), + + GNUNET_GETOPT_option_flag ('C', + "host-enter", + gettext_noop ("create a place"), + &op_host_enter), + + GNUNET_GETOPT_option_flag ('D', + "host-leave", + gettext_noop ("destroy a place we were hosting"), + &op_host_leave), + + GNUNET_GETOPT_option_flag ('E', + "guest-enter", + gettext_noop ("enter somebody else's place"), + &op_guest_enter), + + + GNUNET_GETOPT_option_flag ('F', + "look-for", + gettext_noop ("find state matching name prefix"), + &op_look_for), + + GNUNET_GETOPT_option_flag ('H', + "replay-latest", + gettext_noop ("replay history of messages up to the given --limit"), + &op_replay_latest), + + GNUNET_GETOPT_option_flag ('N', + "host-reconnect", + gettext_noop ("reconnect to a previously created place"), + &op_host_reconnect), + + GNUNET_GETOPT_option_flag ('P', + "host-announce", + gettext_noop ("publish something to a place we are hosting"), + &op_host_announce), + + GNUNET_GETOPT_option_flag ('R', + "guest-reconnect", + gettext_noop ("reconnect to a previously entered place"), + &op_guest_reconnect), + + GNUNET_GETOPT_option_flag ('S', + "look-at", + gettext_noop ("search for state matching exact name"), + &op_look_at), + + GNUNET_GETOPT_option_flag ('T', + "guest-talk", + gettext_noop ("submit something to somebody's place"), + &op_guest_talk), + + GNUNET_GETOPT_option_flag ('U', + "status", + gettext_noop ("list of egos and subscribed places"), + &op_status), + + GNUNET_GETOPT_option_flag ('X', + "replay", + gettext_noop ("extract and replay history between message IDs --start and --until"), + &op_replay), + + + /* options */ + + GNUNET_GETOPT_option_string ('a', + "app", + "APPLICATION_ID", + gettext_noop ("application ID to use when connecting"), + &opt_app), + + GNUNET_GETOPT_option_string ('d', + "data", + "DATA", + gettext_noop ("message body or state value"), + &opt_data), + + GNUNET_GETOPT_option_string ('e', + "ego", + "NAME|PUBKEY", + gettext_noop ("name or public key of ego"), + &opt_ego), + + GNUNET_GETOPT_option_flag ('f', + "follow", + gettext_noop ("wait for incoming messages"), + &opt_follow), + + GNUNET_GETOPT_option_string ('g', + "gns", + "GNS_NAME", + gettext_noop ("GNS name"), + &opt_gns), + + GNUNET_GETOPT_option_string ('i', + "peer", + "PEER_ID", + gettext_noop ("peer ID for --guest-enter"), + &opt_peer), + + GNUNET_GETOPT_option_string ('k', + "name", + "VAR_NAME", + gettext_noop ("name (key) to query from state"), + &opt_name), + + GNUNET_GETOPT_option_string ('m', + "method", + "METHOD_NAME", + gettext_noop ("method name"), + &opt_method), + + GNUNET_GETOPT_option_ulong ('n', + "limit", + NULL, + gettext_noop ("number of messages to replay from history"), + &opt_limit), + + GNUNET_GETOPT_option_string ('p', + "place", + "PUBKEY", + gettext_noop ("key address of place"), + &opt_place), + + GNUNET_GETOPT_option_ulong ('s', + "start", + NULL, + gettext_noop ("start message ID for history replay"), + &opt_start), + + GNUNET_GETOPT_option_flag ('w', + "welcome", + gettext_noop ("respond to entry requests by admitting all guests"), + &opt_welcome), + + GNUNET_GETOPT_option_ulong ('u', + "until", + NULL, + gettext_noop ("end message ID for history replay"), + &opt_until), + + GNUNET_GETOPT_option_flag ('y', + "deny", + gettext_noop ("respond to entry requests by refusing all guests"), + &opt_deny), + + GNUNET_GETOPT_OPTION_END + }; + + if (GNUNET_OK != GNUNET_STRINGS_get_utf8_args (argc, argv, &argc, &argv)) + return 2; + + const char *help = + _ ("gnunet-social - Interact with the social service: enter/leave, send/receive messages, access history and state.\n"); + const char *usage = + "gnunet-social [--status]\n" + "\n" + "gnunet-social --host-enter --ego [--follow] [--welcome | --deny]\n" + "gnunet-social --host-reconnect --place [--follow] [--welcome | --deny]\n" + "gnunet-social --host-leave --place \n" + "gnunet-social --host-assign --place --name --data \n" +// FIXME: some state ops not implemented yet (no hurry) +// "gnunet-social --host-augment --place --name --data \n" +// "gnunet-social --host-diminish --place --name --data \n" +// "gnunet-social --host-set --place --name --data \n" + "gnunet-social --host-announce --place --method --data \n" + "\n" + "gnunet-social --guest-enter --place --peer --ego [--follow]\n" + "gnunet-social --guest-enter --gns --ego [--follow]\n" + "gnunet-social --guest-reconnect --place [--follow]\n" + "gnunet-social --guest-leave --place \n" + "gnunet-social --guest-talk --place --method --data \n" + "\n" + "gnunet-social --replay --place --start --until [--method ]\n" + "gnunet-social --replay-latest --place --limit [--method ]\n" + "\n" + "gnunet-social --look-at --place --name \n" + "gnunet-social --look-for --place --name \n"; + + res = GNUNET_PROGRAM_run (argc, argv, help, usage, options, &run, NULL); + + GNUNET_free ((void *) argv); + + if (GNUNET_OK == res) + return ret; + else + return 1; +} diff --git a/src/social/social.conf.in b/src/social/social.conf.in new file mode 100644 index 0000000..3fe754c --- /dev/null +++ b/src/social/social.conf.in @@ -0,0 +1,15 @@ +[social] +START_ON_DEMAND = @START_ON_DEMAND@ +BINARY = gnunet-service-social +RUN_PER_USER = YES + +UNIXPATH = $GNUNET_USER_RUNTIME_DIR/gnunet-service-social.sock +UNIX_MATCH_UID = YES +UNIX_MATCH_GID = YES + +@UNIXONLY@PORT = 2116 +HOSTNAME = localhost +ACCEPT_FROM = 127.0.0.1; +ACCEPT_FROM6 = ::1; + +DATA_HOME = $GNUNET_DATA_HOME/social diff --git a/src/social/social.h b/src/social/social.h new file mode 100644 index 0000000..73f73f6 --- /dev/null +++ b/src/social/social.h @@ -0,0 +1,292 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @file social/social.h + * @brief Common type definitions for the Social service and API. + * @author Gabor X Toth + */ + +#ifndef SOCIAL_H +#define SOCIAL_H + +#include "platform.h" +#include "gnunet_social_service.h" + +enum MessageState +{ + MSG_STATE_START = 0, + MSG_STATE_HEADER = 1, + MSG_STATE_METHOD = 2, + MSG_STATE_MODIFIER = 3, + MSG_STATE_MOD_CONT = 4, + MSG_STATE_DATA = 5, + MSG_STATE_END = 6, + MSG_STATE_CANCEL = 7, + MSG_STATE_ERROR = 8, +}; + + +GNUNET_NETWORK_STRUCT_BEGIN + +/**** library -> service ****/ + + +struct AppConnectRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_SOCIAL_APP_CONNECT + */ + struct GNUNET_MessageHeader header; + + /* Followed by char *app_id */ +}; + + +struct AppDetachRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_SOCIAL_APP_DETACH + */ + struct GNUNET_MessageHeader header; + + /** + * Public key of place. + */ + struct GNUNET_CRYPTO_EddsaPublicKey place_pub_key; + + /** + * Public key of ego. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key; + + /** + * Operation ID. + */ + uint64_t op_id GNUNET_PACKED; +}; + + +struct MsgProcRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_SOCIAL_MSG_PROC_SET + */ + struct GNUNET_MessageHeader header; + + /** + * @see enum GNUNET_SOCIAL_MsgProcFlags + */ + uint32_t flags; + + /* Followed by char *method_prefix */ +}; + + +struct HostEnterRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_SOCIAL_HOST_ENTER + */ + struct GNUNET_MessageHeader header; + + uint32_t policy GNUNET_PACKED; + + struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key; + + struct GNUNET_CRYPTO_EddsaPublicKey place_pub_key; + + struct GNUNET_CRYPTO_EddsaPrivateKey place_key; + + /* Followed by char *app_id */ +}; + + +struct GuestEnterRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_SOCIAL_GUEST_ENTER + */ + struct GNUNET_MessageHeader header; + + uint32_t relay_count GNUNET_PACKED; + + struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key; + + struct GNUNET_CRYPTO_EddsaPublicKey place_pub_key; + + struct GNUNET_PeerIdentity origin; + + uint32_t flags GNUNET_PACKED; + + /* Followed by char *app_id */ + /* Followed by struct GNUNET_PeerIdentity relays[relay_count] */ + /* Followed by struct GNUNET_MessageHeader *join_msg */ +}; + + +/** Compatible parts of HostEnterRequest and GuestEnterRequest */ +struct PlaceEnterRequest +{ + struct GNUNET_MessageHeader header; + + uint32_t reserved GNUNET_PACKED; + + struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key; + + struct GNUNET_CRYPTO_EddsaPublicKey place_pub_key; +}; + + +struct EgoPlacePublicKey +{ + struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key; + struct GNUNET_CRYPTO_EddsaPublicKey place_pub_key; +}; + + +struct GuestEnterByNameRequest +{ + /** + * Type: GNUNET_MESSAGE_TYPE_SOCIAL_GUEST_ENTER_BY_NAME + */ + struct GNUNET_MessageHeader header; + + struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key; + + /* Followed by char *app_id */ + /* Followed by char *gns_name */ + /* Followed by char *password */ + /* Followed by struct GNUNET_MessageHeader *join_msg */ +}; + + +struct ZoneAddPlaceRequest +{ + struct GNUNET_MessageHeader header; + + uint32_t relay_count GNUNET_PACKED; + + /** + * Operation ID. + */ + uint64_t op_id; + + /** + * Expiration time: absolute value in us. + */ + uint64_t expiration_time; + + struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key; + + struct GNUNET_CRYPTO_EddsaPublicKey place_pub_key; + + struct GNUNET_PeerIdentity origin; + + /* Followed by const char *name */ + /* Followed by const char *password */ + /* Followed by struct GNUNET_PeerIdentity *relays[relay_count] */ +}; + + +struct ZoneAddNymRequest +{ + struct GNUNET_MessageHeader header; + + /** + * Operation ID. + */ + uint64_t op_id; + + /** + * Expiration time: absolute value in us. + */ + uint64_t expiration_time; + + struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key; + + struct GNUNET_CRYPTO_EcdsaPublicKey nym_pub_key; + + /* Followed by const char *name */ +}; + + +/**** service -> library ****/ + + +struct AppEgoMessage +{ + /** + * Type: GNUNET_MESSAGE_TYPE_SOCIAL_APP_EGO + */ + struct GNUNET_MessageHeader header; + + /** + * Public key of ego. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key; + + /* Followed by char *name */ +}; + + +struct AppPlaceMessage +{ + /** + * Type: GNUNET_MESSAGE_TYPE_SOCIAL_APP_PLACE + */ + struct GNUNET_MessageHeader header; + + struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key; + + struct GNUNET_CRYPTO_EddsaPublicKey place_pub_key; + + uint8_t is_host; + + uint8_t place_state; +}; + + +struct HostEnterAck { + /** + * Type: GNUNET_MESSAGE_TYPE_SOCIAL_HOST_ENTER_ACK + */ + struct GNUNET_MessageHeader header; + + /** + * Status code for the operation. + */ + uint32_t result_code GNUNET_PACKED; + + /** + * Last message ID sent to the channel. + */ + uint64_t max_message_id GNUNET_PACKED; + + /** + * Public key of the place. + */ + struct GNUNET_CRYPTO_EddsaPublicKey place_pub_key; +}; + + +GNUNET_NETWORK_STRUCT_END + +#endif diff --git a/src/social/social_api.c b/src/social/social_api.c new file mode 100644 index 0000000..9b96580 --- /dev/null +++ b/src/social/social_api.c @@ -0,0 +1,2827 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ + +/** + * @author Gabor X Toth + * + * @file + * Social service; implements social interactions using the PSYC service. + */ + +#include +#include + +#include "platform.h" +#include "gnunet_util_lib.h" +#include "gnunet_psyc_service.h" +#include "gnunet_psyc_util_lib.h" +#include "gnunet_social_service.h" +#include "social.h" + +#define LOG(kind,...) GNUNET_log_from (kind, "social-api",__VA_ARGS__) + +/** + * Handle for an ego. + */ +struct GNUNET_SOCIAL_Ego +{ + struct GNUNET_CRYPTO_EcdsaPublicKey pub_key; + struct GNUNET_HashCode pub_key_hash; + char *name; +}; + + +/** + * Handle for a pseudonym of another user in the network. + */ +struct GNUNET_SOCIAL_Nym +{ + struct GNUNET_CRYPTO_EcdsaPublicKey pub_key; + struct GNUNET_HashCode pub_key_hash; +}; + + +/** + * Handle for an application. + */ +struct GNUNET_SOCIAL_App +{ + /** + * Configuration to use. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Client connection to the service. + */ + struct GNUNET_MQ_Handle *mq; + + /** + * Message to send on connect. + */ + struct GNUNET_MQ_Envelope *connect_env; + + /** + * Time to wait until we try to reconnect on failure. + */ + struct GNUNET_TIME_Relative reconnect_delay; + + /** + * Task for reconnecting when the listener fails. + */ + struct GNUNET_SCHEDULER_Task *reconnect_task; + + /** + * Async operations. + */ + struct GNUNET_OP_Handle *op; + + /** + * Function called after disconnected from the service. + */ + GNUNET_ContinuationCallback disconnect_cb; + + /** + * Closure for @a disconnect_cb. + */ + void *disconnect_cls; + + /** + * Application ID. + */ + char *id; + + /** + * Hash map of all egos. + * pub_key_hash -> struct GNUNET_SOCIAL_Ego * + */ + struct GNUNET_CONTAINER_MultiHashMap *egos; + + GNUNET_SOCIAL_AppEgoCallback ego_cb; + GNUNET_SOCIAL_AppHostPlaceCallback host_cb; + GNUNET_SOCIAL_AppGuestPlaceCallback guest_cb; + GNUNET_SOCIAL_AppConnectedCallback connected_cb; + void *cb_cls; +}; + + +struct GNUNET_SOCIAL_HostConnection +{ + struct GNUNET_SOCIAL_App *app; + + struct AppPlaceMessage plc_msg; +}; + + +struct GNUNET_SOCIAL_GuestConnection +{ + struct GNUNET_SOCIAL_App *app; + + struct AppPlaceMessage plc_msg; +}; + + +/** + * Handle for a place where social interactions happen. + */ +struct GNUNET_SOCIAL_Place +{ + /** + * Configuration to use. + */ + const struct GNUNET_CONFIGURATION_Handle *cfg; + + /** + * Client connection to the service. + */ + struct GNUNET_MQ_Handle *mq; + + /** + * Message to send on connect. + */ + struct GNUNET_MQ_Envelope *connect_env; + + /** + * Time to wait until we try to reconnect on failure. + */ + struct GNUNET_TIME_Relative reconnect_delay; + + /** + * Task for reconnecting when the listener fails. + */ + struct GNUNET_SCHEDULER_Task *reconnect_task; + + /** + * Async operations. + */ + struct GNUNET_OP_Handle *op; + + /** + * Transmission handle. + */ + struct GNUNET_PSYC_TransmitHandle *tmit; + + /** + * Slicer for processing incoming messages. + */ + struct GNUNET_PSYC_Slicer *slicer; + + // FIXME: do we need is_disconnecing like on the psyc and multicast APIs? + /** + * Function called after disconnected from the service. + */ + GNUNET_ContinuationCallback disconnect_cb; + + /** + * Closure for @a disconnect_cb. + */ + void *disconnect_cls; + + /** + * Public key of the place. + */ + struct GNUNET_CRYPTO_EddsaPublicKey pub_key; + + /** + * Public key of the ego. + */ + struct GNUNET_CRYPTO_EcdsaPublicKey ego_pub_key; + + /** + * Does this place belong to a host (#GNUNET_YES) or guest (#GNUNET_NO)? + */ + uint8_t is_host; +}; + + +/** + * Host handle for a place that we entered. + */ +struct GNUNET_SOCIAL_Host +{ + struct GNUNET_SOCIAL_Place plc; + + /** + * Slicer for processing incoming messages from guests. + */ + struct GNUNET_PSYC_Slicer *slicer; + + GNUNET_SOCIAL_HostEnterCallback enter_cb; + + GNUNET_SOCIAL_AnswerDoorCallback answer_door_cb; + + GNUNET_SOCIAL_FarewellCallback farewell_cb; + + /** + * Closure for callbacks. + */ + void *cb_cls; + + struct GNUNET_SOCIAL_Nym *notice_place_leave_nym; + struct GNUNET_PSYC_Environment *notice_place_leave_env; +}; + + +/** + * Guest handle for place that we entered. + */ +struct GNUNET_SOCIAL_Guest +{ + struct GNUNET_SOCIAL_Place plc; + + GNUNET_SOCIAL_GuestEnterCallback enter_cb; + + GNUNET_SOCIAL_EntryDecisionCallback entry_dcsn_cb; + + /** + * Closure for callbacks. + */ + void *cb_cls; +}; + + +/** + * Hash map of all nyms. + * pub_key_hash -> struct GNUNET_SOCIAL_Nym * + */ +struct GNUNET_CONTAINER_MultiHashMap *nyms; + + +/** + * Handle for an announcement request. + */ +struct GNUNET_SOCIAL_Announcement +{ + +}; + + +/** + * A talk request. + */ +struct GNUNET_SOCIAL_TalkRequest +{ + +}; + + +/** + * A history lesson. + */ +struct GNUNET_SOCIAL_HistoryRequest +{ + /** + * Place. + */ + struct GNUNET_SOCIAL_Place *plc; + + /** + * Operation ID. + */ + uint64_t op_id; + + /** + * Slicer for processing incoming messages. + */ + struct GNUNET_PSYC_Slicer *slicer; + + /** + * Function to call when the operation finished. + */ + GNUNET_ResultCallback result_cb; + + /** + * Closure for @a result_cb. + */ + void *cls; +}; + + +struct GNUNET_SOCIAL_LookHandle +{ + /** + * Place. + */ + struct GNUNET_SOCIAL_Place *plc; + + /** + * Operation ID. + */ + uint64_t op_id; + + /** + * State variable result callback. + */ + GNUNET_PSYC_StateVarCallback var_cb; + + /** + * Function to call when the operation finished. + */ + GNUNET_ResultCallback result_cb; + + /** + * Name of current modifier being received. + */ + char *mod_name; + + /** + * Size of current modifier value being received. + */ + size_t mod_value_size; + + /** + * Remaining size of current modifier value still to be received. + */ + size_t mod_value_remaining; + + /** + * Closure for @a result_cb. + */ + void *cls; +}; + + +struct ZoneAddPlaceHandle +{ + GNUNET_ResultCallback result_cb; + void *result_cls; +}; + + +struct ZoneAddNymHandle +{ + GNUNET_ResultCallback result_cb; + void *result_cls; +}; + + +/*** CLEANUP / DISCONNECT ***/ + + +static void +host_cleanup (struct GNUNET_SOCIAL_Host *hst) +{ + if (NULL != hst->slicer) + { + GNUNET_PSYC_slicer_destroy (hst->slicer); + hst->slicer = NULL; + } + GNUNET_free (hst); +} + + +static void +guest_cleanup (struct GNUNET_SOCIAL_Guest *gst) +{ + GNUNET_free (gst); +} + + +static void +place_cleanup (struct GNUNET_SOCIAL_Place *plc) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "cleaning up place %p\n", + plc); + if (NULL != plc->tmit) + { + GNUNET_PSYC_transmit_destroy (plc->tmit); + plc->tmit = NULL; + } + if (NULL != plc->connect_env) + { + GNUNET_MQ_discard (plc->connect_env); + plc->connect_env = NULL; + } + if (NULL != plc->mq) + { + GNUNET_MQ_destroy (plc->mq); + plc->mq = NULL; + } + if (NULL != plc->disconnect_cb) + { + plc->disconnect_cb (plc->disconnect_cls); + plc->disconnect_cb = NULL; + } + + (GNUNET_YES == plc->is_host) + ? host_cleanup ((struct GNUNET_SOCIAL_Host *) plc) + : guest_cleanup ((struct GNUNET_SOCIAL_Guest *) plc); +} + + +static void +place_disconnect (struct GNUNET_SOCIAL_Place *plc) +{ + place_cleanup (plc); +} + + +/*** NYM ***/ + +static struct GNUNET_SOCIAL_Nym * +nym_get_or_create (const struct GNUNET_CRYPTO_EcdsaPublicKey *pub_key) +{ + struct GNUNET_SOCIAL_Nym *nym = NULL; + struct GNUNET_HashCode pub_key_hash; + + if (NULL == pub_key) + return NULL; + + GNUNET_CRYPTO_hash (pub_key, sizeof (*pub_key), &pub_key_hash); + + if (NULL == nyms) + nyms = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_YES); + else + nym = GNUNET_CONTAINER_multihashmap_get (nyms, &pub_key_hash); + + if (NULL == nym) + { + nym = GNUNET_new (struct GNUNET_SOCIAL_Nym); + nym->pub_key = *pub_key; + nym->pub_key_hash = pub_key_hash; + GNUNET_CONTAINER_multihashmap_put (nyms, &nym->pub_key_hash, nym, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_UNIQUE_FAST); + } + return nym; +} + + +static void +nym_destroy (struct GNUNET_SOCIAL_Nym *nym) +{ + GNUNET_CONTAINER_multihashmap_remove (nyms, &nym->pub_key_hash, nym); + GNUNET_free (nym); +} + + +/*** MESSAGE HANDLERS ***/ + +/** _notice_place_leave from guests */ + +static void +host_recv_notice_place_leave_method (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_PSYC_MessageMethod *meth, + uint64_t message_id, + const char *method_name) +{ + struct GNUNET_SOCIAL_Host *hst = cls; + + if (0 == memcmp (&(struct GNUNET_CRYPTO_EcdsaPublicKey) {}, + &msg->slave_pub_key, sizeof (msg->slave_pub_key))) + return; + + struct GNUNET_SOCIAL_Nym *nym = nym_get_or_create (&msg->slave_pub_key); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Host received method for message ID %" PRIu64 " from nym %s: %s\n", + message_id, GNUNET_h2s (&nym->pub_key_hash), method_name); + + hst->notice_place_leave_nym = (struct GNUNET_SOCIAL_Nym *) nym; + hst->notice_place_leave_env = GNUNET_PSYC_env_create (); + + char *str = GNUNET_CRYPTO_ecdsa_public_key_to_string (&hst->notice_place_leave_nym->pub_key); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "_notice_place_leave: got method from nym %s (%s).\n", + GNUNET_h2s (&hst->notice_place_leave_nym->pub_key_hash), str); + GNUNET_free (str); +} + + +static void +host_recv_notice_place_leave_modifier (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + enum GNUNET_PSYC_Operator oper, + const char *name, + const void *value, + uint16_t value_size, + uint16_t full_value_size) +{ + struct GNUNET_SOCIAL_Host *hst = cls; + if (NULL == hst->notice_place_leave_env) + return; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "Host received modifier for _notice_place_leave message with ID %" PRIu64 ":\n" + "%c%s: %.*s\n", + message_id, oper, name, value_size, (const char *) value); + + /* skip _nym, it's added later in eom() */ + if (0 == memcmp (name, "_nym", sizeof ("_nym")) + || 0 == memcmp (name, "_nym_", sizeof ("_nym_") - 1)) + return; + + GNUNET_PSYC_env_add (hst->notice_place_leave_env, + GNUNET_PSYC_OP_SET, name, value, value_size); +} + + +static void +host_recv_notice_place_leave_eom (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + uint8_t is_cancelled) +{ + struct GNUNET_SOCIAL_Host *hst = cls; + if (NULL == hst->notice_place_leave_env) + return; + + char *str = GNUNET_CRYPTO_ecdsa_public_key_to_string (&hst->notice_place_leave_nym->pub_key); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "_notice_place_leave: got EOM from nym %s (%s).\n", + GNUNET_h2s (&hst->notice_place_leave_nym->pub_key_hash), str); + GNUNET_free (str); + + if (GNUNET_YES != is_cancelled) + { + if (NULL != hst->farewell_cb) + hst->farewell_cb (hst->cb_cls, hst->notice_place_leave_nym, + hst->notice_place_leave_env); + /* announce leaving guest to place */ + GNUNET_PSYC_env_add (hst->notice_place_leave_env, GNUNET_PSYC_OP_SET, + "_nym", hst->notice_place_leave_nym, + sizeof (*hst->notice_place_leave_nym)); + GNUNET_SOCIAL_host_announce (hst, "_notice_place_leave", + hst->notice_place_leave_env, + NULL, NULL, GNUNET_SOCIAL_ANNOUNCE_NONE); + nym_destroy (hst->notice_place_leave_nym); + } + GNUNET_PSYC_env_destroy (hst->notice_place_leave_env); + hst->notice_place_leave_env = NULL; +} + + +/*** PLACE ***/ + + +static int +check_place_result (void *cls, + const struct GNUNET_OperationResultMessage *res) +{ + uint16_t size = ntohs (res->header.size); + if (size < sizeof (*res)) + { /* Error, message too small. */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +static void +handle_place_result (void *cls, + const struct GNUNET_OperationResultMessage *res) +{ + struct GNUNET_SOCIAL_Place *plc = cls; + + uint16_t size = ntohs (res->header.size); + uint16_t data_size = size - sizeof (*res); + const char *data = (0 < data_size) ? (const char *) &res[1] : NULL; + + GNUNET_OP_result (plc->op, GNUNET_ntohll (res->op_id), + GNUNET_ntohll (res->result_code), + data, data_size, NULL); +} + + +static int +check_app_result (void *cls, + const struct GNUNET_OperationResultMessage *res) +{ + uint16_t size = ntohs (res->header.size); + if (size < sizeof (*res)) + { /* Error, message too small. */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +static void +handle_app_result (void *cls, + const struct GNUNET_OperationResultMessage *res) +{ + struct GNUNET_SOCIAL_App *app = cls; + + uint16_t size = ntohs (res->header.size); + uint16_t data_size = size - sizeof (*res); + const char *data = (0 < data_size) ? (const char *) &res[1] : NULL; + + GNUNET_OP_result (app->op, GNUNET_ntohll (res->op_id), + GNUNET_ntohll (res->result_code), + data, data_size, NULL); +} + + +static void +op_recv_history_result (void *cls, int64_t result, + const void *err_msg, uint16_t err_msg_size) +{ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Received history replay result: %" PRId64 ".\n", result); + + struct GNUNET_SOCIAL_HistoryRequest *hist = cls; + + if (NULL != hist->result_cb) + hist->result_cb (hist->cls, result, err_msg, err_msg_size); + + GNUNET_free (hist); +} + + +static void +op_recv_state_result (void *cls, int64_t result, + const void *err_msg, uint16_t err_msg_size) +{ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Received state request result: %" PRId64 ".\n", result); + + struct GNUNET_SOCIAL_LookHandle *look = cls; + + if (NULL != look->result_cb) + look->result_cb (look->cls, result, err_msg, err_msg_size); + + GNUNET_free (look); +} + + +static int +check_place_history_result (void *cls, + const struct GNUNET_OperationResultMessage *res) +{ + struct GNUNET_PSYC_MessageHeader * + pmsg = (struct GNUNET_PSYC_MessageHeader *) GNUNET_MQ_extract_nested_mh (res); + uint16_t size = ntohs (res->header.size); + + if (NULL == pmsg || size < sizeof (*res) + sizeof (*pmsg)) + { /* Error, message too small. */ + GNUNET_break (0); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +static void +handle_place_history_result (void *cls, + const struct GNUNET_OperationResultMessage *res) +{ + struct GNUNET_SOCIAL_Place *plc = cls; + struct GNUNET_PSYC_MessageHeader * + pmsg = (struct GNUNET_PSYC_MessageHeader *) GNUNET_MQ_extract_nested_mh (res); + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "%p Received historic fragment for message #%" PRIu64 ".\n", + plc, GNUNET_ntohll (pmsg->message_id)); + + GNUNET_ResultCallback result_cb = NULL; + struct GNUNET_SOCIAL_HistoryRequest *hist = NULL; + + if (GNUNET_YES != GNUNET_OP_get (plc->op, + GNUNET_ntohll (res->op_id), + &result_cb, (void *) &hist, NULL)) + { /* Operation not found. */ + LOG (GNUNET_ERROR_TYPE_WARNING, + "%p Replay operation not found for historic fragment of message #%" + PRIu64 ".\n", + plc, GNUNET_ntohll (pmsg->message_id)); + return; + } + + GNUNET_PSYC_slicer_message (hist->slicer, + (const struct GNUNET_PSYC_MessageHeader *) pmsg); +} + + +static int +check_place_state_result (void *cls, + const struct GNUNET_OperationResultMessage *res) +{ + const struct GNUNET_MessageHeader *mod = GNUNET_MQ_extract_nested_mh (res); + if (NULL == mod) + { + GNUNET_break_op (0); + LOG (GNUNET_ERROR_TYPE_WARNING, + "Invalid modifier in state result\n"); + return GNUNET_SYSERR; + } + + uint16_t size = ntohs (res->header.size); + uint16_t mod_size = ntohs (mod->size); + if (size - sizeof (*res) != mod_size) + { + GNUNET_break_op (0); + LOG (GNUNET_ERROR_TYPE_WARNING, + "Invalid modifier size in state result: %u - %u != %u\n", + ntohs (res->header.size), sizeof (*res), mod_size); + return GNUNET_SYSERR; + } + return GNUNET_OK; +} + + +static void +handle_place_state_result (void *cls, + const struct GNUNET_OperationResultMessage *res) +{ + struct GNUNET_SOCIAL_Place *plc = cls; + + GNUNET_ResultCallback result_cb = NULL; + struct GNUNET_SOCIAL_LookHandle *look = NULL; + + if (GNUNET_YES != GNUNET_OP_get (plc->op, + GNUNET_ntohll (res->op_id), + &result_cb, (void *) &look, NULL)) + { /* Operation not found. */ + return; + } + + const struct GNUNET_MessageHeader *mod = GNUNET_MQ_extract_nested_mh (res); + uint16_t mod_size = ntohs (mod->size); + + switch (ntohs (mod->type)) + { + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MODIFIER: + { + const struct GNUNET_PSYC_MessageModifier * + pmod = (const struct GNUNET_PSYC_MessageModifier *) mod; + + const char *name = (const char *) &pmod[1]; + uint16_t name_size = ntohs (pmod->name_size); + if (0 == name_size + || mod_size - sizeof (*pmod) < name_size + || '\0' != name[name_size - 1]) + { + GNUNET_break_op (0); + LOG (GNUNET_ERROR_TYPE_WARNING, + "Invalid modifier name in state result\n"); + return; + } + look->mod_value_size = ntohs (pmod->value_size); + look->var_cb (look->cls, mod, name, name + name_size, + mod_size - sizeof (*mod) - name_size, + look->mod_value_size); + if (look->mod_value_size > mod_size - sizeof (*mod) - name_size) + { + look->mod_value_remaining = look->mod_value_size; + look->mod_name = GNUNET_malloc (name_size); + GNUNET_memcpy (look->mod_name, name, name_size); + } + break; + } + + case GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_MOD_CONT: + look->var_cb (look->cls, mod, look->mod_name, (const char *) &mod[1], + mod_size - sizeof (*mod), look->mod_value_size); + look->mod_value_remaining -= mod_size - sizeof (*mod); + if (0 == look->mod_value_remaining) + { + GNUNET_free (look->mod_name); + } + break; + } +} + + +static void +handle_place_message_ack (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + struct GNUNET_SOCIAL_Place *plc = cls; + + GNUNET_PSYC_transmit_got_ack (plc->tmit); +} + + +static int +check_place_message (void *cls, + const struct GNUNET_PSYC_MessageHeader *pmsg) +{ + return GNUNET_OK; +} + + +static void +handle_place_message (void *cls, + const struct GNUNET_PSYC_MessageHeader *pmsg) +{ + struct GNUNET_SOCIAL_Place *plc = cls; + + GNUNET_PSYC_slicer_message (plc->slicer, pmsg); +} + + +static int +check_host_message (void *cls, + const struct GNUNET_PSYC_MessageHeader *pmsg) +{ + return GNUNET_OK; +} + + +static void +handle_host_message (void *cls, + const struct GNUNET_PSYC_MessageHeader *pmsg) +{ + struct GNUNET_SOCIAL_Host *hst = cls; + + GNUNET_PSYC_slicer_message (hst->slicer, pmsg); + GNUNET_PSYC_slicer_message (hst->plc.slicer, pmsg); +} + + +static void +handle_host_enter_ack (void *cls, + const struct HostEnterAck *hack) +{ + struct GNUNET_SOCIAL_Host *hst = cls; + + hst->plc.pub_key = hack->place_pub_key; + + int32_t result = ntohl (hack->result_code); + if (NULL != hst->enter_cb) + hst->enter_cb (hst->cb_cls, result, &hack->place_pub_key, + GNUNET_ntohll (hack->max_message_id)); +} + + +static int +check_host_enter_request (void *cls, + const struct GNUNET_PSYC_JoinRequestMessage *req) +{ + return GNUNET_OK; +} + + +static void +handle_host_enter_request (void *cls, + const struct GNUNET_PSYC_JoinRequestMessage *req) +{ + struct GNUNET_SOCIAL_Host *hst = cls; + + if (NULL == hst->answer_door_cb) + return; + + const char *method_name = NULL; + struct GNUNET_PSYC_Environment *env = NULL; + struct GNUNET_PSYC_MessageHeader *entry_pmsg = NULL; + const void *data = NULL; + uint16_t data_size = 0; + char *str; + const struct GNUNET_PSYC_Message *join_msg = NULL; + + do + { + if (sizeof (*req) + sizeof (*join_msg) <= ntohs (req->header.size)) + { + join_msg = (struct GNUNET_PSYC_Message *) GNUNET_MQ_extract_nested_mh (req); + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Received join_msg of type %u and size %u.\n", + ntohs (join_msg->header.type), ntohs (join_msg->header.size)); + + env = GNUNET_PSYC_env_create (); + entry_pmsg = GNUNET_PSYC_message_header_create_from_psyc (join_msg); + if (GNUNET_OK != GNUNET_PSYC_message_parse (entry_pmsg, &method_name, env, + &data, &data_size)) + { + GNUNET_break_op (0); + str = GNUNET_CRYPTO_ecdsa_public_key_to_string (&req->slave_pub_key); + LOG (GNUNET_ERROR_TYPE_WARNING, + "Ignoring invalid entry request from nym %s.\n", + str); + GNUNET_free (str); + break; + } + } + + struct GNUNET_SOCIAL_Nym *nym = nym_get_or_create (&req->slave_pub_key); + hst->answer_door_cb (hst->cb_cls, nym, method_name, env, + data, data_size); + } while (0); + + if (NULL != env) + GNUNET_PSYC_env_destroy (env); + if (NULL != entry_pmsg) + GNUNET_free (entry_pmsg); +} + + +static void +handle_guest_enter_ack (void *cls, + const struct GNUNET_PSYC_CountersResultMessage *cres) +{ + struct GNUNET_SOCIAL_Guest *gst = cls; + + int32_t result = ntohl (cres->result_code); + if (NULL != gst->enter_cb) + gst->enter_cb (gst->cb_cls, result, &gst->plc.pub_key, + GNUNET_ntohll (cres->max_message_id)); +} + + +static int +check_guest_enter_decision (void *cls, + const struct GNUNET_PSYC_JoinDecisionMessage *dcsn) +{ + return GNUNET_OK; +} + + +static void +handle_guest_enter_decision (void *cls, + const struct GNUNET_PSYC_JoinDecisionMessage *dcsn) +{ + struct GNUNET_SOCIAL_Guest *gst = cls; + + struct GNUNET_PSYC_Message *pmsg = NULL; + if (ntohs (dcsn->header.size) > sizeof (*dcsn)) + pmsg = (struct GNUNET_PSYC_Message *) GNUNET_MQ_extract_nested_mh (dcsn); + + if (NULL != gst->entry_dcsn_cb) + gst->entry_dcsn_cb (gst->cb_cls, ntohl (dcsn->is_admitted), pmsg); +} + + +static int +check_app_ego (void *cls, + const struct AppEgoMessage *emsg) +{ + return GNUNET_OK; +} + + +static void +handle_app_ego (void *cls, + const struct AppEgoMessage *emsg) +{ + struct GNUNET_SOCIAL_App *app = cls; + + uint16_t name_size = ntohs (emsg->header.size) - sizeof (*emsg); + + struct GNUNET_HashCode ego_pub_hash; + GNUNET_CRYPTO_hash (&emsg->ego_pub_key, sizeof (emsg->ego_pub_key), + &ego_pub_hash); + + struct GNUNET_SOCIAL_Ego * + ego = GNUNET_CONTAINER_multihashmap_get (app->egos, &ego_pub_hash); + if (NULL == ego) + { + ego = GNUNET_malloc (sizeof (*ego)); + ego->pub_key = emsg->ego_pub_key; + ego->name = GNUNET_malloc (name_size); + GNUNET_memcpy (ego->name, &emsg[1], name_size); + } + else + { + ego->name = GNUNET_realloc (ego->name, name_size); + GNUNET_memcpy (ego->name, &emsg[1], name_size); + } + + GNUNET_CONTAINER_multihashmap_put (app->egos, &ego_pub_hash, ego, + GNUNET_CONTAINER_MULTIHASHMAPOPTION_REPLACE); + + if (NULL != app->ego_cb) + app->ego_cb (app->cb_cls, ego, &ego->pub_key, ego->name); +} + + +static void +handle_app_ego_end (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + //struct GNUNET_SOCIAL_App *app = cls; +} + + +static int +check_app_place (void *cls, + const struct AppPlaceMessage *pmsg) +{ + return GNUNET_OK; +} + + +static void +handle_app_place (void *cls, + const struct AppPlaceMessage *pmsg) +{ + struct GNUNET_SOCIAL_App *app = cls; + + if ((GNUNET_YES == pmsg->is_host && NULL == app->host_cb) + || (GNUNET_NO == pmsg->is_host && NULL == app->guest_cb)) + return; + + struct GNUNET_HashCode ego_pub_hash; + GNUNET_CRYPTO_hash (&pmsg->ego_pub_key, sizeof (pmsg->ego_pub_key), + &ego_pub_hash); + struct GNUNET_SOCIAL_Ego * + ego = GNUNET_CONTAINER_multihashmap_get (app->egos, &ego_pub_hash); + if (NULL == ego) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Failure to obtain ego %s.\n", + GNUNET_h2s (&ego_pub_hash)); + GNUNET_break (0); + return; + } + + if (GNUNET_YES == pmsg->is_host) + { + if (NULL != app->host_cb) { + struct GNUNET_SOCIAL_HostConnection *hconn = GNUNET_malloc (sizeof (*hconn)); + hconn->app = app; + hconn->plc_msg = *pmsg; + app->host_cb (app->cb_cls, hconn, ego, &pmsg->place_pub_key, pmsg->place_state); + GNUNET_free (hconn); + } + } + else if (NULL != app->guest_cb) + { + struct GNUNET_SOCIAL_GuestConnection *gconn = GNUNET_malloc (sizeof (*gconn)); + gconn->app = app; + gconn->plc_msg = *pmsg; + app->guest_cb (app->cb_cls, gconn, ego, &pmsg->place_pub_key, pmsg->place_state); + GNUNET_free (gconn); + } +} + + +static void +handle_app_place_end (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + struct GNUNET_SOCIAL_App *app = cls; + + if (NULL != app->connected_cb) + app->connected_cb (app->cb_cls); +} + + +/** + * Handler for a #GNUNET_MESSAGE_TYPE_SOCIAL_PLACE_LEAVE_ACK message received + * from the social service. + * + * @param cls the place of type `struct GNUNET_SOCIAL_Place` + * @param msg the message received from the service + */ +static void +handle_place_leave_ack (void *cls, + const struct GNUNET_MessageHeader *msg) +{ + struct GNUNET_SOCIAL_Place *plc = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%s left place %p\n", + plc->is_host ? "host" : "guest", + plc); + place_disconnect (plc); +} + + +/*** HOST ***/ + + +static void +host_connect (struct GNUNET_SOCIAL_Host *hst); + + +static void +host_reconnect (void *cls) +{ + host_connect (cls); +} + + +/** + * Host client disconnected from service. + * + * Reconnect after backoff period. + */ +static void +host_disconnected (void *cls, enum GNUNET_MQ_Error error) +{ + struct GNUNET_SOCIAL_Host *hst = cls; + struct GNUNET_SOCIAL_Place *plc = &hst->plc; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Host client disconnected (%d), re-connecting\n", + (int) error); + if (NULL != plc->tmit) + { + GNUNET_PSYC_transmit_destroy (plc->tmit); + plc->tmit = NULL; + } + if (NULL != plc->mq) + { + GNUNET_MQ_destroy (plc->mq); + plc->mq = NULL; + } + + plc->reconnect_task = GNUNET_SCHEDULER_add_delayed (plc->reconnect_delay, + host_reconnect, + hst); + plc->reconnect_delay = GNUNET_TIME_STD_BACKOFF (plc->reconnect_delay); +} + + +static void +host_connect (struct GNUNET_SOCIAL_Host *hst) +{ + struct GNUNET_SOCIAL_Place *plc = &hst->plc; + + struct GNUNET_MQ_MessageHandler handlers[] = { + GNUNET_MQ_hd_fixed_size (host_enter_ack, + GNUNET_MESSAGE_TYPE_SOCIAL_HOST_ENTER_ACK, + struct HostEnterAck, + hst), + GNUNET_MQ_hd_fixed_size (place_leave_ack, + GNUNET_MESSAGE_TYPE_SOCIAL_PLACE_LEAVE_ACK, + struct GNUNET_MessageHeader, + plc), + GNUNET_MQ_hd_var_size (host_enter_request, + GNUNET_MESSAGE_TYPE_PSYC_JOIN_REQUEST, + struct GNUNET_PSYC_JoinRequestMessage, + hst), + GNUNET_MQ_hd_var_size (host_message, + GNUNET_MESSAGE_TYPE_PSYC_MESSAGE, + struct GNUNET_PSYC_MessageHeader, + hst), + GNUNET_MQ_hd_fixed_size (place_message_ack, + GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_ACK, + struct GNUNET_MessageHeader, + plc), + GNUNET_MQ_hd_var_size (place_history_result, + GNUNET_MESSAGE_TYPE_PSYC_HISTORY_RESULT, + struct GNUNET_OperationResultMessage, + plc), + GNUNET_MQ_hd_var_size (place_state_result, + GNUNET_MESSAGE_TYPE_PSYC_STATE_RESULT, + struct GNUNET_OperationResultMessage, + plc), + GNUNET_MQ_hd_var_size (place_result, + GNUNET_MESSAGE_TYPE_PSYC_RESULT_CODE, + struct GNUNET_OperationResultMessage, + plc), + GNUNET_MQ_handler_end () + }; + + plc->mq = GNUNET_CLIENT_connect (plc->cfg, "social", + handlers, host_disconnected, hst); + GNUNET_assert (NULL != plc->mq); + plc->tmit = GNUNET_PSYC_transmit_create (plc->mq); + + GNUNET_MQ_send_copy (plc->mq, plc->connect_env); +} + + +/** + * Enter a place as host. + * + * A place is created upon first entering, and it is active until permanently + * left using GNUNET_SOCIAL_host_leave(). + * + * @param app + * Application handle. + * @param ego + * Identity of the host. + * @param policy + * Policy specifying entry and history restrictions for the place. + * @param slicer + * Slicer to handle incoming messages. + * @param enter_cb + * Function called when the place is entered and ready to use. + * @param answer_door_cb + * Function to handle new nyms that want to enter. + * @param farewell_cb + * Function to handle departing nyms. + * @param cls + * Closure for the callbacks. + * + * @return Handle for the host. This handle contains the pubkey. + */ +struct GNUNET_SOCIAL_Host * +GNUNET_SOCIAL_host_enter (const struct GNUNET_SOCIAL_App *app, + const struct GNUNET_SOCIAL_Ego *ego, + enum GNUNET_PSYC_Policy policy, + struct GNUNET_PSYC_Slicer *slicer, + GNUNET_SOCIAL_HostEnterCallback enter_cb, + GNUNET_SOCIAL_AnswerDoorCallback answer_door_cb, + GNUNET_SOCIAL_FarewellCallback farewell_cb, + void *cls) +{ + struct GNUNET_SOCIAL_Host *hst = GNUNET_malloc (sizeof (*hst)); + struct GNUNET_SOCIAL_Place *plc = &hst->plc; + + plc->cfg = app->cfg; + plc->is_host = GNUNET_YES; + plc->slicer = slicer; + + hst->enter_cb = enter_cb; + hst->answer_door_cb = answer_door_cb; + hst->farewell_cb = farewell_cb; + hst->cb_cls = cls; + + plc->op = GNUNET_OP_create (); + + hst->slicer = GNUNET_PSYC_slicer_create (); + GNUNET_PSYC_slicer_method_add (hst->slicer, "_notice_place_leave", NULL, + host_recv_notice_place_leave_method, + host_recv_notice_place_leave_modifier, + NULL, host_recv_notice_place_leave_eom, hst); + + uint16_t app_id_size = strlen (app->id) + 1; + struct HostEnterRequest *hreq; + plc->connect_env = GNUNET_MQ_msg_extra (hreq, app_id_size, + GNUNET_MESSAGE_TYPE_SOCIAL_HOST_ENTER); + hreq->policy = policy; + hreq->ego_pub_key = ego->pub_key; + GNUNET_memcpy (&hreq[1], app->id, app_id_size); + + host_connect (hst); + return hst; +} + + +/** + * Reconnect to an already entered place as host. + * + * @param hconn + * Host connection handle. + * @see GNUNET_SOCIAL_app_connect() & GNUNET_SOCIAL_AppHostPlaceCallback() + * @param slicer + * Slicer to handle incoming messages. + * @param enter_cb + * Function called when the place is entered and ready to use. + * @param answer_door_cb + * Function to handle new nyms that want to enter. + * @param farewell_cb + * Function to handle departing nyms. + * @param cls + * Closure for the callbacks. + * + * @return Handle for the host. + */ + struct GNUNET_SOCIAL_Host * +GNUNET_SOCIAL_host_enter_reconnect (struct GNUNET_SOCIAL_HostConnection *hconn, + struct GNUNET_PSYC_Slicer *slicer, + GNUNET_SOCIAL_HostEnterCallback enter_cb, + GNUNET_SOCIAL_AnswerDoorCallback answer_door_cb, + GNUNET_SOCIAL_FarewellCallback farewell_cb, + void *cls) +{ + struct GNUNET_SOCIAL_Host *hst = GNUNET_malloc (sizeof (*hst)); + struct GNUNET_SOCIAL_Place *plc = &hst->plc; + + hst->enter_cb = enter_cb; + hst->answer_door_cb = answer_door_cb; + hst->farewell_cb = farewell_cb; + hst->cb_cls = cls; + + plc->cfg = hconn->app->cfg; + plc->is_host = GNUNET_YES; + plc->slicer = slicer; + plc->pub_key = hconn->plc_msg.place_pub_key; + plc->ego_pub_key = hconn->plc_msg.ego_pub_key; + + plc->op = GNUNET_OP_create (); + + hst->slicer = GNUNET_PSYC_slicer_create (); + GNUNET_PSYC_slicer_method_add (hst->slicer, "_notice_place_leave", NULL, + host_recv_notice_place_leave_method, + host_recv_notice_place_leave_modifier, + NULL, host_recv_notice_place_leave_eom, hst); + + size_t app_id_size = strlen (hconn->app->id) + 1; + struct HostEnterRequest *hreq; + plc->connect_env = GNUNET_MQ_msg_extra (hreq, app_id_size, + GNUNET_MESSAGE_TYPE_SOCIAL_HOST_ENTER); + hreq->place_pub_key = hconn->plc_msg.place_pub_key; + hreq->ego_pub_key = hconn->plc_msg.ego_pub_key; + GNUNET_memcpy (&hreq[1], hconn->app->id, app_id_size); + + host_connect (hst); + return hst; +} + + +/** + * Decision whether to admit @a nym into the place or refuse entry. + * + * @param hst + * Host of the place. + * @param nym + * Handle for the entity that wanted to enter. + * @param is_admitted + * #GNUNET_YES if @a nym is admitted, + * #GNUNET_NO if @a nym is refused entry, + * #GNUNET_SYSERR if we cannot answer the request. + * @param method_name + * Method name for the rejection message. + * @param env + * Environment containing variables for the message, or NULL. + * @param data + * Data for the rejection message to send back. + * @param data_size + * Number of bytes in @a data for method. + * @return #GNUNET_OK on success, + * #GNUNET_SYSERR if the message is too large. + */ +int +GNUNET_SOCIAL_host_entry_decision (struct GNUNET_SOCIAL_Host *hst, + struct GNUNET_SOCIAL_Nym *nym, + int is_admitted, + const struct GNUNET_PSYC_Message *entry_resp) +{ + struct GNUNET_SOCIAL_Place *plc = &hst->plc; + struct GNUNET_PSYC_JoinDecisionMessage *dcsn; + uint16_t entry_resp_size + = (NULL != entry_resp) ? ntohs (entry_resp->header.size) : 0; + + if (GNUNET_MULTICAST_FRAGMENT_MAX_PAYLOAD < sizeof (*dcsn) + entry_resp_size) + return GNUNET_SYSERR; + + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (dcsn, entry_resp_size, + GNUNET_MESSAGE_TYPE_PSYC_JOIN_DECISION); + dcsn->is_admitted = htonl (is_admitted); + dcsn->slave_pub_key = nym->pub_key; + + if (0 < entry_resp_size) + GNUNET_memcpy (&dcsn[1], entry_resp, entry_resp_size); + + GNUNET_MQ_send (plc->mq, env); + return GNUNET_OK; +} + + +/** + * Throw @a nym out of the place. + * + * The @a nym reference will remain valid until the + * #GNUNET_SOCIAL_FarewellCallback is invoked, + * which should be very soon after this call. + * + * @param host + * Host of the place. + * @param nym + * Handle for the entity to be ejected. + * @param env + * Environment for the message or NULL. + */ +void +GNUNET_SOCIAL_host_eject (struct GNUNET_SOCIAL_Host *hst, + const struct GNUNET_SOCIAL_Nym *nym, + struct GNUNET_PSYC_Environment *e) +{ + struct GNUNET_PSYC_Environment *env = e; + if (NULL == env) + env = GNUNET_PSYC_env_create (); + GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_SET, + "_nym", &nym->pub_key, sizeof (nym->pub_key)); + GNUNET_SOCIAL_host_announce (hst, "_notice_place_leave", env, NULL, NULL, + GNUNET_SOCIAL_ANNOUNCE_NONE); + if (NULL == e) + GNUNET_PSYC_env_destroy (env); +} + + +/** + * Get the public key of @a ego. + * + * @param ego + * Ego. + * + * @return Public key of ego. + */ +const struct GNUNET_CRYPTO_EcdsaPublicKey * +GNUNET_SOCIAL_ego_get_pub_key (const struct GNUNET_SOCIAL_Ego *ego) +{ + return &ego->pub_key; +} + + +/** + * Get the hash of the public key of @a ego. + * + * @param ego + * Ego. + * + * @return Hash of the public key of @a ego. + */ +const struct GNUNET_HashCode * +GNUNET_SOCIAL_ego_get_pub_key_hash (const struct GNUNET_SOCIAL_Ego *ego) +{ + return &ego->pub_key_hash; +} + + +/** + * Get the name of @a ego. + * + * @param ego + * Ego. + * + * @return Public key of @a ego. + */ +const char * +GNUNET_SOCIAL_ego_get_name (const struct GNUNET_SOCIAL_Ego *ego) +{ + return ego->name; +} + + +/** + * Get the public key of @a nym. + * + * Suitable, for example, to be used with GNUNET_SOCIAL_zone_add_nym(). + * + * @param nym + * Pseudonym. + * + * @return Public key of @a nym. + */ +const struct GNUNET_CRYPTO_EcdsaPublicKey * +GNUNET_SOCIAL_nym_get_pub_key (const struct GNUNET_SOCIAL_Nym *nym) +{ + return &nym->pub_key; +} + + +/** + * Get the hash of the public key of @a nym. + * + * @param nym + * Pseudonym. + * + * @return Hash of the public key of @a nym. + */ +const struct GNUNET_HashCode * +GNUNET_SOCIAL_nym_get_pub_key_hash (const struct GNUNET_SOCIAL_Nym *nym) +{ + return &nym->pub_key_hash; +} + + +/** + * Send a message to all nyms that are present in the place. + * + * This function is restricted to the host. Nyms can only send requests + * to the host who can decide to relay it to everyone in the place. + * + * @param host Host of the place. + * @param method_name Method to use for the announcement. + * @param env Environment containing variables for the message and operations + * on objects of the place. Can be NULL. + * @param notify Function to call to get the payload of the announcement. + * @param notify_cls Closure for @a notify. + * @param flags Flags for this announcement. + * + * @return NULL on error (announcement already in progress?). + */ +struct GNUNET_SOCIAL_Announcement * +GNUNET_SOCIAL_host_announce (struct GNUNET_SOCIAL_Host *hst, + const char *method_name, + const struct GNUNET_PSYC_Environment *env, + GNUNET_PSYC_TransmitNotifyData notify_data, + void *notify_data_cls, + enum GNUNET_SOCIAL_AnnounceFlags flags) +{ + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "PSYC_transmit_message for host, method: %s\n", + method_name); + if (GNUNET_OK == + GNUNET_PSYC_transmit_message (hst->plc.tmit, method_name, env, + NULL, notify_data, notify_data_cls, flags)) + return (struct GNUNET_SOCIAL_Announcement *) hst->plc.tmit; + else + return NULL; +} + + +/** + * Resume transmitting announcement. + * + * @param a + * The announcement to resume. + */ +void +GNUNET_SOCIAL_host_announce_resume (struct GNUNET_SOCIAL_Announcement *a) +{ + GNUNET_PSYC_transmit_resume ((struct GNUNET_PSYC_TransmitHandle *) a); +} + + +/** + * Cancel announcement. + * + * @param a + * The announcement to cancel. + */ +void +GNUNET_SOCIAL_host_announce_cancel (struct GNUNET_SOCIAL_Announcement *a) +{ + GNUNET_PSYC_transmit_cancel ((struct GNUNET_PSYC_TransmitHandle *) a); +} + + +/** + * Obtain handle for a hosted place. + * + * The returned handle can be used to access the place API. + * + * @param host Handle for the host. + * + * @return Handle for the hosted place, valid as long as @a host is valid. + */ +struct GNUNET_SOCIAL_Place * +GNUNET_SOCIAL_host_get_place (struct GNUNET_SOCIAL_Host *hst) +{ + return &hst->plc; +} + + +/** + * Disconnect from a home. + * + * Invalidates host handle. + * + * @param hst + * The host to disconnect. + */ +void +GNUNET_SOCIAL_host_disconnect (struct GNUNET_SOCIAL_Host *hst, + GNUNET_ContinuationCallback disconnect_cb, + void *cls) +{ + struct GNUNET_SOCIAL_Place *plc = &hst->plc; + + plc->disconnect_cb = disconnect_cb; + plc->disconnect_cls = cls; + place_disconnect (plc); +} + + +/** + * Stop hosting the home. + * + * Sends a _notice_place_closing announcement to the home. + * Invalidates host handle. + * + * @param hst + * The host leaving. + * @param env + * Environment for the message or NULL. + * _nym is set to @e nym regardless whether an @e env is provided. + * @param disconnect_cb + * Function called after the host left the place + * and disconnected from the social service. + * @param cls + * Closure for @a disconnect_cb. + */ +void +GNUNET_SOCIAL_host_leave (struct GNUNET_SOCIAL_Host *hst, + const struct GNUNET_PSYC_Environment *env, + GNUNET_ContinuationCallback disconnect_cb, + void *cls) +{ + struct GNUNET_MQ_Envelope *envelope; + + GNUNET_SOCIAL_host_announce (hst, "_notice_place_closing", env, NULL, NULL, + GNUNET_SOCIAL_ANNOUNCE_NONE); + hst->plc.disconnect_cb = disconnect_cb; + hst->plc.disconnect_cls = cls; + envelope = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_SOCIAL_PLACE_LEAVE); + GNUNET_MQ_send (hst->plc.mq, + envelope); +} + + +/*** GUEST ***/ + + +static void +guest_connect (struct GNUNET_SOCIAL_Guest *gst); + + +static void +guest_reconnect (void *cls) +{ + guest_connect (cls); +} + + +/** + * Guest client disconnected from service. + * + * Reconnect after backoff period. + */ +static void +guest_disconnected (void *cls, enum GNUNET_MQ_Error error) +{ + struct GNUNET_SOCIAL_Guest *gst = cls; + struct GNUNET_SOCIAL_Place *plc = &gst->plc; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Guest client disconnected (%d), re-connecting\n", + (int) error); + if (NULL != plc->tmit) + { + GNUNET_PSYC_transmit_destroy (plc->tmit); + plc->tmit = NULL; + } + if (NULL != plc->mq) + { + GNUNET_MQ_destroy (plc->mq); + plc->mq = NULL; + } + + plc->reconnect_task = GNUNET_SCHEDULER_add_delayed (plc->reconnect_delay, + guest_reconnect, + gst); + plc->reconnect_delay = GNUNET_TIME_STD_BACKOFF (plc->reconnect_delay); +} + + +static void +guest_connect (struct GNUNET_SOCIAL_Guest *gst) +{ + struct GNUNET_SOCIAL_Place *plc = &gst->plc; + + struct GNUNET_MQ_MessageHandler handlers[] = { + GNUNET_MQ_hd_fixed_size (guest_enter_ack, + GNUNET_MESSAGE_TYPE_SOCIAL_GUEST_ENTER_ACK, + struct GNUNET_PSYC_CountersResultMessage, + gst), + GNUNET_MQ_hd_fixed_size (place_leave_ack, + GNUNET_MESSAGE_TYPE_SOCIAL_PLACE_LEAVE_ACK, + struct GNUNET_MessageHeader, + plc), + GNUNET_MQ_hd_var_size (guest_enter_decision, + GNUNET_MESSAGE_TYPE_PSYC_JOIN_DECISION, + struct GNUNET_PSYC_JoinDecisionMessage, + gst), + GNUNET_MQ_hd_var_size (place_message, + GNUNET_MESSAGE_TYPE_PSYC_MESSAGE, + struct GNUNET_PSYC_MessageHeader, + plc), + GNUNET_MQ_hd_fixed_size (place_message_ack, + GNUNET_MESSAGE_TYPE_PSYC_MESSAGE_ACK, + struct GNUNET_MessageHeader, + plc), + GNUNET_MQ_hd_var_size (place_history_result, + GNUNET_MESSAGE_TYPE_PSYC_HISTORY_RESULT, + struct GNUNET_OperationResultMessage, + plc), + GNUNET_MQ_hd_var_size (place_state_result, + GNUNET_MESSAGE_TYPE_PSYC_STATE_RESULT, + struct GNUNET_OperationResultMessage, + plc), + GNUNET_MQ_hd_var_size (place_result, + GNUNET_MESSAGE_TYPE_PSYC_RESULT_CODE, + struct GNUNET_OperationResultMessage, + plc), + GNUNET_MQ_handler_end () + }; + + plc->mq = GNUNET_CLIENT_connect (plc->cfg, "social", + handlers, guest_disconnected, gst); + GNUNET_assert (NULL != plc->mq); + plc->tmit = GNUNET_PSYC_transmit_create (plc->mq); + + GNUNET_MQ_send_copy (plc->mq, plc->connect_env); +} + + +static struct GNUNET_MQ_Envelope * +guest_enter_request_create (const char *app_id, + const struct GNUNET_CRYPTO_EcdsaPublicKey *ego_pub_key, + const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key, + const struct GNUNET_PeerIdentity *origin, + size_t relay_count, + const struct GNUNET_PeerIdentity *relays, + const struct GNUNET_PSYC_Message *join_msg) +{ + uint16_t app_id_size = strlen (app_id) + 1; + uint16_t join_msg_size = ntohs (join_msg->header.size); + uint16_t relay_size = relay_count * sizeof (*relays); + + struct GuestEnterRequest *greq; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (greq, app_id_size + relay_size + join_msg_size, + GNUNET_MESSAGE_TYPE_SOCIAL_GUEST_ENTER); + greq->place_pub_key = *place_pub_key; + greq->ego_pub_key = *ego_pub_key; + greq->origin = *origin; + greq->relay_count = htonl (relay_count); + + char *p = (char *) &greq[1]; + GNUNET_memcpy (p, app_id, app_id_size); + p += app_id_size; + + if (0 < relay_size) + { + GNUNET_memcpy (p, relays, relay_size); + p += relay_size; + } + + GNUNET_memcpy (p, join_msg, join_msg_size); + return env; +} + + +/** + * Request entry to a place as a guest. + * + * @param app + * Application handle. + * @param ego + * Identity of the guest. + * @param place_pub_key + * Public key of the place to enter. + * @param flags + * Flags for the entry. + * @param origin + * Peer identity of the origin of the underlying multicast group. + * @param relay_count + * Number of elements in the @a relays array. + * @param relays + * Relays for the underlying multicast group. + * @param method_name + * Method name for the message. + * @param env + * Environment containing variables for the message, or NULL. + * @param data + * Payload for the message to give to the enter callback. + * @param data_size + * Number of bytes in @a data. + * @param slicer + * Slicer to use for processing incoming requests from guests. + * + * @return NULL on errors, otherwise handle for the guest. + */ +struct GNUNET_SOCIAL_Guest * +GNUNET_SOCIAL_guest_enter (const struct GNUNET_SOCIAL_App *app, + const struct GNUNET_SOCIAL_Ego *ego, + const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key, + enum GNUNET_PSYC_SlaveJoinFlags flags, + const struct GNUNET_PeerIdentity *origin, + uint32_t relay_count, + const struct GNUNET_PeerIdentity *relays, + const struct GNUNET_PSYC_Message *entry_msg, + struct GNUNET_PSYC_Slicer *slicer, + GNUNET_SOCIAL_GuestEnterCallback local_enter_cb, + GNUNET_SOCIAL_EntryDecisionCallback entry_dcsn_cb, + void *cls) +{ + struct GNUNET_SOCIAL_Guest *gst = GNUNET_malloc (sizeof (*gst)); + struct GNUNET_SOCIAL_Place *plc = &gst->plc; + + plc->ego_pub_key = ego->pub_key; + plc->pub_key = *place_pub_key; + plc->cfg = app->cfg; + plc->is_host = GNUNET_NO; + plc->slicer = slicer; + + plc->op = GNUNET_OP_create (); + + plc->connect_env + = guest_enter_request_create (app->id, &ego->pub_key, &plc->pub_key, + origin, relay_count, relays, entry_msg); + + gst->enter_cb = local_enter_cb; + gst->entry_dcsn_cb = entry_dcsn_cb; + gst->cb_cls = cls; + + guest_connect (gst); + return gst; +} + + +/** + * Request entry to a place by name as a guest. + * + * @param app + * Application handle. + * @param ego + * Identity of the guest. + * @param gns_name + * GNS name of the place to enter. Either in the form of + * 'room.friend.gnu', or 'NYMPUBKEY.zkey'. This latter case refers to + * the 'PLACE' record of the empty label ("+") in the GNS zone with the + * nym's public key 'NYMPUBKEY', and can be used to request entry to a + * pseudonym's place directly. + * @param password + * Password to decrypt the record, or NULL for cleartext records. + * @param join_msg + * Entry request message or NULL. + * @param slicer + * Slicer to use for processing incoming requests from guests. + * @param local_enter_cb + * Called upon connection established to the social service. + * @param entry_decision_cb + * Called upon receiving entry decision. + * + * @return NULL on errors, otherwise handle for the guest. + */ +struct GNUNET_SOCIAL_Guest * +GNUNET_SOCIAL_guest_enter_by_name (const struct GNUNET_SOCIAL_App *app, + const struct GNUNET_SOCIAL_Ego *ego, + const char *gns_name, + const char *password, + const struct GNUNET_PSYC_Message *join_msg, + struct GNUNET_PSYC_Slicer *slicer, + GNUNET_SOCIAL_GuestEnterCallback local_enter_cb, + GNUNET_SOCIAL_EntryDecisionCallback entry_decision_cb, + void *cls) +{ + struct GNUNET_SOCIAL_Guest *gst = GNUNET_malloc (sizeof (*gst)); + struct GNUNET_SOCIAL_Place *plc = &gst->plc; + + if (NULL == password) + password = ""; + + uint16_t app_id_size = strlen (app->id) + 1; + uint16_t gns_name_size = strlen (gns_name) + 1; + uint16_t password_size = strlen (password) + 1; + + uint16_t join_msg_size = 0; + if (NULL != join_msg) + join_msg_size = ntohs (join_msg->header.size); + + struct GuestEnterByNameRequest *greq; + plc->connect_env + = GNUNET_MQ_msg_extra (greq, app_id_size + gns_name_size + + password_size + join_msg_size, + GNUNET_MESSAGE_TYPE_SOCIAL_GUEST_ENTER_BY_NAME); + + greq->ego_pub_key = ego->pub_key; + + char *p = (char *) &greq[1]; + GNUNET_memcpy (p, app->id, app_id_size); + p += app_id_size; + GNUNET_memcpy (p, gns_name, gns_name_size); + p += gns_name_size; + GNUNET_memcpy (p, password, password_size); + p += password_size; + if (NULL != join_msg) + GNUNET_memcpy (p, join_msg, join_msg_size); + + plc->ego_pub_key = ego->pub_key; + plc->cfg = app->cfg; + plc->is_host = GNUNET_NO; + plc->slicer = slicer; + + plc->op = GNUNET_OP_create (); + + gst->enter_cb = local_enter_cb; + gst->entry_dcsn_cb = entry_decision_cb; + gst->cb_cls = cls; + + guest_connect (gst); + return gst; +} + + +struct ReconnectContext +{ + struct GNUNET_SOCIAL_Guest *guest; + int *result; + int64_t *max_message_id; + GNUNET_SOCIAL_GuestEnterCallback enter_cb; + void *enter_cls; +}; + + +static void +guest_enter_reconnect_cb (void *cls, + int result, + const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key, + uint64_t max_message_id) +{ + struct ReconnectContext *reconnect_ctx = cls; + + GNUNET_assert (NULL != reconnect_ctx); + reconnect_ctx->result = GNUNET_new (int); + *(reconnect_ctx->result) = result; + reconnect_ctx->max_message_id = GNUNET_new (int64_t); + *(reconnect_ctx->max_message_id) = max_message_id; +} + + +static void +guest_entry_dcsn_reconnect_cb (void *cls, + int is_admitted, + const struct GNUNET_PSYC_Message *entry_resp) +{ + struct ReconnectContext *reconnect_ctx = cls; + struct GNUNET_SOCIAL_Guest *gst = reconnect_ctx->guest; + + GNUNET_assert (NULL != reconnect_ctx); + GNUNET_assert (NULL != reconnect_ctx->result); + GNUNET_assert (NULL != reconnect_ctx->max_message_id); + if (GNUNET_YES != is_admitted) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Guest was rejected after calling " + "GNUNET_SOCIAL_guest_enter_reconnect ()\n"); + } + else if (NULL != reconnect_ctx->enter_cb) + { + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "guest reconnected!\n"); + reconnect_ctx->enter_cb (reconnect_ctx->enter_cls, + *(reconnect_ctx->result), + &gst->plc.pub_key, + *(reconnect_ctx->max_message_id)); + } + GNUNET_free (reconnect_ctx->result); + GNUNET_free (reconnect_ctx->max_message_id); + GNUNET_free (reconnect_ctx); +} + + +/** + * Reconnect to an already entered place as guest. + * + * @param gconn + * Guest connection handle. + * @see GNUNET_SOCIAL_app_connect() & GNUNET_SOCIAL_AppGuestPlaceCallback() + * @param flags + * Flags for the entry. + * @param slicer + * Slicer to use for processing incoming requests from guests. + * @param enter_cb + * Called upon re-entering is complete. + * @param entry_decision_cb + * Called upon receiving entry decision. + * + * @return NULL on errors, otherwise handle for the guest. + */ +struct GNUNET_SOCIAL_Guest * +GNUNET_SOCIAL_guest_enter_reconnect (struct GNUNET_SOCIAL_GuestConnection *gconn, + enum GNUNET_PSYC_SlaveJoinFlags flags, + struct GNUNET_PSYC_Slicer *slicer, + GNUNET_SOCIAL_GuestEnterCallback enter_cb, + void *cls) +{ + struct GNUNET_SOCIAL_Guest *gst = GNUNET_malloc (sizeof (*gst)); + struct GNUNET_SOCIAL_Place *plc = &gst->plc; + struct ReconnectContext *reconnect_ctx; + + uint16_t app_id_size = strlen (gconn->app->id) + 1; + struct GuestEnterRequest *greq; + plc->connect_env + = GNUNET_MQ_msg_extra (greq, app_id_size, + GNUNET_MESSAGE_TYPE_SOCIAL_GUEST_ENTER); + greq->ego_pub_key = gconn->plc_msg.ego_pub_key; + greq->place_pub_key = gconn->plc_msg.place_pub_key; + greq->flags = htonl (flags); + + GNUNET_memcpy (&greq[1], gconn->app->id, app_id_size); + + plc->cfg = gconn->app->cfg; + plc->is_host = GNUNET_NO; + plc->slicer = slicer; + plc->pub_key = gconn->plc_msg.place_pub_key; + plc->ego_pub_key = gconn->plc_msg.ego_pub_key; + + reconnect_ctx = GNUNET_new (struct ReconnectContext); + reconnect_ctx->guest = gst; + reconnect_ctx->enter_cb = enter_cb; + reconnect_ctx->enter_cls = cls; + + plc->op = GNUNET_OP_create (); + gst->enter_cb = &guest_enter_reconnect_cb; + gst->entry_dcsn_cb = &guest_entry_dcsn_reconnect_cb; + gst->cb_cls = reconnect_ctx; + + guest_connect (gst); + return gst; +} + + +/** + * Talk to the host of the place. + * + * @param place + * Place where we want to talk to the host. + * @param method_name + * Method to invoke on the host. + * @param env + * Environment containing variables for the message, or NULL. + * @param notify_data + * Function to use to get the payload for the method. + * @param notify_data_cls + * Closure for @a notify_data. + * @param flags + * Flags for the message being sent. + * + * @return NULL if we are already trying to talk to the host, + * otherwise handle to cancel the request. + */ +struct GNUNET_SOCIAL_TalkRequest * +GNUNET_SOCIAL_guest_talk (struct GNUNET_SOCIAL_Guest *gst, + const char *method_name, + const struct GNUNET_PSYC_Environment *env, + GNUNET_PSYC_TransmitNotifyData notify_data, + void *notify_data_cls, + enum GNUNET_SOCIAL_TalkFlags flags) +{ + struct GNUNET_SOCIAL_Place *plc = &gst->plc; + GNUNET_assert (NULL != plc->tmit); + + if (GNUNET_OK == + GNUNET_PSYC_transmit_message (plc->tmit, method_name, env, + NULL, notify_data, notify_data_cls, flags)) + return (struct GNUNET_SOCIAL_TalkRequest *) plc->tmit; + else + return NULL; +} + + +/** + * Resume talking to the host of the place. + * + * @param tr + * Talk request to resume. + */ +void +GNUNET_SOCIAL_guest_talk_resume (struct GNUNET_SOCIAL_TalkRequest *tr) +{ + GNUNET_PSYC_transmit_resume ((struct GNUNET_PSYC_TransmitHandle *) tr); +} + + +/** + * Cancel talking to the host of the place. + * + * @param tr + * Talk request to cancel. + */ +void +GNUNET_SOCIAL_guest_talk_cancel (struct GNUNET_SOCIAL_TalkRequest *tr) +{ + GNUNET_PSYC_transmit_cancel ( (struct GNUNET_PSYC_TransmitHandle *) tr); +} + + +/** + * Disconnect from a place. + * + * Invalidates guest handle. + * + * @param gst + * The guest to disconnect. + */ +void +GNUNET_SOCIAL_guest_disconnect (struct GNUNET_SOCIAL_Guest *gst, + GNUNET_ContinuationCallback disconnect_cb, + void *cls) +{ + struct GNUNET_SOCIAL_Place *plc = &gst->plc; + + plc->disconnect_cb = disconnect_cb; + plc->disconnect_cls = cls; + place_disconnect (plc); +} + + +/** + * Leave a place temporarily or permanently. + * + * Notifies the owner of the place about leaving, and destroys the place handle. + * + * @param place + * Place to leave. + * @param keep_active + * Keep place active after last application disconnected. + * #GNUNET_YES or #GNUNET_NO + * @param env + * Optional environment for the leave message if @a keep_active + * is #GNUNET_NO. NULL if not needed. + * @param leave_cb + * Called upon disconnecting from the social service. + */ +void +GNUNET_SOCIAL_guest_leave (struct GNUNET_SOCIAL_Guest *gst, + struct GNUNET_PSYC_Environment *env, + GNUNET_ContinuationCallback disconnect_cb, + void *cls) +{ + struct GNUNET_MQ_Envelope *envelope; + + GNUNET_SOCIAL_guest_talk (gst, "_notice_place_leave", env, NULL, NULL, + GNUNET_SOCIAL_TALK_NONE); + gst->plc.disconnect_cb = disconnect_cb; + gst->plc.disconnect_cls = cls; + envelope = GNUNET_MQ_msg_header (GNUNET_MESSAGE_TYPE_SOCIAL_PLACE_LEAVE); + GNUNET_MQ_send (gst->plc.mq, + envelope); +} + + +/** + * Obtain handle for a place entered as guest. + * + * The returned handle can be used to access the place API. + * + * @param guest Handle for the guest. + * + * @return Handle for the place, valid as long as @a guest is valid. + */ +struct GNUNET_SOCIAL_Place * +GNUNET_SOCIAL_guest_get_place (struct GNUNET_SOCIAL_Guest *gst) +{ + return &gst->plc; +} + + +/** + * Obtain the public key of a place. + * + * @param plc + * Place. + * + * @return Public key of the place. + */ +const struct GNUNET_CRYPTO_EddsaPublicKey * +GNUNET_SOCIAL_place_get_pub_key (const struct GNUNET_SOCIAL_Place *plc) +{ + return &plc->pub_key; +} + + +/** + * Set message processing @a flags for a @a method_prefix. + * + * @param plc + * Place. + * @param method_prefix + * Method prefix @a flags apply to. + * @param flags + * The flags that apply to a matching @a method_prefix. + */ +void +GNUNET_SOCIAL_place_msg_proc_set (struct GNUNET_SOCIAL_Place *plc, + const char *method_prefix, + enum GNUNET_SOCIAL_MsgProcFlags flags) +{ + GNUNET_assert (NULL != method_prefix); + struct MsgProcRequest *mpreq; + uint16_t method_size = strnlen (method_prefix, + GNUNET_MAX_MESSAGE_SIZE + - sizeof (*mpreq)) + 1; + GNUNET_assert ('\0' == method_prefix[method_size - 1]); + + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (mpreq, method_size, + GNUNET_MESSAGE_TYPE_SOCIAL_MSG_PROC_SET); + mpreq->flags = htonl (flags); + GNUNET_memcpy (&mpreq[1], method_prefix, method_size); + + GNUNET_MQ_send (plc->mq, env); +} + + +/** + * Clear all message processing flags previously set for this place. + */ +void +GNUNET_SOCIAL_place_msg_proc_clear (struct GNUNET_SOCIAL_Place *plc) +{ + struct GNUNET_MessageHeader *req; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (req, GNUNET_MESSAGE_TYPE_SOCIAL_MSG_PROC_CLEAR); + + GNUNET_MQ_send (plc->mq, env); +} + + +static struct GNUNET_SOCIAL_HistoryRequest * +place_history_replay (struct GNUNET_SOCIAL_Place *plc, + uint64_t start_message_id, + uint64_t end_message_id, + uint64_t message_limit, + const char *method_prefix, + uint32_t flags, + struct GNUNET_PSYC_Slicer *slicer, + GNUNET_ResultCallback result_cb, + void *cls) +{ + struct GNUNET_PSYC_HistoryRequestMessage *req; + struct GNUNET_SOCIAL_HistoryRequest *hist = GNUNET_malloc (sizeof (*hist)); + hist->plc = plc; + hist->slicer = slicer; + hist->result_cb = result_cb; + hist->cls = cls; + hist->op_id = GNUNET_OP_add (plc->op, op_recv_history_result, hist, NULL); + + GNUNET_assert (NULL != method_prefix); + uint16_t method_size = strnlen (method_prefix, + GNUNET_MAX_MESSAGE_SIZE + - sizeof (*req)) + 1; + GNUNET_assert ('\0' == method_prefix[method_size - 1]); + + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (req, method_size, + GNUNET_MESSAGE_TYPE_PSYC_HISTORY_REPLAY); + req->start_message_id = GNUNET_htonll (start_message_id); + req->end_message_id = GNUNET_htonll (end_message_id); + req->message_limit = GNUNET_htonll (message_limit); + req->flags = htonl (flags); + req->op_id = GNUNET_htonll (hist->op_id); + GNUNET_memcpy (&req[1], method_prefix, method_size); + + GNUNET_MQ_send (plc->mq, env); + return hist; +} + + +/** + * Learn about the history of a place. + * + * Messages are returned through the @a slicer function + * and have the #GNUNET_PSYC_MESSAGE_HISTORIC flag set. + * + * @param place + * Place we want to learn more about. + * @param start_message_id + * First historic message we are interested in. + * @param end_message_id + * Last historic message we are interested in (inclusive). + * @param method_prefix + * Only retrieve messages with this method prefix. + * @param flags + * OR'ed GNUNET_PSYC_HistoryReplayFlags + * @param slicer + * Slicer to use for retrieved messages. + * Can be the same as the slicer of the place. + * @param result_cb + * Function called after all messages retrieved. + * NULL if not needed. + * @param cls Closure for @a result_cb. + */ +struct GNUNET_SOCIAL_HistoryRequest * +GNUNET_SOCIAL_place_history_replay (struct GNUNET_SOCIAL_Place *plc, + uint64_t start_message_id, + uint64_t end_message_id, + const char *method_prefix, + uint32_t flags, + struct GNUNET_PSYC_Slicer *slicer, + GNUNET_ResultCallback result_cb, + void *cls) +{ + return place_history_replay (plc, start_message_id, end_message_id, 0, + method_prefix, flags, slicer, result_cb, cls); +} + + +/** + * Learn about the history of a place. + * + * Sends messages through the slicer function of the place where + * start_message_id <= message_id <= end_message_id. + * The messages will have the #GNUNET_PSYC_MESSAGE_HISTORIC flag set. + * + * To get the latest message, use 0 for both the start and end message ID. + * + * @param place + * Place we want to learn more about. + * @param message_limit + * Maximum number of historic messages we are interested in. + * @param method_prefix + * Only retrieve messages with this method prefix. + * @param flags + * OR'ed GNUNET_PSYC_HistoryReplayFlags + * @param result_cb + * Function called after all messages retrieved. + * NULL if not needed. + * @param cls Closure for @a result_cb. + */ +struct GNUNET_SOCIAL_HistoryRequest * +GNUNET_SOCIAL_place_history_replay_latest (struct GNUNET_SOCIAL_Place *plc, + uint64_t message_limit, + const char *method_prefix, + uint32_t flags, + struct GNUNET_PSYC_Slicer *slicer, + GNUNET_ResultCallback result_cb, + void *cls) +{ + return place_history_replay (plc, 0, 0, message_limit, method_prefix, flags, + slicer, result_cb, cls); +} + + +/** + * Cancel learning about the history of a place. + * + * @param hist + * History lesson to cancel. + */ +void +GNUNET_SOCIAL_place_history_replay_cancel (struct GNUNET_SOCIAL_HistoryRequest *hist) +{ + GNUNET_OP_remove (hist->plc->op, hist->op_id); + GNUNET_free (hist); +} + + +/** + * Request matching state variables. + */ +static struct GNUNET_SOCIAL_LookHandle * +place_state_get (struct GNUNET_SOCIAL_Place *plc, + uint16_t type, const char *name, + GNUNET_PSYC_StateVarCallback var_cb, + GNUNET_ResultCallback result_cb, void *cls) +{ + struct GNUNET_PSYC_StateRequestMessage *req; + struct GNUNET_SOCIAL_LookHandle *look = GNUNET_malloc (sizeof (*look)); + look->plc = plc; + look->var_cb = var_cb; + look->result_cb = result_cb; + look->cls = cls; + look->op_id = GNUNET_OP_add (plc->op, &op_recv_state_result, look, NULL); + + GNUNET_assert (NULL != name); + size_t name_size = strnlen (name, GNUNET_MAX_MESSAGE_SIZE + - sizeof (*req)) + 1; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (req, name_size, type); + req->op_id = GNUNET_htonll (look->op_id); + GNUNET_memcpy (&req[1], name, name_size); + + GNUNET_MQ_send (plc->mq, env); + return look; +} + + +/** + * Look at a particular object in the place. + * + * The best matching object is returned (its name might be less specific than + * what was requested). + * + * @param place + * The place where to look. + * @param full_name + * Full name of the object. + * @param value_size + * Set to the size of the returned value. + * + * @return NULL if there is no such object at this place. + */ +struct GNUNET_SOCIAL_LookHandle * +GNUNET_SOCIAL_place_look_at (struct GNUNET_SOCIAL_Place *plc, + const char *full_name, + GNUNET_PSYC_StateVarCallback var_cb, + GNUNET_ResultCallback result_cb, + void *cls) +{ + return place_state_get (plc, GNUNET_MESSAGE_TYPE_PSYC_STATE_GET, + full_name, var_cb, result_cb, cls); +} + + +/** + * Look for objects in the place with a matching name prefix. + * + * @param place + * The place where to look. + * @param name_prefix + * Look at objects with names beginning with this value. + * @param var_cb + * Function to call for each object found. + * @param cls + * Closure for callback function. + * + * @return Handle that can be used to stop looking at objects. + */ +struct GNUNET_SOCIAL_LookHandle * +GNUNET_SOCIAL_place_look_for (struct GNUNET_SOCIAL_Place *plc, + const char *name_prefix, + GNUNET_PSYC_StateVarCallback var_cb, + GNUNET_ResultCallback result_cb, + void *cls) +{ + return place_state_get (plc, GNUNET_MESSAGE_TYPE_PSYC_STATE_GET_PREFIX, + name_prefix, var_cb, result_cb, cls); +} + + +/** + * Cancel a state request operation. + * + * @param sr + * Handle for the operation to cancel. + */ +void +GNUNET_SOCIAL_place_look_cancel (struct GNUNET_SOCIAL_LookHandle *look) +{ + GNUNET_OP_remove (look->plc->op, look->op_id); + GNUNET_free (look); +} + + +static void +op_recv_zone_add_place_result (void *cls, int64_t result, + const void *err_msg, uint16_t err_msg_size) +{ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Received zone add place result: %" PRId64 ".\n", result); + + struct ZoneAddPlaceHandle *add_plc = cls; + if (NULL != add_plc->result_cb) + add_plc->result_cb (add_plc->result_cls, result, err_msg, err_msg_size); + + GNUNET_free (add_plc); +} + + +/** + * Advertise @e place in the GNS zone of @e ego. + * + * @param app + * Application handle. + * @param ego + * Ego. + * @param place_pub_key + * Public key of place to add. + * @param name + * The name for the PLACE record to put in the zone. + * @param password + * Password used to encrypt the record or NULL to keep it cleartext. + * @param relay_count + * Number of elements in the @a relays array. + * @param relays + * List of relays to put in the PLACE record to advertise + * as entry points to the place in addition to the origin. + * @param expiration_time + * Expiration time of the record, use 0 to remove the record. + * @param result_cb + * Function called with the result of the operation. + * @param result_cls + * Closure for @a result_cb + * + * @return #GNUNET_OK if the request was sent, + * #GNUNET_SYSERR on error, e.g. the name/password is too long. + */ +int +GNUNET_SOCIAL_zone_add_place (const struct GNUNET_SOCIAL_App *app, + const struct GNUNET_SOCIAL_Ego *ego, + const char *name, + const char *password, + const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key, + const struct GNUNET_PeerIdentity *origin, + uint32_t relay_count, + const struct GNUNET_PeerIdentity *relays, + struct GNUNET_TIME_Absolute expiration_time, + GNUNET_ResultCallback result_cb, + void *result_cls) +{ + struct ZoneAddPlaceRequest *preq; + size_t name_size = strlen (name) + 1; + size_t password_size = strlen (password) + 1; + size_t relay_size = relay_count * sizeof (*relays); + size_t payload_size = name_size + password_size + relay_size; + + if (GNUNET_MAX_MESSAGE_SIZE < sizeof (*preq) + payload_size) + return GNUNET_SYSERR; + + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (preq, payload_size, + GNUNET_MESSAGE_TYPE_SOCIAL_ZONE_ADD_PLACE); + preq->expiration_time = GNUNET_htonll (expiration_time.abs_value_us); + preq->ego_pub_key = ego->pub_key; + preq->place_pub_key = *place_pub_key; + preq->origin = *origin; + preq->relay_count = htonl (relay_count); + + char *p = (char *) &preq[1]; + GNUNET_memcpy (p, name, name_size); + p += name_size; + GNUNET_memcpy (p, password, password_size); + p += password_size; + GNUNET_memcpy (p, relays, relay_size); + + struct ZoneAddPlaceHandle * add_plc = GNUNET_malloc (sizeof (*add_plc)); + add_plc->result_cb = result_cb; + add_plc->result_cls = result_cls; + + preq->op_id = GNUNET_htonll (GNUNET_OP_add (app->op, + op_recv_zone_add_place_result, + add_plc, NULL)); + + GNUNET_MQ_send (app->mq, env); + return GNUNET_OK; +} + + +static void +op_recv_zone_add_nym_result (void *cls, int64_t result, + const void *err_msg, uint16_t err_msg_size) +{ + LOG (GNUNET_ERROR_TYPE_DEBUG, + "Received zone add nym result: %" PRId64 ".\n", result); + + struct ZoneAddNymHandle *add_nym = cls; + if (NULL != add_nym->result_cb) + add_nym->result_cb (add_nym->result_cls, result, err_msg, err_msg_size); + + GNUNET_free (add_nym); +} + + +/** + * Add nym to the GNS zone of @e ego. + * + * @param cfg + * Configuration. + * @param ego + * Ego. + * @param name + * The name for the PKEY record to put in the zone. + * @param nym_pub_key + * Public key of nym to add. + * @param expiration_time + * Expiration time of the record, use 0 to remove the record. + * @param result_cb + * Function called with the result of the operation. + * @param result_cls + * Closure for @a result_cb + * + * @return #GNUNET_OK if the request was sent, + * #GNUNET_SYSERR on error, e.g. the name is too long. + */ +int +GNUNET_SOCIAL_zone_add_nym (const struct GNUNET_SOCIAL_App *app, + const struct GNUNET_SOCIAL_Ego *ego, + const char *name, + const struct GNUNET_CRYPTO_EcdsaPublicKey *nym_pub_key, + struct GNUNET_TIME_Absolute expiration_time, + GNUNET_ResultCallback result_cb, + void *result_cls) +{ + struct ZoneAddNymRequest *nreq; + + size_t name_size = strlen (name) + 1; + if (GNUNET_MAX_MESSAGE_SIZE < sizeof (*nreq) + name_size) + return GNUNET_SYSERR; + + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg_extra (nreq, name_size, + GNUNET_MESSAGE_TYPE_SOCIAL_ZONE_ADD_NYM); + nreq->expiration_time = GNUNET_htonll (expiration_time.abs_value_us); + nreq->ego_pub_key = ego->pub_key; + nreq->nym_pub_key = *nym_pub_key; + GNUNET_memcpy (&nreq[1], name, name_size); + + struct ZoneAddNymHandle *add_nym = GNUNET_malloc (sizeof (*add_nym)); + add_nym->result_cb = result_cb; + add_nym->result_cls = result_cls; + + nreq->op_id = GNUNET_htonll (GNUNET_OP_add (app->op, + op_recv_zone_add_nym_result, + add_nym, NULL)); + + GNUNET_MQ_send (app->mq, env); + return GNUNET_OK; +} + + +/*** APP ***/ + + +static void +app_connect (struct GNUNET_SOCIAL_App *app); + + +static void +app_reconnect (void *cls) +{ + app_connect (cls); +} + + +/** + * App client disconnected from service. + * + * Reconnect after backoff period. + */ +static void +app_disconnected (void *cls, enum GNUNET_MQ_Error error) +{ + struct GNUNET_SOCIAL_App *app = cls; + + LOG (GNUNET_ERROR_TYPE_DEBUG, + "App client disconnected (%d), re-connecting\n", + (int) error); + if (NULL != app->mq) + { + GNUNET_MQ_destroy (app->mq); + app->mq = NULL; + } + + app->reconnect_task = GNUNET_SCHEDULER_add_delayed (app->reconnect_delay, + app_reconnect, + app); + app->reconnect_delay = GNUNET_TIME_STD_BACKOFF (app->reconnect_delay); +} + + +static void +app_connect (struct GNUNET_SOCIAL_App *app) +{ + struct GNUNET_MQ_MessageHandler handlers[] = { + GNUNET_MQ_hd_var_size (app_ego, + GNUNET_MESSAGE_TYPE_SOCIAL_APP_EGO, + struct AppEgoMessage, + app), + GNUNET_MQ_hd_fixed_size (app_ego_end, + GNUNET_MESSAGE_TYPE_SOCIAL_APP_EGO_END, + struct GNUNET_MessageHeader, + app), + GNUNET_MQ_hd_var_size (app_place, + GNUNET_MESSAGE_TYPE_SOCIAL_APP_PLACE, + struct AppPlaceMessage, + app), + GNUNET_MQ_hd_fixed_size (app_place_end, + GNUNET_MESSAGE_TYPE_SOCIAL_APP_PLACE_END, + struct GNUNET_MessageHeader, + app), + GNUNET_MQ_hd_var_size (app_result, + GNUNET_MESSAGE_TYPE_PSYC_RESULT_CODE, + struct GNUNET_OperationResultMessage, + app), + GNUNET_MQ_handler_end () + }; + + app->mq = GNUNET_CLIENT_connect (app->cfg, "social", + handlers, app_disconnected, app); + GNUNET_assert (NULL != app->mq); + GNUNET_MQ_send_copy (app->mq, app->connect_env); +} + + +/** + * Connect application to the social service. + * + * The @host_place_cb and @guest_place_cb functions are + * initially called for each entered places, + * then later each time a new place is entered with the current application ID. + * + * @param cfg + * Configuration. + * @param id + * Application ID. + * @param ego_cb + * Function to notify about an available ego. + * @param host_cb + * Function to notify about a place entered as host. + * @param guest_cb + * Function to notify about a place entered as guest. + * @param cls + * Closure for the callbacks. + * + * @return Handle that can be used to stop listening. + */ +struct GNUNET_SOCIAL_App * +GNUNET_SOCIAL_app_connect (const struct GNUNET_CONFIGURATION_Handle *cfg, + const char *id, + GNUNET_SOCIAL_AppEgoCallback ego_cb, + GNUNET_SOCIAL_AppHostPlaceCallback host_cb, + GNUNET_SOCIAL_AppGuestPlaceCallback guest_cb, + GNUNET_SOCIAL_AppConnectedCallback connected_cb, + void *cls) +{ + uint16_t app_id_size = strnlen (id, GNUNET_SOCIAL_APP_MAX_ID_SIZE); + if (GNUNET_SOCIAL_APP_MAX_ID_SIZE == app_id_size) + return NULL; + app_id_size++; + + struct GNUNET_SOCIAL_App *app = GNUNET_malloc (sizeof *app); + app->cfg = cfg; + app->ego_cb = ego_cb; + app->host_cb = host_cb; + app->guest_cb = guest_cb; + app->connected_cb = connected_cb; + app->cb_cls = cls; + app->egos = GNUNET_CONTAINER_multihashmap_create (1, GNUNET_NO); + app->op = GNUNET_OP_create (); + app->id = GNUNET_malloc (app_id_size); + GNUNET_memcpy (app->id, id, app_id_size); + + struct AppConnectRequest *creq; + app->connect_env = GNUNET_MQ_msg_extra (creq, app_id_size, + GNUNET_MESSAGE_TYPE_SOCIAL_APP_CONNECT); + GNUNET_memcpy (&creq[1], app->id, app_id_size); + + app_connect (app); + return app; +} + + +static void +app_cleanup (struct GNUNET_SOCIAL_App *app) +{ + if (NULL != app->mq) + { + GNUNET_MQ_destroy (app->mq); + app->mq = NULL; + } + if (NULL != app->disconnect_cb) + { + app->disconnect_cb (app->disconnect_cls); + app->disconnect_cb = NULL; + } + GNUNET_free (app); +} + +/** + * Disconnect application. + * + * @param app + * Application handle. + * @param disconnect_cb + * Disconnect callback. + * @param disconnect_cls + * Disconnect closure. + */ +void +GNUNET_SOCIAL_app_disconnect (struct GNUNET_SOCIAL_App *app, + GNUNET_ContinuationCallback disconnect_cb, + void *disconnect_cls) +{ + if (NULL == app) return; + + app->disconnect_cb = disconnect_cb; + app->disconnect_cls = disconnect_cls; + + if (NULL != app->mq) + { + struct GNUNET_MQ_Envelope *env = GNUNET_MQ_get_last_envelope (app->mq); + if (NULL != env) + { + GNUNET_MQ_notify_sent (env, (GNUNET_SCHEDULER_TaskCallback) app_cleanup, app); + } + else + { + app_cleanup (app); + } + } + else + { + app_cleanup (app); + } +} + + +/** + * Detach application from a place. + * + * Removes the place from the entered places list for this application. + * Note: this does not disconnect from the place. + * + * @see GNUNET_SOCIAL_host_disconnect() and GNUNET_SOCIAL_guest_disconnect() + * + * @param app + * Application. + * @param plc + * Place. + */ +void +GNUNET_SOCIAL_app_detach (struct GNUNET_SOCIAL_App *app, + struct GNUNET_SOCIAL_Place *plc) +{ + struct AppDetachRequest *dreq; + struct GNUNET_MQ_Envelope * + env = GNUNET_MQ_msg (dreq, GNUNET_MESSAGE_TYPE_SOCIAL_APP_DETACH); + dreq->place_pub_key = plc->pub_key; + dreq->ego_pub_key = plc->ego_pub_key; + + GNUNET_MQ_send (app->mq, env); +} + + +/* end of social_api.c */ diff --git a/src/social/test_social.c b/src/social/test_social.c new file mode 100644 index 0000000..feac3c5 --- /dev/null +++ b/src/social/test_social.c @@ -0,0 +1,1449 @@ +/* + * This file is part of GNUnet + * Copyright (C) 2013 GNUnet e.V. + * + * GNUnet is free software: you can redistribute it and/or modify it + * under the terms of the GNU Affero General Public License as published + * by the Free Software Foundation, either version 3 of the License, + * or (at your option) any later version. + * + * GNUnet is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + + SPDX-License-Identifier: AGPL3.0-or-later + */ +/** + * @file social/test_social.c + * @brief Tests for the Social API. + * @author Gabor X Toth + */ + +#include + +#include "platform.h" +#include "gnunet_crypto_lib.h" +#include "gnunet_common.h" +#include "gnunet_util_lib.h" +#include "gnunet_testing_lib.h" +#include "gnunet_psyc_util_lib.h" +#include "gnunet_social_service.h" +#include "gnunet_identity_service.h" + +#define TIMEOUT GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, 30) + +#define DATA2ARG(data) data, sizeof (data) + +/** + * Return value from 'main'. + */ +int res; + +struct GNUNET_SOCIAL_App *app; +const char *app_id = "test"; + +/** + * Handle for task for timeout termination. + */ +struct GNUNET_SCHEDULER_Task *end_badly_task; + +const struct GNUNET_CONFIGURATION_Handle *cfg; + +struct GNUNET_PeerIdentity this_peer; + +struct GNUNET_IDENTITY_Handle *id; + +const struct GNUNET_IDENTITY_Ego *identity_host_ego; +const struct GNUNET_IDENTITY_Ego *identity_guest_ego; + +const struct GNUNET_SOCIAL_Ego *host_ego; +const struct GNUNET_SOCIAL_Ego *guest_ego; + +const char *host_name = "Host One"; +const char *guest_name = "Guest One"; + +struct GNUNET_CRYPTO_EddsaPrivateKey *place_key; +struct GNUNET_CRYPTO_EcdsaPrivateKey *guest_key; + +struct GNUNET_CRYPTO_EddsaPublicKey place_pub_key; +struct GNUNET_HashCode place_pub_hash; + +const struct GNUNET_CRYPTO_EcdsaPublicKey *guest_pub_key; +const struct GNUNET_CRYPTO_EcdsaPublicKey *host_pub_key; + +struct GNUNET_PSYC_Slicer *host_slicer; +struct GNUNET_PSYC_Slicer *guest_slicer; + +struct GNUNET_SOCIAL_Host *hst; +struct GNUNET_SOCIAL_Guest *gst; + +struct GNUNET_SOCIAL_Place *hst_plc; +struct GNUNET_SOCIAL_Place *gst_plc; + +struct GNUNET_SOCIAL_Nym *nym_eject; + +struct GuestEnterMessage +{ + struct GNUNET_PSYC_Message *msg; + const char *method_name; + struct GNUNET_PSYC_Environment *env; + void *data; + uint16_t data_size; +} guest_enter_msg; + +struct TransmitClosure +{ + struct GNUNET_SOCIAL_Announcement *host_ann; + struct GNUNET_SOCIAL_TalkRequest *guest_talk; + struct GNUNET_PSYC_Environment *env; + char *data[16]; + uint8_t data_delay[16]; + uint8_t data_count; + uint8_t paused; + uint8_t n; +} tmit; + +struct ResultClosure { + uint32_t n; +} mod_foo_bar_rcls; + +uint8_t join_req_count; +struct GNUNET_PSYC_Message *join_resp; + +uint32_t counter; + +uint8_t is_guest_nym_added = GNUNET_NO; +uint8_t is_host_reconnected = GNUNET_NO; +uint8_t is_guest_reconnected = GNUNET_NO; + +enum +{ + TEST_NONE = 0, + TEST_IDENTITIES_CREATE = 1, + TEST_HOST_ENTER = 2, + TEST_GUEST_ENTER = 3, + TEST_HOST_ANSWER_DOOR_REFUSE = 4, + TEST_GUEST_RECV_ENTRY_DCSN_REFUSE = 5, + TEST_HOST_ANSWER_DOOR_ADMIT = 6, + TEST_GUEST_RECV_ENTRY_DCSN_ADMIT = 7, + TEST_HOST_ANNOUNCE = 8, + TEST_HOST_ANNOUNCE_END = 9, + TEST_GUEST_TALK = 10, + TEST_HOST_ANNOUNCE2 = 11, + TEST_HOST_ANNOUNCE2_END = 12, + TEST_GUEST_HISTORY_REPLAY = 13, + TEST_GUEST_HISTORY_REPLAY_LATEST = 14, + TEST_GUEST_LOOK_AT = 15, + TEST_GUEST_LOOK_FOR = 16, + TEST_GUEST_LEAVE = 17, + TEST_ZONE_ADD_PLACE = 18, + TEST_GUEST_ENTER_BY_NAME = 19, + TEST_RECONNECT = 20, + TEST_GUEST_LEAVE2 = 21, + TEST_HOST_LEAVE = 22, +} test; + + +static void +schedule_guest_leave (void *cls); + + +static void +host_answer_door (void *cls, + struct GNUNET_SOCIAL_Nym *nym, + const char *method_name, + struct GNUNET_PSYC_Environment *env, + const void *data, + size_t data_size); + +static void +host_enter (); + +static void +guest_init (); + +static void +guest_enter (); + +static void +guest_enter_by_name (); + +static void +guest_talk (); + +static void +host_announce2 (); + + +/** + * Terminate the test case (failure). + * + * @param cls NULL + */ +static void +end_badly (void *cls) +{ + end_badly_task = NULL; + GNUNET_SCHEDULER_shutdown (); + res = 2; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Test FAILED.\n"); +} + + +/** + * Terminate the test case (failure). + * + * @param cls NULL + */ +static void +end_shutdown (void *cls) +{ + if (NULL != id) + { + GNUNET_IDENTITY_disconnect (id); + id = NULL; + } + + if (NULL != guest_slicer) + { + GNUNET_PSYC_slicer_destroy (guest_slicer); + guest_slicer = NULL; + } + + if (NULL != host_slicer) + { + GNUNET_PSYC_slicer_destroy (host_slicer); + host_slicer = NULL; + } + if (NULL != end_badly_task) + { + GNUNET_SCHEDULER_cancel (end_badly_task); + end_badly_task = NULL; + } + if (NULL != gst) + { + GNUNET_SOCIAL_guest_leave (gst, NULL, NULL, NULL); + gst = NULL; + gst_plc = NULL; + } + if (NULL != hst) + { + GNUNET_SOCIAL_host_leave (hst, NULL, NULL, NULL); + hst = NULL; + hst_plc = NULL; + } + GNUNET_SOCIAL_app_disconnect (app, NULL, NULL); +} + + +/** + * Terminate the test case (success). + * + * @param cls NULL + */ +static void +end_normally (void *cls) +{ + GNUNET_SCHEDULER_shutdown (); + res = 0; + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, "Test PASSED.\n"); +} + + +/** + * Finish the test case (successfully). + */ +static void +end () +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Ending tests.\n", test); + + if (end_badly_task != NULL) + { + GNUNET_SCHEDULER_cancel (end_badly_task); + end_badly_task = NULL; + } + GNUNET_SCHEDULER_add_now (&end_normally, NULL); +} + + +static void +transmit_resume (void *cls) +{ + struct TransmitClosure *tmit = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Transmission resumed.\n", test); + if (NULL != tmit->host_ann) + GNUNET_SOCIAL_host_announce_resume (tmit->host_ann); + else + GNUNET_SOCIAL_guest_talk_resume (tmit->guest_talk); +} + + +static int +notify_data (void *cls, uint16_t *data_size, void *data) +{ + struct TransmitClosure *tmit = cls; + if (NULL != tmit->env) + { + GNUNET_PSYC_env_destroy (tmit->env); + tmit->env = NULL; + } + if (0 == tmit->data_count) + { + *data_size = 0; + return GNUNET_YES; + } + + uint16_t size = strlen (tmit->data[tmit->n]); + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Transmit notify data: %u bytes available, " + "processing fragment %u/%u (size %u).\n", + test, *data_size, tmit->n + 1, tmit->data_count, size); + if (*data_size < size) + { + *data_size = 0; + GNUNET_assert (0); + return GNUNET_SYSERR; + } + + if (GNUNET_YES != tmit->paused && 0 < tmit->data_delay[tmit->n]) + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Transmission paused.\n", test); + tmit->paused = GNUNET_YES; + GNUNET_SCHEDULER_add_delayed ( + GNUNET_TIME_relative_multiply (GNUNET_TIME_UNIT_SECONDS, + tmit->data_delay[tmit->n]), + &transmit_resume, tmit); + *data_size = 0; + return GNUNET_NO; + } + tmit->paused = GNUNET_NO; + + *data_size = size; + GNUNET_memcpy (data, tmit->data[tmit->n], size); + + return ++tmit->n < tmit->data_count ? GNUNET_NO : GNUNET_YES; +} + + +static void +host_left () +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: The host has left the place.\n", test); + end (); +} + + +static void +schedule_host_leave (void *cls) +{ + test = TEST_HOST_LEAVE; + GNUNET_SOCIAL_host_leave (hst, NULL, &host_left, NULL); + hst = NULL; + hst_plc = NULL; +} + + +static void +host_farewell2 (void *cls, + const struct GNUNET_SOCIAL_Nym *nym, + struct GNUNET_PSYC_Environment *env) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Nym left the place again.\n"); + GNUNET_SCHEDULER_add_now (&schedule_host_leave, NULL); +} + + +static void +host_reconnected (void *cls, int result, + const struct GNUNET_CRYPTO_EddsaPublicKey *home_pub_key, + uint64_t max_message_id) +{ + place_pub_key = *home_pub_key; + GNUNET_CRYPTO_hash (&place_pub_key, sizeof (place_pub_key), &place_pub_hash); + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Host reconnected to place %s\n", + test, GNUNET_h2s (&place_pub_hash)); + + is_host_reconnected = GNUNET_YES; + if (GNUNET_YES == is_guest_reconnected) + { + GNUNET_assert (NULL != gst); + GNUNET_SCHEDULER_add_now (&schedule_guest_leave, NULL); + } +} + + +static void +guest_reconnected (void *cls, int result, + const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key, + uint64_t max_message_id) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Guest reconnected to place: %d\n", + test, result); + GNUNET_assert (0 <= result); + + is_guest_reconnected = GNUNET_YES; + if (GNUNET_YES == is_host_reconnected) + { + GNUNET_assert (NULL != gst); + GNUNET_SCHEDULER_add_now (&schedule_guest_leave, NULL); + } +} + + +static void +app_connected (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: App connected: %p\n", test, cls); +} + + +static void +app_recv_host (void *cls, + struct GNUNET_SOCIAL_HostConnection *hconn, + struct GNUNET_SOCIAL_Ego *ego, + const struct GNUNET_CRYPTO_EddsaPublicKey *host_pub_key, + enum GNUNET_SOCIAL_AppPlaceState place_state) +{ + struct GNUNET_HashCode host_pub_hash; + + GNUNET_CRYPTO_hash (host_pub_key, + sizeof (*host_pub_key), + &host_pub_hash); + + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Got app host place notification: %s\n", + test, + GNUNET_h2s (&host_pub_hash)); + + if (test == TEST_RECONNECT) + { + if (0 == memcmp (&place_pub_key, host_pub_key, sizeof (*host_pub_key))) + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Reconnecting to host place: %s\n", + test, GNUNET_h2s (&host_pub_hash)); + hst = GNUNET_SOCIAL_host_enter_reconnect (hconn, host_slicer, + &host_reconnected, + &host_answer_door, + &host_farewell2, + NULL); + } + } +} + + +static void +app_recv_guest (void *cls, + struct GNUNET_SOCIAL_GuestConnection *gconn, + struct GNUNET_SOCIAL_Ego *ego, + const struct GNUNET_CRYPTO_EddsaPublicKey *guest_pub_key, + enum GNUNET_SOCIAL_AppPlaceState place_state) +{ + struct GNUNET_HashCode guest_pub_hash; + + GNUNET_CRYPTO_hash (guest_pub_key, + sizeof (*guest_pub_key), + &guest_pub_hash); + + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Got app guest place notification: %s\n", + test, GNUNET_h2s (&guest_pub_hash)); + + if (test == TEST_RECONNECT) + { + if (0 == memcmp (&place_pub_key, + guest_pub_key, + sizeof (*guest_pub_key))) + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Reconnecting to guest place: %s\n", + test, GNUNET_h2s (&guest_pub_hash)); + gst = GNUNET_SOCIAL_guest_enter_reconnect (gconn, + GNUNET_PSYC_SLAVE_JOIN_NONE, + guest_slicer, + &guest_reconnected, + NULL); + GNUNET_assert (NULL != gst); + } + } +} + + +static void +enter_if_ready () +{ + if (NULL == host_ego || NULL == guest_ego) + { + return; + } + host_enter (); + guest_init (); +} + + +static void +app_recv_ego (void *cls, + struct GNUNET_SOCIAL_Ego *ego, + const struct GNUNET_CRYPTO_EcdsaPublicKey *ego_pub_key, + const char *name) +{ + char *ego_pub_str = GNUNET_CRYPTO_ecdsa_public_key_to_string (ego_pub_key); + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Got app ego notification: %p %s %s\n", + test, ego, name, ego_pub_str); + GNUNET_free (ego_pub_str); + + if (NULL != strstr (name, host_name)) + { + host_ego = ego; + host_pub_key = ego_pub_key; + if (TEST_IDENTITIES_CREATE == test) + { + enter_if_ready (); + } + else + { + GNUNET_assert (TEST_RECONNECT == test); + } + } + else if (NULL != strstr (name, guest_name)) + { + guest_ego = ego; + guest_pub_key = ego_pub_key; + if (TEST_IDENTITIES_CREATE == test) + { + enter_if_ready (); + } + else + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "test = %d\n", + test); + GNUNET_assert (TEST_RECONNECT == test); + } + } +} + + +static void +schedule_reconnect (void *cls) +{ + test = TEST_RECONNECT; + GNUNET_SOCIAL_host_disconnect (hst, NULL, NULL); + GNUNET_SOCIAL_guest_disconnect (gst, NULL, NULL); + hst = NULL; + gst = NULL; + + GNUNET_SOCIAL_app_disconnect (app, NULL, NULL); + app = GNUNET_SOCIAL_app_connect (cfg, app_id, + &app_recv_ego, + &app_recv_host, + &app_recv_guest, + &app_connected, + NULL); +} + + +static void +host_recv_zone_add_place_result (void *cls, int64_t result, + const void *data, uint16_t data_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Zone add place result: %" PRId64 " (%.*s).\n", + test, result, data_size, (const char *) data); + GNUNET_assert (GNUNET_YES == result); + + GNUNET_assert (GNUNET_YES == is_guest_nym_added); + guest_enter_by_name (); +} + + +static void +zone_add_place () +{ + test = TEST_ZONE_ADD_PLACE; + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Adding place to zone.\n", test); + + GNUNET_SOCIAL_zone_add_place (app, host_ego, "home", "let.me*in!", + &place_pub_key, &this_peer, 1, &this_peer, + GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES), + host_recv_zone_add_place_result, app); +} + + +static void +host_farewell (void *cls, + const struct GNUNET_SOCIAL_Nym *nym, + struct GNUNET_PSYC_Environment *env) +{ + const struct GNUNET_CRYPTO_EcdsaPublicKey * + nym_key = GNUNET_SOCIAL_nym_get_pub_key (nym); + + char *str = GNUNET_CRYPTO_ecdsa_public_key_to_string (nym_key); + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Farewell: nym %s (%s) has left the place.\n", + test, GNUNET_h2s (GNUNET_SOCIAL_nym_get_pub_key_hash (nym)), str); + GNUNET_free (str); + GNUNET_assert (1 == GNUNET_PSYC_env_get_count (env)); + if (0 != memcmp (guest_pub_key, nym_key, sizeof (*nym_key))) + { + str = GNUNET_CRYPTO_ecdsa_public_key_to_string (guest_pub_key); + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Test #%u: Farewell: nym does not match guest: %s\n", + test, str); + GNUNET_free (str); + GNUNET_assert (0); + } + zone_add_place (); +} + + +static void +guest_left (void *cls) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: The guest has left the place.\n", test); +} + + +static void +guest_leave () +{ + if (test < TEST_RECONNECT) + test = TEST_GUEST_LEAVE; + else + test = TEST_GUEST_LEAVE2; + + struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create (); + GNUNET_PSYC_env_add (env, GNUNET_PSYC_OP_SET, + "_notice_place_leave", DATA2ARG ("Leaving.")); + GNUNET_SOCIAL_guest_leave (gst, env, &guest_left, NULL); + GNUNET_PSYC_env_destroy (env); + gst = NULL; + gst_plc = NULL; +} + + +static void +schedule_guest_leave (void *cls) +{ + guest_leave (); +} + + +static void +guest_look_for_result (void *cls, + int64_t result_code, + const void *data, + uint16_t data_size) +{ + struct ResultClosure *rcls = cls; + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: guest_look_for_result: %" PRId64 "\n", + test, result_code); + GNUNET_assert (GNUNET_OK == result_code); + GNUNET_assert (6 == rcls->n); + GNUNET_free (rcls); + GNUNET_SCHEDULER_add_now (&schedule_guest_leave, NULL); +} + + +static void +guest_look_for_var (void *cls, + const struct GNUNET_MessageHeader *mod, + const char *name, + const void *value, + uint32_t value_size, + uint32_t full_value_size) +{ + struct ResultClosure *rcls = cls; + rcls->n++; + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: guest_look_for_var: %s\n%.*s\n", + test, name, value_size, (const char *) value); +} + + +static void +guest_look_for () +{ + test = TEST_GUEST_LOOK_FOR; + struct ResultClosure *rcls = GNUNET_malloc (sizeof (*rcls)); + GNUNET_SOCIAL_place_look_for (gst_plc, "_foo", guest_look_for_var, guest_look_for_result, rcls); +} + + +static void +guest_look_at_result (void *cls, int64_t result_code, + const void *data, uint16_t data_size) +{ + struct ResultClosure *rcls = cls; + + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: guest_look_at_result: %" PRId64 "\n", + test, result_code); + GNUNET_assert (GNUNET_OK == result_code); + GNUNET_assert (1 == rcls->n); + GNUNET_free (rcls); + guest_look_for (); +} + + +static void +guest_look_at_var (void *cls, + const struct GNUNET_MessageHeader *mod, + const char *name, + const void *value, + uint32_t value_size, + uint32_t full_value_size) +{ + struct ResultClosure *rcls = cls; + rcls->n++; + + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: guest_look_at_var: %s\n%.*s\n", + test ,name, value_size, (const char *) value); +} + + +static void +guest_look_at () +{ + test = TEST_GUEST_LOOK_AT; + struct ResultClosure *rcls = GNUNET_malloc (sizeof (*rcls)); + GNUNET_SOCIAL_place_look_at (gst_plc, "_foo_bar", guest_look_at_var, guest_look_at_result, rcls); +} + + +static void +guest_recv_history_replay_latest_result (void *cls, int64_t result, + const void *data, uint16_t data_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Guest received latest history replay result " + "(%" PRIu32 " messages, %" PRId64 " fragments):\n" + "%.*s\n", + test, counter, result, data_size, (const char *) data); + //GNUNET_assert (2 == counter); /* message count */ + //GNUNET_assert (7 == result); /* fragment count */ + + guest_look_at (); +} + + +static void +guest_history_replay_latest () +{ + test = TEST_GUEST_HISTORY_REPLAY_LATEST; + counter = 0; + GNUNET_SOCIAL_place_history_replay_latest (gst_plc, 3, "", + GNUNET_PSYC_HISTORY_REPLAY_LOCAL, + guest_slicer, + &guest_recv_history_replay_latest_result, + NULL); +} + + +static void +guest_recv_history_replay_result (void *cls, int64_t result, + const void *data, uint16_t data_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Guest received history replay result: %" PRId64 "\n" + "%.*s\n", + test, result, data_size, (const char *) data); +// GNUNET_assert (2 == counter); /* message count */ +// GNUNET_assert (7 == result); /* fragment count */ + + guest_history_replay_latest (); +} + + +static void +guest_history_replay () +{ + test = TEST_GUEST_HISTORY_REPLAY; + counter = 0; + GNUNET_SOCIAL_place_history_replay (gst_plc, 1, 3, "", + GNUNET_PSYC_HISTORY_REPLAY_LOCAL, + guest_slicer, + &guest_recv_history_replay_result, + NULL); +} + + +static void +guest_recv_method (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_PSYC_MessageMethod *meth, + uint64_t message_id, + const char *method_name) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Guest received method for message ID %" PRIu64 ":\n" + "%s (flags: %x)\n", + test, message_id, method_name, ntohl (meth->flags)); + /** @todo FIXME: check message */ +} + + +static void +guest_recv_modifier (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + enum GNUNET_PSYC_Operator oper, + const char *name, + const void *value, + uint16_t value_size, + uint16_t full_value_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Guest received modifier for message ID %" PRIu64 ":\n" + "%c%s: %.*s (size: %u)\n", + test, message_id, oper, name, value_size, (const char *) value, value_size); + /** @todo FIXME: check modifier */ +} + +static void +guest_recv_mod_foo_bar (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + enum GNUNET_PSYC_Operator oper, + const char *name, + const void *value, + uint16_t value_size, + uint16_t full_value_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Guest received modifier matching _foo_bar for message ID %" PRIu64 ":\n" + "%c%s: %.*s (size: %u)\n", + test, message_id, oper, name, value_size, (const char *) value, value_size); + struct ResultClosure *rc = cls; + rc->n++; + /** @todo FIXME: check modifier */ +} + + +static void +guest_recv_data (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + const void *data, + uint16_t data_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Guest received data for message ID %" PRIu64 ":\n" + "%.*s\n", + test, message_id, data_size, (const char *) data); + /** @todo FIXME: check data */ +} + + +static void +guest_recv_eom (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + uint8_t is_cancelled) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Guest received end of message ID %" PRIu64 + ", cancelled: %u\n", + test, message_id, is_cancelled); + + switch (test) + { + case TEST_HOST_ANNOUNCE: + test = TEST_HOST_ANNOUNCE_END; + break; + + case TEST_HOST_ANNOUNCE_END: + guest_talk (); + break; + + case TEST_HOST_ANNOUNCE2: + test = TEST_HOST_ANNOUNCE2_END; + break; + + case TEST_HOST_ANNOUNCE2_END: + guest_history_replay (); + break; + + case TEST_GUEST_HISTORY_REPLAY: + case TEST_GUEST_HISTORY_REPLAY_LATEST: + counter++; + break; + + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "invalid test: %d\n", test); + GNUNET_assert (0); + } +} + + +static void +host_recv_method (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_PSYC_MessageMethod *meth, + uint64_t message_id, + const char *method_name) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Host received method for message ID %" PRIu64 ":\n" + "%s\n", + test, message_id, method_name); + /** @todo FIXME: check message */ +} + + +static void +host_recv_modifier (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + enum GNUNET_PSYC_Operator oper, + const char *name, + const void *value, + uint16_t value_size, + uint16_t full_value_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Host received modifier for message ID %" PRIu64 ":\n" + "%c%s: %.*s\n", + test, message_id, oper, name, value_size, (const char *) value); +} + + +static void +host_recv_data (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + const void *data, + uint16_t data_size) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Host received data for message ID %" PRIu64 ":\n" + "%.*s\n", + test, message_id, data_size, (const char *) data); +} + + +static void +host_recv_eom (void *cls, + const struct GNUNET_PSYC_MessageHeader *msg, + const struct GNUNET_MessageHeader *pmsg, + uint64_t message_id, + uint8_t is_cancelled) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Host received end of message ID %" PRIu64 + ", cancelled: %u\n", + test, message_id, is_cancelled); + + switch (test) + { + case TEST_HOST_ANNOUNCE: + test = TEST_HOST_ANNOUNCE_END; + break; + + case TEST_HOST_ANNOUNCE_END: + guest_talk (); + break; + + case TEST_HOST_ANNOUNCE2: + test = TEST_HOST_ANNOUNCE2_END; + break; + + case TEST_HOST_ANNOUNCE2_END: + guest_history_replay (); + break; + + case TEST_GUEST_TALK: + host_announce2 (); + break; + + default: + if (TEST_GUEST_LEAVE <= test) + break; + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Invalid test: #%u\n", test); + GNUNET_assert (0); + } +} + + +static void +guest_talk () +{ + test = TEST_GUEST_TALK; + + tmit = (struct TransmitClosure) {}; + tmit.env = GNUNET_PSYC_env_create (); + GNUNET_PSYC_env_add (tmit.env, GNUNET_PSYC_OP_ASSIGN, + "_bar_foo", DATA2ARG ("one two three")); + GNUNET_PSYC_env_add (tmit.env, GNUNET_PSYC_OP_ASSIGN, + "_bar_baz", DATA2ARG ("four five")); + tmit.data[0] = "zzz xxx yyy "; + tmit.data[1] = "zyx wvu tsr qpo.\n"; + tmit.data_delay[1] = 1; + tmit.data[2] = "testing ten nine eight.\n"; + tmit.data_count = 3; + + tmit.guest_talk + = GNUNET_SOCIAL_guest_talk (gst, "_converse_guest", tmit.env, + ¬ify_data, &tmit, + GNUNET_SOCIAL_TALK_NONE); +} + + +static void +host_announce () +{ + test = TEST_HOST_ANNOUNCE; + + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Host announcement.\n", test); + + tmit = (struct TransmitClosure) {}; + tmit.env = GNUNET_PSYC_env_create (); + GNUNET_PSYC_env_add (tmit.env, GNUNET_PSYC_OP_ASSIGN, + "_foo", DATA2ARG ("bar baz")); + GNUNET_PSYC_env_add (tmit.env, GNUNET_PSYC_OP_ASSIGN, + "_foo_bar", DATA2ARG ("foo bar")); + GNUNET_PSYC_env_add (tmit.env, GNUNET_PSYC_OP_ASSIGN, + "_foo_bar_baz", DATA2ARG ("foo bar baz")); + tmit.data[0] = "aaa bbb ccc "; + tmit.data[1] = "abc def ghi jkl.\n"; + tmit.data_delay[1] = 1; + tmit.data[2] = "testing one two three "; + tmit.data[3] = "four five.\n"; + tmit.data_count = 4; + + tmit.host_ann + = GNUNET_SOCIAL_host_announce (hst, "_converse_host", tmit.env, + ¬ify_data, &tmit, + GNUNET_SOCIAL_ANNOUNCE_NONE); +} + + +static void +host_announce2 () +{ + GNUNET_assert (2 == mod_foo_bar_rcls.n); + GNUNET_PSYC_slicer_modifier_remove (guest_slicer, "_foo_bar", + guest_recv_mod_foo_bar); + + test = TEST_HOST_ANNOUNCE2; + + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Host announcement 2.\n", test); + + tmit = (struct TransmitClosure) {}; + tmit.env = GNUNET_PSYC_env_create (); + GNUNET_PSYC_env_add (tmit.env, GNUNET_PSYC_OP_ASSIGN, + "_foo2", DATA2ARG ("BAR BAZ")); + GNUNET_PSYC_env_add (tmit.env, GNUNET_PSYC_OP_ASSIGN, + "_foo2_bar", DATA2ARG ("FOO BAR")); + GNUNET_PSYC_env_add (tmit.env, GNUNET_PSYC_OP_ASSIGN, + "_foo2_bar_baz", DATA2ARG ("FOO BAR BAZ")); + tmit.data[0] = "AAA BBB CCC "; + tmit.data[1] = "ABC DEF GHI JKL.\n"; + tmit.data[2] = "TESTING ONE TWO THREE.\n"; + tmit.data_count = 3; + + tmit.host_ann + = GNUNET_SOCIAL_host_announce (hst, "_converse_host_two", tmit.env, + ¬ify_data, &tmit, + GNUNET_SOCIAL_ANNOUNCE_NONE); +} + + +static void +guest_recv_entry_decision (void *cls, + int is_admitted, + const struct GNUNET_PSYC_Message *entry_msg) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Guest received entry decision (try %u): %d.\n", + test, join_req_count, is_admitted); + + if (NULL != entry_msg) + { + struct GNUNET_PSYC_Environment *env = GNUNET_PSYC_env_create (); + const char *method_name = NULL; + const void *data = NULL; + uint16_t data_size = 0; + struct GNUNET_PSYC_MessageHeader * + pmsg = GNUNET_PSYC_message_header_create_from_psyc (entry_msg); + GNUNET_PSYC_message_parse (pmsg, &method_name, env, &data, &data_size); + GNUNET_free (pmsg); + + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%s\n%.*s\n", + method_name, data_size, (const char *) data); + /** @todo FIXME: check response message */ + } + + switch (test) + { + case TEST_GUEST_RECV_ENTRY_DCSN_REFUSE: + GNUNET_assert (GNUNET_NO == is_admitted); + test = TEST_HOST_ANSWER_DOOR_ADMIT; + GNUNET_SOCIAL_guest_disconnect (gst, &guest_enter, NULL); + break; + + case TEST_GUEST_RECV_ENTRY_DCSN_ADMIT: + GNUNET_assert (GNUNET_YES == is_admitted); + host_announce (); + break; + + case TEST_GUEST_ENTER_BY_NAME: + GNUNET_SCHEDULER_add_now (&schedule_reconnect, NULL); + break; + + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "invalid test: %d\n", test); + GNUNET_assert (0); + } +} + + +static void +host_answer_door (void *cls, + struct GNUNET_SOCIAL_Nym *nym, + const char *method_name, + struct GNUNET_PSYC_Environment *env, + const void *data, + size_t data_size) +{ + join_req_count++; + + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Host received entry request from guest (try %u).\n", + (uint8_t) test, join_req_count); + GNUNET_log (GNUNET_ERROR_TYPE_DEBUG, + "%s\n%.*s\n", + method_name, (int) data_size, (const char *) data); + + switch (test) + { + case TEST_HOST_ANSWER_DOOR_REFUSE: + test = TEST_GUEST_RECV_ENTRY_DCSN_REFUSE; + join_resp = GNUNET_PSYC_message_create ("_notice_place_refuse", env, + DATA2ARG ("Go away!")); + GNUNET_SOCIAL_host_entry_decision (hst, nym, GNUNET_NO, join_resp); + break; + + case TEST_HOST_ANSWER_DOOR_ADMIT: + test = TEST_GUEST_RECV_ENTRY_DCSN_ADMIT; + // fall through + + case TEST_GUEST_ENTER_BY_NAME: + join_resp = GNUNET_PSYC_message_create ("_notice_place_admit", env, + DATA2ARG ("Welcome, nym!")); + GNUNET_SOCIAL_host_entry_decision (hst, nym, GNUNET_YES, join_resp); + break; + + default: + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, "Invalid test: #%u\n", test); + GNUNET_assert (0); + } +} + + +static void +guest_recv_local_enter (void *cls, int result, + const struct GNUNET_CRYPTO_EddsaPublicKey *place_pub_key, + uint64_t max_message_id) +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Guest entered local place: %d\n", + test, result); + GNUNET_assert (GNUNET_OK == result); +} + + +static void +guest_enter () +{ + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Entering place as guest.\n", test); + + struct GuestEnterMessage *emsg = &guest_enter_msg; + + emsg->method_name = "_request_enter"; + emsg->env = GNUNET_PSYC_env_create (); + GNUNET_PSYC_env_add (emsg->env, GNUNET_PSYC_OP_ASSIGN, + "_abc", "abc def", 7); + GNUNET_PSYC_env_add (emsg->env, GNUNET_PSYC_OP_ASSIGN, + "_abc_def", "abc def ghi", 11); + emsg->data = "let me in"; + emsg->data_size = strlen (emsg->data) + 1; + emsg->msg = GNUNET_PSYC_message_create (emsg->method_name, emsg->env, + emsg->data, emsg->data_size); + + gst = GNUNET_SOCIAL_guest_enter (app, guest_ego, &place_pub_key, + GNUNET_PSYC_SLAVE_JOIN_NONE, + &this_peer, 0, NULL, emsg->msg, guest_slicer, + guest_recv_local_enter, + guest_recv_entry_decision, NULL); + gst_plc = GNUNET_SOCIAL_guest_get_place (gst); + + GNUNET_SOCIAL_place_msg_proc_set (gst_plc, "_converse", + GNUNET_SOCIAL_MSG_PROC_SAVE); +} + + +static void +guest_enter_by_name () +{ + test = TEST_GUEST_ENTER_BY_NAME; + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Entering place by name as guest.\n", test); + + struct GuestEnterMessage *emsg = &guest_enter_msg; + + emsg->method_name = "_request_enter"; + emsg->env = GNUNET_PSYC_env_create (); + GNUNET_PSYC_env_add (emsg->env, GNUNET_PSYC_OP_ASSIGN, + "_abc", "abc def", 7); + GNUNET_PSYC_env_add (emsg->env, GNUNET_PSYC_OP_ASSIGN, + "_abc_def", "abc def ghi", 11); + emsg->data = "let me in"; + emsg->data_size = strlen (emsg->data) + 1; + emsg->msg = GNUNET_PSYC_message_create (emsg->method_name, emsg->env, + emsg->data, emsg->data_size); + + gst = GNUNET_SOCIAL_guest_enter_by_name (app, guest_ego, + "home.host.gnu", "let.me*in!", + emsg->msg, guest_slicer, + guest_recv_local_enter, + guest_recv_entry_decision, NULL); + gst_plc = GNUNET_SOCIAL_guest_get_place (gst); +} + + +static void +app_recv_zone_add_nym_result (void *cls, int64_t result, + const void *data, uint16_t data_size) +{ + GNUNET_assert (GNUNET_YES == result); + is_guest_nym_added = GNUNET_YES; +} + + +static void +guest_init () +{ + guest_pub_key = GNUNET_SOCIAL_ego_get_pub_key (guest_ego); + + guest_slicer = GNUNET_PSYC_slicer_create (); + GNUNET_PSYC_slicer_method_add (guest_slicer, "", NULL, + guest_recv_method, guest_recv_modifier, + guest_recv_data, guest_recv_eom, NULL); + GNUNET_PSYC_slicer_modifier_add (guest_slicer, "_foo_bar", + guest_recv_mod_foo_bar, &mod_foo_bar_rcls); + test = TEST_HOST_ANSWER_DOOR_REFUSE; + + GNUNET_SOCIAL_zone_add_nym (app, guest_ego, "host", host_pub_key, + GNUNET_TIME_relative_to_absolute (GNUNET_TIME_UNIT_MINUTES), + app_recv_zone_add_nym_result, NULL); +} + + +static void +id_host_created (void *cls, const char *emsg) +{ + if (NULL != emsg) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Test #%u: Could not create host identity: %s\n", + test, emsg); +#if ! DEBUG_TEST_SOCIAL + GNUNET_assert (0); +#endif + } + +} + + +static void +id_guest_created (void *cls, const char *emsg) +{ + if (NULL != emsg) + { + GNUNET_log (GNUNET_ERROR_TYPE_ERROR, + "Test #%u: Could not create guest identity: %s\n", + test, emsg); +#if ! DEBUG_TEST_SOCIAL + GNUNET_assert (0); +#endif + } + //if (NULL != guest_ego) + // guest_init (); +} + + +static void +host_entered (void *cls, int result, + const struct GNUNET_CRYPTO_EddsaPublicKey *home_pub_key, + uint64_t max_message_id) +{ + place_pub_key = *home_pub_key; + GNUNET_CRYPTO_hash (&place_pub_key, sizeof (place_pub_key), &place_pub_hash); + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Host entered place %s\n", + test, GNUNET_h2s (&place_pub_hash)); + guest_enter (); +} + + +static void +host_enter () +{ + host_slicer = GNUNET_PSYC_slicer_create (); + GNUNET_PSYC_slicer_method_add (host_slicer, "", NULL, + host_recv_method, host_recv_modifier, + host_recv_data, host_recv_eom, NULL); + + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Test #%u: Entering place as host.\n", test); + test = TEST_HOST_ENTER; + hst = GNUNET_SOCIAL_host_enter (app, host_ego, + GNUNET_PSYC_CHANNEL_PRIVATE, + host_slicer, host_entered, + host_answer_door, host_farewell, NULL); + hst_plc = GNUNET_SOCIAL_host_get_place (hst); + + GNUNET_SOCIAL_place_msg_proc_set (hst_plc, "_converse", + GNUNET_SOCIAL_MSG_PROC_RELAY); +} + + +static void +start_app_if_ready () +{ + if (NULL == identity_host_ego || NULL == identity_guest_ego) + { + return; + } + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "starting app...\n"); + app = GNUNET_SOCIAL_app_connect (cfg, + app_id, + app_recv_ego, + app_recv_host, + app_recv_guest, + app_connected, + NULL); +} + + +static void +identity_ego_cb (void *cls, struct GNUNET_IDENTITY_Ego *ego, + void **ctx, const char *name) +{ + if (NULL != ego) + { + if (ego == identity_host_ego) + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Host ego deleted\n"); + } + else if (ego == identity_guest_ego) + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Guest ego deleted\n"); + } + else if (0 == strcmp (name, host_name)) + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Created ego %s\n", + name); + identity_host_ego = ego; + start_app_if_ready (); + } + else if (0 == strcmp (name, guest_name)) + { + GNUNET_log (GNUNET_ERROR_TYPE_MESSAGE, + "Created guest ego %s\n", + name); + identity_guest_ego = ego; + start_app_if_ready (); + } + } +} + + +/** + * Main function of the test, run from scheduler. + * + * @param cls NULL + * @param cfg configuration we use (also to connect to Social service) + * @param peer handle to access more of the peer (not used) + */ +static void +#if DEBUG_TEST_SOCIAL +run (void *cls, char *const *args, const char *cfgfile, + const struct GNUNET_CONFIGURATION_Handle *c) +#else +run (void *cls, + const struct GNUNET_CONFIGURATION_Handle *c, + struct GNUNET_TESTING_Peer *peer) +#endif +{ + cfg = c; + res = 1; + end_badly_task = GNUNET_SCHEDULER_add_delayed (TIMEOUT, + &end_badly, NULL); + GNUNET_SCHEDULER_add_shutdown (&end_shutdown, + NULL); + GNUNET_CRYPTO_get_peer_identity (cfg, &this_peer); + + id = GNUNET_IDENTITY_connect (cfg, &identity_ego_cb, NULL); + + test = TEST_IDENTITIES_CREATE; + GNUNET_IDENTITY_create (id, host_name, &id_host_created, NULL); + GNUNET_IDENTITY_create (id, guest_name, &id_guest_created, NULL); +} + + +int +main (int argc, char *argv[]) +{ + res = 1; +#if DEBUG_TEST_SOCIAL + const struct GNUNET_GETOPT_CommandLineOption opts[] = { + GNUNET_GETOPT_OPTION_END + }; + if (GNUNET_OK != GNUNET_PROGRAM_run (argc, argv, "test-social", + "test-social [options]", + opts, &run, NULL)) + return 1; +#else + if (0 != GNUNET_TESTING_peer_run ("test-social", "test_social.conf", &run, NULL)) + return 1; +#endif + return res; +} + +/* end of test_social.c */ diff --git a/src/social/test_social.conf b/src/social/test_social.conf new file mode 100644 index 0000000..bc48776 --- /dev/null +++ b/src/social/test_social.conf @@ -0,0 +1,19 @@ +@INLINE@ ../../contrib/conf/gnunet/no_forcestart.conf + +[PATHS] +GNUNET_TEST_HOME = $GNUNET_TMP/gnunet-test-social/ + +[social] +IMMEDIATE_START = YES + +[transport] +PLUGINS = tcp + +[nat] +DISABLEV6 = YES +ENABLE_UPNP = NO +BEHIND_NAT = NO +ALLOW_NAT = NO +INTERNAL_ADDRESS = 127.0.0.1 +EXTERNAL_ADDRESS = 127.0.0.1 +