commit 1f59e703d82b47f3aeaf432045a2633c2841169b Author: Christian Grothoff Date: Mon Feb 11 20:39:36 2019 +0100 initial import from gnunet.git 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 +