diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..027d8ea --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +gen +gen.* +.cproject +.project +.settings/ +.vs/ diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..e69de29 diff --git a/CONTRIBUTING b/CONTRIBUTING new file mode 100644 index 0000000..44c4d8b --- /dev/null +++ b/CONTRIBUTING @@ -0,0 +1,44 @@ +# Contributing to Eclipse Cyclone DDS + +Thanks for your interest in this project. + +## Project description + +Eclipse Cyclone DDS is an implementation of the OMG Data Distribution Service +(DDS) specification (see http://www.omg.org/spec/DDS/ ) and the related +specifications for interoperability (see http://www.omg.org/spec/DDSI-RTPS/ ) + +* https://projects.eclipse.org/projects/iot.cyclonedds + +## Developer resources + +Information regarding source code management, builds, coding standards, and +more. + +* https://projects.eclipse.org/projects/iot.cyclonedds/developer + +The project maintains the following source code repositories + +* https://github.com/eclipse/cyclonedds + +## Eclipse Contributor Agreement + +Before your contribution can be accepted by the project team contributors must +electronically sign the Eclipse Contributor Agreement (ECA). + +* http://www.eclipse.org/legal/ECA.php + +Commits that are provided by non-committers must have a Signed-off-by field in +the footer indicating that the author is aware of the terms by which the +contribution has been provided to the project. The non-committer must +additionally have an Eclipse Foundation account and must have a signed Eclipse +Contributor Agreement (ECA) on file. + +For more information, please see the Eclipse Committer Handbook: +https://www.eclipse.org/projects/handbook/#resources-commit + +## Contact + +Contact the project developers via the project's "dev" list. + +* https://accounts.eclipse.org/mailing-list/cyclonedds-dev diff --git a/LICENSE b/LICENSE index f735bee..38bb25d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,203 +1,318 @@ -Eclipse Public License - v 1.0 +This program and the accompanying materials are made available under the +terms of the Eclipse Public License v. 2.0 which is available at +http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +v. 1.0 which is available at +http://www.eclipse.org/org/documents/edl-v10.php. -THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC -LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM -CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. + + +Eclipse Public License - v 2.0 + + THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE + PUBLIC LICENSE ("AGREEMENT"). ANY USE, REPRODUCTION OR DISTRIBUTION + OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 1. DEFINITIONS "Contribution" means: -a) in the case of the initial Contributor, the initial code and documentation - distributed under this Agreement, and -b) in the case of each subsequent Contributor: - i) changes to the Program, and - ii) additions to the Program; + a) in the case of the initial Contributor, the initial content + Distributed under this Agreement, and - where such changes and/or additions to the Program originate from and are - distributed by that particular Contributor. A Contribution 'originates' - from a Contributor if it was added to the Program by such Contributor - itself or anyone acting on such Contributor's behalf. Contributions do not - include additions to the Program which: (i) are separate modules of - software distributed in conjunction with the Program under their own - license agreement, and (ii) are not derivative works of the Program. + b) in the case of each subsequent Contributor: + i) changes to the Program, and + ii) additions to the Program; + where such changes and/or additions to the Program originate from + and are Distributed by that particular Contributor. A Contribution + "originates" from a Contributor if it was added to the Program by + such Contributor itself or anyone acting on such Contributor's behalf. + Contributions do not include changes or additions to the Program that + are not Modified Works. -"Contributor" means any person or entity that distributes the Program. +"Contributor" means any person or entity that Distributes the Program. -"Licensed Patents" mean patent claims licensable by a Contributor which are -necessarily infringed by the use or sale of its Contribution alone or when -combined with the Program. +"Licensed Patents" mean patent claims licensable by a Contributor which +are necessarily infringed by the use or sale of its Contribution alone +or when combined with the Program. -"Program" means the Contributions distributed in accordance with this +"Program" means the Contributions Distributed in accordance with this Agreement. -"Recipient" means anyone who receives the Program under this Agreement, -including all Contributors. +"Recipient" means anyone who receives the Program under this Agreement +or any Secondary License (as applicable), including Contributors. + +"Derivative Works" shall mean any work, whether in Source Code or other +form, that is based on (or derived from) the Program and for which the +editorial revisions, annotations, elaborations, or other modifications +represent, as a whole, an original work of authorship. + +"Modified Works" shall mean any work in Source Code or other form that +results from an addition to, deletion from, or modification of the +contents of the Program, including, for purposes of clarity any new file +in Source Code form that contains any contents of the Program. Modified +Works shall not include works that contain only declarations, +interfaces, types, classes, structures, or files of the Program solely +in each case in order to link to, bind by name, or subclass the Program +or Modified Works thereof. + +"Distribute" means the acts of a) distributing or b) making available +in any manner that enables the transfer of a copy. + +"Source Code" means the form of a Program preferred for making +modifications, including but not limited to software source code, +documentation source, and configuration files. + +"Secondary License" means either the GNU General Public License, +Version 2.0, or any later versions of that license, including any +exceptions or additional permissions as identified by the initial +Contributor. 2. GRANT OF RIGHTS - a) Subject to the terms of this Agreement, each Contributor hereby grants - Recipient a non-exclusive, worldwide, royalty-free copyright license to - reproduce, prepare derivative works of, publicly display, publicly - perform, distribute and sublicense the Contribution of such Contributor, - if any, and such derivative works, in source code and object code form. - b) Subject to the terms of this Agreement, each Contributor hereby grants - Recipient a non-exclusive, worldwide, royalty-free patent license under - Licensed Patents to make, use, sell, offer to sell, import and otherwise - transfer the Contribution of such Contributor, if any, in source code and - object code form. This patent license shall apply to the combination of - the Contribution and the Program if, at the time the Contribution is - added by the Contributor, such addition of the Contribution causes such - combination to be covered by the Licensed Patents. The patent license - shall not apply to any other combinations which include the Contribution. - No hardware per se is licensed hereunder. - c) Recipient understands that although each Contributor grants the licenses - to its Contributions set forth herein, no assurances are provided by any - Contributor that the Program does not infringe the patent or other - intellectual property rights of any other entity. Each Contributor - disclaims any liability to Recipient for claims brought by any other - entity based on infringement of intellectual property rights or - otherwise. As a condition to exercising the rights and licenses granted - hereunder, each Recipient hereby assumes sole responsibility to secure - any other intellectual property rights needed, if any. For example, if a - third party patent license is required to allow Recipient to distribute - the Program, it is Recipient's responsibility to acquire that license - before distributing the Program. - d) Each Contributor represents that to its knowledge it has sufficient - copyright rights in its Contribution, if any, to grant the copyright - license set forth in this Agreement. + + a) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free copyright + license to reproduce, prepare Derivative Works of, publicly display, + publicly perform, Distribute and sublicense the Contribution of such + Contributor, if any, and such Derivative Works. + + b) Subject to the terms of this Agreement, each Contributor hereby + grants Recipient a non-exclusive, worldwide, royalty-free patent + license under Licensed Patents to make, use, sell, offer to sell, + import and otherwise transfer the Contribution of such Contributor, + if any, in Source Code or other form. This patent license shall + apply to the combination of the Contribution and the Program if, at + the time the Contribution is added by the Contributor, such addition + of the Contribution causes such combination to be covered by the + Licensed Patents. The patent license shall not apply to any other + combinations which include the Contribution. No hardware per se is + licensed hereunder. + + c) Recipient understands that although each Contributor grants the + licenses to its Contributions set forth herein, no assurances are + provided by any Contributor that the Program does not infringe the + patent or other intellectual property rights of any other entity. + Each Contributor disclaims any liability to Recipient for claims + brought by any other entity based on infringement of intellectual + property rights or otherwise. As a condition to exercising the + rights and licenses granted hereunder, each Recipient hereby + assumes sole responsibility to secure any other intellectual + property rights needed, if any. For example, if a third party + patent license is required to allow Recipient to Distribute the + Program, it is Recipient's responsibility to acquire that license + before distributing the Program. + + d) Each Contributor represents that to its knowledge it has + sufficient copyright rights in its Contribution, if any, to grant + the copyright license set forth in this Agreement. + + e) Notwithstanding the terms of any Secondary License, no + Contributor makes additional grants to any Recipient (other than + those set forth in this Agreement) as a result of such Recipient's + receipt of the Program under the terms of a Secondary License + (if permitted under the terms of Section 3). 3. REQUIREMENTS -A Contributor may choose to distribute the Program in object code form under -its own license agreement, provided that: +3.1 If a Contributor Distributes the Program in any form, then: - a) it complies with the terms and conditions of this Agreement; and - b) its license agreement: - i) effectively disclaims on behalf of all Contributors all warranties - and conditions, express and implied, including warranties or - conditions of title and non-infringement, and implied warranties or - conditions of merchantability and fitness for a particular purpose; - ii) effectively excludes on behalf of all Contributors all liability for - damages, including direct, indirect, special, incidental and - consequential damages, such as lost profits; - iii) states that any provisions which differ from this Agreement are - offered by that Contributor alone and not by any other party; and - iv) states that source code for the Program is available from such - Contributor, and informs licensees how to obtain it in a reasonable - manner on or through a medium customarily used for software exchange. + a) the Program must also be made available as Source Code, in + accordance with section 3.2, and the Contributor must accompany + the Program with a statement that the Source Code for the Program + is available under this Agreement, and informs Recipients how to + obtain it in a reasonable manner on or through a medium customarily + used for software exchange; and -When the Program is made available in source code form: + b) the Contributor may Distribute the Program under a license + different than this Agreement, provided that such license: + i) effectively disclaims on behalf of all other Contributors all + warranties and conditions, express and implied, including + warranties or conditions of title and non-infringement, and + implied warranties or conditions of merchantability and fitness + for a particular purpose; - a) it must be made available under this Agreement; and - b) a copy of this Agreement must be included with each copy of the Program. - Contributors may not remove or alter any copyright notices contained - within the Program. + ii) effectively excludes on behalf of all other Contributors all + liability for damages, including direct, indirect, special, + incidental and consequential damages, such as lost profits; -Each Contributor must identify itself as the originator of its Contribution, -if -any, in a manner that reasonably allows subsequent Recipients to identify the -originator of the Contribution. + iii) does not attempt to limit or alter the recipients' rights + in the Source Code under section 3.2; and + + iv) requires any subsequent distribution of the Program by any + party to be under a license that satisfies the requirements + of this section 3. + +3.2 When the Program is Distributed as Source Code: + + a) it must be made available under this Agreement, or if the + Program (i) is combined with other material in a separate file or + files made available under a Secondary License, and (ii) the initial + Contributor attached to the Source Code the notice described in + Exhibit A of this Agreement, then the Program may be made available + under the terms of such Secondary Licenses, and + + b) a copy of this Agreement must be included with each copy of + the Program. + +3.3 Contributors may not remove or alter any copyright, patent, +trademark, attribution notices, disclaimers of warranty, or limitations +of liability ("notices") contained within the Program from any copy of +the Program which they Distribute, provided that Contributors may add +their own appropriate notices. 4. COMMERCIAL DISTRIBUTION -Commercial distributors of software may accept certain responsibilities with -respect to end users, business partners and the like. While this license is -intended to facilitate the commercial use of the Program, the Contributor who -includes the Program in a commercial product offering should do so in a manner -which does not create potential liability for other Contributors. Therefore, -if a Contributor includes the Program in a commercial product offering, such -Contributor ("Commercial Contributor") hereby agrees to defend and indemnify -every other Contributor ("Indemnified Contributor") against any losses, -damages and costs (collectively "Losses") arising from claims, lawsuits and -other legal actions brought by a third party against the Indemnified -Contributor to the extent caused by the acts or omissions of such Commercial -Contributor in connection with its distribution of the Program in a commercial -product offering. The obligations in this section do not apply to any claims -or Losses relating to any actual or alleged intellectual property -infringement. In order to qualify, an Indemnified Contributor must: -a) promptly notify the Commercial Contributor in writing of such claim, and -b) allow the Commercial Contributor to control, and cooperate with the -Commercial Contributor in, the defense and any related settlement -negotiations. The Indemnified Contributor may participate in any such claim at -its own expense. +Commercial distributors of software may accept certain responsibilities +with respect to end users, business partners and the like. While this +license is intended to facilitate the commercial use of the Program, +the Contributor who includes the Program in a commercial product +offering should do so in a manner which does not create potential +liability for other Contributors. Therefore, if a Contributor includes +the Program in a commercial product offering, such Contributor +("Commercial Contributor") hereby agrees to defend and indemnify every +other Contributor ("Indemnified Contributor") against any losses, +damages and costs (collectively "Losses") arising from claims, lawsuits +and other legal actions brought by a third party against the Indemnified +Contributor to the extent caused by the acts or omissions of such +Commercial Contributor in connection with its distribution of the Program +in a commercial product offering. The obligations in this section do not +apply to any claims or Losses relating to any actual or alleged +intellectual property infringement. In order to qualify, an Indemnified +Contributor must: a) promptly notify the Commercial Contributor in +writing of such claim, and b) allow the Commercial Contributor to control, +and cooperate with the Commercial Contributor in, the defense and any +related settlement negotiations. The Indemnified Contributor may +participate in any such claim at its own expense. -For example, a Contributor might include the Program in a commercial product -offering, Product X. That Contributor is then a Commercial Contributor. If -that Commercial Contributor then makes performance claims, or offers -warranties related to Product X, those performance claims and warranties are -such Commercial Contributor's responsibility alone. Under this section, the -Commercial Contributor would have to defend claims against the other -Contributors related to those performance claims and warranties, and if a -court requires any other Contributor to pay any damages as a result, the -Commercial Contributor must pay those damages. +For example, a Contributor might include the Program in a commercial +product offering, Product X. That Contributor is then a Commercial +Contributor. If that Commercial Contributor then makes performance +claims, or offers warranties related to Product X, those performance +claims and warranties are such Commercial Contributor's responsibility +alone. Under this section, the Commercial Contributor would have to +defend claims against the other Contributors related to those performance +claims and warranties, and if a court requires any other Contributor to +pay any damages as a result, the Commercial Contributor must pay +those damages. 5. NO WARRANTY -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, THE PROGRAM IS PROVIDED ON AN -"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR -IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, -NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each -Recipient is solely responsible for determining the appropriateness of using -and distributing the Program and assumes all risks associated with its -exercise of rights under this Agreement , including but not limited to the -risks and costs of program errors, compliance with applicable laws, damage to -or loss of data, programs or equipment, and unavailability or interruption of -operations. +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN "AS IS" +BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR +IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF +TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR +PURPOSE. Each Recipient is solely responsible for determining the +appropriateness of using and distributing the Program and assumes all +risks associated with its exercise of rights under this Agreement, +including but not limited to the risks and costs of program errors, +compliance with applicable laws, damage to or loss of data, programs +or equipment, and unavailability or interruption of operations. 6. DISCLAIMER OF LIABILITY -EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, NEITHER RECIPIENT NOR ANY -CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION -LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT +PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS +SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST +PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE -EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY -OF SUCH DAMAGES. +EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. 7. GENERAL If any provision of this Agreement is invalid or unenforceable under -applicable law, it shall not affect the validity or enforceability of the -remainder of the terms of this Agreement, and without further action by the -parties hereto, such provision shall be reformed to the minimum extent -necessary to make such provision valid and enforceable. +applicable law, it shall not affect the validity or enforceability of +the remainder of the terms of this Agreement, and without further +action by the parties hereto, such provision shall be reformed to the +minimum extent necessary to make such provision valid and enforceable. -If Recipient institutes patent litigation against any entity (including a -cross-claim or counterclaim in a lawsuit) alleging that the Program itself -(excluding combinations of the Program with other software or hardware) -infringes such Recipient's patent(s), then such Recipient's rights granted -under Section 2(b) shall terminate as of the date such litigation is filed. +If Recipient institutes patent litigation against any entity +(including a cross-claim or counterclaim in a lawsuit) alleging that the +Program itself (excluding combinations of the Program with other software +or hardware) infringes such Recipient's patent(s), then such Recipient's +rights granted under Section 2(b) shall terminate as of the date such +litigation is filed. -All Recipient's rights under this Agreement shall terminate if it fails to -comply with any of the material terms or conditions of this Agreement and does -not cure such failure in a reasonable period of time after becoming aware of -such noncompliance. If all Recipient's rights under this Agreement terminate, -Recipient agrees to cease use and distribution of the Program as soon as -reasonably practicable. However, Recipient's obligations under this Agreement -and any licenses granted by Recipient relating to the Program shall continue -and survive. +All Recipient's rights under this Agreement shall terminate if it +fails to comply with any of the material terms or conditions of this +Agreement and does not cure such failure in a reasonable period of +time after becoming aware of such noncompliance. If all Recipient's +rights under this Agreement terminate, Recipient agrees to cease use +and distribution of the Program as soon as reasonably practicable. +However, Recipient's obligations under this Agreement and any licenses +granted by Recipient relating to the Program shall continue and survive. -Everyone is permitted to copy and distribute copies of this Agreement, but in -order to avoid inconsistency the Agreement is copyrighted and may only be -modified in the following manner. The Agreement Steward reserves the right to -publish new versions (including revisions) of this Agreement from time to -time. No one other than the Agreement Steward has the right to modify this -Agreement. The Eclipse Foundation is the initial Agreement Steward. The -Eclipse Foundation may assign the responsibility to serve as the Agreement -Steward to a suitable separate entity. Each new version of the Agreement will -be given a distinguishing version number. The Program (including -Contributions) may always be distributed subject to the version of the -Agreement under which it was received. In addition, after a new version of the -Agreement is published, Contributor may elect to distribute the Program -(including its Contributions) under the new version. Except as expressly -stated in Sections 2(a) and 2(b) above, Recipient receives no rights or -licenses to the intellectual property of any Contributor under this Agreement, -whether expressly, by implication, estoppel or otherwise. All rights in the -Program not expressly granted under this Agreement are reserved. +Everyone is permitted to copy and distribute copies of this Agreement, +but in order to avoid inconsistency the Agreement is copyrighted and +may only be modified in the following manner. The Agreement Steward +reserves the right to publish new versions (including revisions) of +this Agreement from time to time. No one other than the Agreement +Steward has the right to modify this Agreement. The Eclipse Foundation +is the initial Agreement Steward. The Eclipse Foundation may assign the +responsibility to serve as the Agreement Steward to a suitable separate +entity. Each new version of the Agreement will be given a distinguishing +version number. The Program (including Contributions) may always be +Distributed subject to the version of the Agreement under which it was +received. In addition, after a new version of the Agreement is published, +Contributor may elect to Distribute the Program (including its +Contributions) under the new version. -This Agreement is governed by the laws of the State of New York and the -intellectual property laws of the United States of America. No party to this -Agreement will bring a legal action under this Agreement more than one year -after the cause of action arose. Each party waives its rights to a jury trial in -any resulting litigation. +Except as expressly stated in Sections 2(a) and 2(b) above, Recipient +receives no rights or licenses to the intellectual property of any +Contributor under this Agreement, whether expressly, by implication, +estoppel or otherwise. All rights in the Program not expressly granted +under this Agreement are reserved. Nothing in this Agreement is intended +to be enforceable by any entity that is not a Contributor or Recipient. +No third-party beneficiary rights are created under this Agreement. + +Exhibit A - Form of Secondary Licenses Notice + +"This Source Code may also be made available under the following +Secondary Licenses when the conditions for such availability set forth +in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), +version(s), and exceptions or additional permissions here}." + + Simply including a copy of this Agreement, including this Exhibit A + is not sufficient to license the Source Code under Secondary Licenses. + + If it is not possible or desirable to put the notice in a particular + file, then You may include the notice in a location (such as a LICENSE + file in a relevant directory) where a recipient would be likely to + look for such a notice. + + You may add additional accurate notices of copyright ownership. + + + +Eclipse Distribution License - v 1.0 + +Copyright (c) 2007, Eclipse Foundation, Inc. and its licensors. + +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. + Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + Neither the name of the Eclipse Foundation, Inc. nor the names of its + contributors may be used to endorse or promote products derived from + this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/NOTICE b/NOTICE new file mode 100644 index 0000000..78656d8 --- /dev/null +++ b/NOTICE @@ -0,0 +1,44 @@ +# Notices for Eclipse Cyclone DDS + +This content is produced and maintained by the Eclipse Cyclone DDS project. + +* Project home: https://projects.eclipse.org/projects/iot.cyclonedds + +## Trademarks + +Eclipse Cyclone DDS is a trademark of the Eclipse Foundation. + +## Copyright + +All content is the property of the respective authors or their employers. For +more information regarding authorship of content, please consult the listed +source code repository logs. + +## Declared Project Licenses + +This program and the accompanying materials are made available under the terms +of the Eclipse Public License v. 2.0 which is available at +http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License v. 1.0 +which is available at http://www.eclipse.org/org/documents/edl-v10.php. + +SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + +## Source Code + +The project maintains the following source code repositories: + +* https://github.com/eclipse/cyclonedds + +## Third-party Content + + + +## Cryptography + +Content may contain encryption software. The country in which you are currently +may have restrictions on the import, possession, and use, and/or re-export to +another country, of encryption software. BEFORE using any encryption software, +please check the country's laws, regulations and policies concerning the import, +possession, or use, and re-export of encryption software, to see if this is +permitted. + diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt new file mode 100644 index 0000000..fccb870 --- /dev/null +++ b/src/CMakeLists.txt @@ -0,0 +1,77 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +cmake_minimum_required(VERSION 3.5) + +FUNCTION(PREPEND var prefix) + SET(listVar "") + FOREACH(f ${ARGN}) + LIST(APPEND listVar "${prefix}/${f}") + ENDFOREACH(f) + SET(${var} "${listVar}" PARENT_SCOPE) +ENDFUNCTION(PREPEND) + +# Update the git submodules +execute_process(COMMAND git submodule init + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) +execute_process(COMMAND git submodule update + WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + +# Set module path before defining project so platform files will work. +set(CMAKE_MODULE_PATH "${CMAKE_CURRENT_LIST_DIR}/cmake/modules") +set(CMAKE_PROJECT_NAME_FULL "Cyclone DDS") +string(REPLACE " " "" PROJECT_NAME "${CMAKE_PROJECT_NAME_FULL}") +project(${PROJECT_NAME} VERSION 0.1.0) + +find_package(Abstraction REQUIRED) + +# Set some convenience variants of the project-name +string(REPLACE " " "-" CMAKE_PROJECT_NAME_DASHED "${CMAKE_PROJECT_NAME_FULL}") +string(TOUPPER ${CMAKE_PROJECT_NAME} CMAKE_PROJECT_NAME_CAPS) +string(TOLOWER ${CMAKE_PROJECT_NAME} CMAKE_PROJECT_NAME_SMALL) + +set(CMAKE_C_STANDARD 99) +if(CMAKE_SYSTEM_NAME STREQUAL "VxWorks") + add_definitions(-std=c99) +endif() + +include(FileIDs) +include(GNUInstallDirs) +include(AnalyzeBuild) +# Include Coverage before CTest so that COVERAGE_COMMAND can be modified +# in the Coverage module should that ever be necessary. +include(Coverage) +set(MEMORYCHECK_COMMAND_OPTIONS "--track-origins=yes --leak-check=full --trace-children=yes --child-silent-after-fork=yes --xml=yes --xml-file=TestResultValgrind_%p.xml --tool=memcheck --show-reachable=yes --leak-resolution=high") +set(MEMORYCHECK_SUPPRESSIONS_FILE "${CMAKE_CURRENT_LIST_DIR}/valgrind.supp" CACHE FILEPATH "suppression file") + +# By default building the testing tree is enabled by including CTest, but +# since not everybody has CUnit and/or Criterion installed, and because it is +# not strictly required to build the product itself, switch to off by default. +option(BUILD_TESTING "Build the testing tree." OFF) +include(CTest) + +# Build all executables and libraries into the top-level /bin and /lib folders. +set(CMAKE_RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/bin") +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") +set(CMAKE_ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") + +add_subdirectory(idlc) +add_subdirectory(os) +add_subdirectory(etc) +add_subdirectory(util) +add_subdirectory(core) +add_subdirectory(tools) +add_subdirectory(scripts) +#add_subdirectory(examples) +#add_subdirectory(docs) + +# Pull-in CPack and support for generating Config.cmake and packages. +include(Packaging) diff --git a/src/cmake/CoverageSettings.cmake.in b/src/cmake/CoverageSettings.cmake.in new file mode 100644 index 0000000..818584e --- /dev/null +++ b/src/cmake/CoverageSettings.cmake.in @@ -0,0 +1,31 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +cmake_minimum_required(VERSION 3.5) + +set(COVERAGE_SOURCE_DIR "@PROJECT_SOURCE_DIR@") +set(COVERAGE_RUN_DIR "@CMAKE_BINARY_DIR@") +set(COVERAGE_OUTPUT_DIR "@CMAKE_BINARY_DIR@/coverage") + +# TODO: Make this a list instead of separate variables when more directories +# need to be excluded. Currently there's actually only one directory to +# be excluded, but when the test(s) are moved, more directories will be +# added. I just added two directories to indicate how the coverage +# generators handle multiple exclusion directories. +# +# Do not include the various test directories. +set(COVERAGE_EXCLUDE_TESTS "tests") +set(COVERAGE_EXCLUDE_EXAMPLES "examples") +set(COVERAGE_EXCLUDE_BUILD_SUPPORT "cmake") + +# Add this flag when you want to suppress LCOV and ctest outputs during coverage collecting. +#set(COVERAGE_QUIET_FLAG "--quiet") + diff --git a/src/cmake/launch-c.bat.in b/src/cmake/launch-c.bat.in new file mode 100644 index 0000000..8f771d9 --- /dev/null +++ b/src/cmake/launch-c.bat.in @@ -0,0 +1,26 @@ +@echo off +REM Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +REM +REM This program and the accompanying materials are made available under the +REM terms of the Eclipse Public License v. 2.0 which is available at +REM http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +REM v. 1.0 which is available at +REM http://www.eclipse.org/org/documents/edl-v10.php. +REM +REM SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + +REM VxWorks toolchain requires WIND_BASE to be exported, should the user be +REM compiling for VxWorks and not have WIND_BASE exported, do that here before +REM invoking the compiler. +if "%WIND_BASE%" == "" ( + set WIND_BASE="@WIND_BASE@" +) + +REM Strip C compiler from command line arguments for compatibility because +REM this launcher may also be used from an integrated development environment +REM at some point. +if "%1" == "@CMAKE_C_COMPILER@" ( + shift +) + +"@CMAKE_C_COMPILER@" %* diff --git a/src/cmake/launch-c.in b/src/cmake/launch-c.in new file mode 100644 index 0000000..27a64da --- /dev/null +++ b/src/cmake/launch-c.in @@ -0,0 +1,40 @@ +#!/bin/sh +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# + +# VxWorks toolchain requires WIND_BASE to be exported, should the user be +# compiling for VxWorks and not have WIND_BASE exported, to that here before +# invoking the compiler. +if [ -z "${WIND_BASE}" ] && [ -n "@WIND_BASE@" ]; then + WIND_BASE="@WIND_BASE@" + export WIND_BASE +fi + +if [ -n "@WIND_LMAPI@" ]; then + if [ -z "${LD_LIBRARY_PATH}" ]; then + LD_LIBRARY_PATH="@WIND_LMAPI@" + export LD_LIBRARY_PATH + elif [[ "${LD_LIBRARY_PATH}" == ?(*:)"@WIND_LMAPI@"?(:*) ]]; then + LD_LIBRARY_PATH="@WIND_LMAPI@:${LD_LIBRARY_PATH}" + export LD_LIBRARY_PATH + fi +fi + +# Strip C compiler from command line arguments for compatibility because this +# launcher may also be used from an integrated development environment at some +# point. +if [ "$1" = "@CMAKE_C_COMPILER@" ]; then + shift +fi + +exec "@CMAKE_C_COMPILER@" "$@" + diff --git a/src/cmake/launch-cxx.bat.in b/src/cmake/launch-cxx.bat.in new file mode 100644 index 0000000..5d38b3d --- /dev/null +++ b/src/cmake/launch-cxx.bat.in @@ -0,0 +1,26 @@ +@echo off +REM Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +REM +REM This program and the accompanying materials are made available under the +REM terms of the Eclipse Public License v. 2.0 which is available at +REM http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +REM v. 1.0 which is available at +REM http://www.eclipse.org/org/documents/edl-v10.php. +REM +REM SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + +REM VxWorks toolchain requires WIND_BASE to be exported, should the user be +REM compiling for VxWorks and not have WIND_BASE exported, do that here before +REM invoking the compiler. +if "%WIND_BASE%" == "" ( + set WIND_BASE="@WIND_BASE@" +) + +REM Strip C compiler from command line arguments for compatibility because +REM this launcher may also be used from an integrated development environment +REM at some point. +if "%1" == "@CMAKE_CXX_COMPILER@" ( + shift +) + +"@CMAKE_CXX_COMPILER@" %* diff --git a/src/cmake/launch-cxx.in b/src/cmake/launch-cxx.in new file mode 100644 index 0000000..0d3bd19 --- /dev/null +++ b/src/cmake/launch-cxx.in @@ -0,0 +1,40 @@ +#!/bin/sh +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# + +# VxWorks toolchain requires WIND_BASE to be exported, should the user be +# compiling for VxWorks and not have WIND_BASE exported, to that here before +# invoking the compiler. +if [ -z "${WIND_BASE}" ] && [ -n "@WIND_BASE@" ]; then + WIND_BASE="@WIND_BASE@" + export WIND_BASE +fi + +if [ -n "@WIND_LMAPI@" ]; then + if [ -z "${LD_LIBRARY_PATH}" ]; then + LD_LIBRARY_PATH="@WIND_LMAPI@" + export LD_LIBRARY_PATH + elif [[ "${LD_LIBRARY_PATH}" == ?(*:)"@WIND_LMAPI@"?(:*) ]]; then + LD_LIBRARY_PATH="@WIND_LMAPI@:${LD_LIBRARY_PATH}" + export LD_LIBRARY_PATH + fi +fi + +# Strip C compiler from command line arguments for compatibility because this +# launcher may also be used from an integrated development environment at some +# point. +if [ "$1" = "@CMAKE_CXX_COMPILER@" ]; then + shift +fi + +exec "@CMAKE_CXX_COMPILER@" "$@" + diff --git a/src/cmake/modules/AnalyzeBuild.cmake b/src/cmake/modules/AnalyzeBuild.cmake new file mode 100644 index 0000000..20b664a --- /dev/null +++ b/src/cmake/modules/AnalyzeBuild.cmake @@ -0,0 +1,65 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +cmake_minimum_required(VERSION 3.3) # For IN_LIST + +option(ANALYZE_BUILD "Enable static analysis during build" OFF) + +if(ANALYZE_BUILD) + get_property(languages GLOBAL PROPERTY ENABLED_LANGUAGES) + set(scan_build_supported Clang AppleClang GNU) + + if("C" IN_LIST languages) + include(CheckCCompilerFlag) + + if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") + set(ANALYZE_C_BUILD_FLAG "/analyze") + elseif(CMAKE_C_COMPILER_ID IN_LIST scan_build_supported) + message(STATUS "Static analysis for C using ${CMAKE_C_COMPILER_ID}-compiler is available by using 'scan-build' manually only") + endif() + + if(CMAKE_C_COMPILER_ID STREQUAL "MSVC") + CHECK_C_COMPILER_FLAG(${ANALYZE_C_BUILD_FLAG} C_COMPILER_HAS_ANALYZE_BUILD) + + if(C_COMPILER_HAS_ANALYZE_BUILD) + if(CMAKE_GENERATOR MATCHES "Visual Studio") + # $ may not be used with Visual Studio generators. + add_compile_options(${ANALYZE_C_BUILD_FLAG}) + else() + add_compile_options($<$:${ANALYZE_C_BUILD_FLAG}>) + endif() + endif() + endif() + endif() + + if("CXX" IN_LIST languages) + include(CheckCXXCompilerFlag) + + if(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC") + set(ANALYZE_CXX_BUILD_FLAG "/analyze") + elseif(CMAKE_C_COMPILER_ID IN_LIST scan_build_supported) + message(STATUS "Static analysis for CXX using ${CMAKE_CXX_COMPILER_ID}-compiler is available by using 'scan-build' manually only") + endif() + + if(DEFINED ANALYZE_CXX_BUILD_FLAG) + CHECK_CXX_COMPILER_FLAG(${ANALYZE_CXX_BUILD_FLAG} CXX_COMPILER_HAS_ANALYZE_BUILD) + + if(CXX_COMPILER_HAS_ANALYZE_BUILD) + if(CMAKE_GENERATOR MATCHES "Visual Studio") + add_compile_options(${ANALYZE_CXX_BUILD_FLAG}) + else() + add_compile_options($<$:${ANALYZE_CXX_BUILD_FLAG}>) + endif() + endif() + endif() + endif() +endif() + diff --git a/src/cmake/modules/CUnit.cmake b/src/cmake/modules/CUnit.cmake new file mode 100644 index 0000000..699cb20 --- /dev/null +++ b/src/cmake/modules/CUnit.cmake @@ -0,0 +1,128 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +find_package(CUnit REQUIRED) + +include(Glob) + +set(CUNIT_DIR "${CMAKE_CURRENT_LIST_DIR}/CUnit") + +function(add_cunit_executable target) + # Generate semi-random filename to store the generated code in to avoid + # possible naming conflicts. + string(RANDOM random) + set(runner "${target}_${random}") + + set(s "[ \t\r\n]") # space + set(w "[0-9a-zA-Z_]") # word + set(param "${s}*(${w}+)${s}*") + set(pattern "CUnit_${w}+${s}*\\(${param}(,${param}(,${param})?)?\\)") + + glob(filenames "c" ${ARGN}) + + foreach(filename ${filenames}) + file(READ "${filename}" contents) + string(REGEX MATCHALL "${pattern}" captures "${contents}") + + list(APPEND sources "${filename}") + list(LENGTH captures length) + if(length) + foreach(capture ${captures}) + string(REGEX REPLACE "${pattern}" "\\1" suite "${capture}") + + if("${capture}" MATCHES "CUnit_Suite_Initialize") + list(APPEND suites ${suite}) + list(APPEND suites_w_init ${suite}) + elseif("${capture}" MATCHES "CUnit_Suite_Cleanup") + list(APPEND suites ${suite}) + list(APPEND suites_w_deinit ${suite}) + elseif("${capture}" MATCHES "CUnit_Test") + list(APPEND suites ${suite}) + + # Specifying a test name is mandatory + if("${capture}" MATCHES ",") + string(REGEX REPLACE "${pattern}" "\\3" test "${capture}") + else() + message(FATAL_ERROR "Bad CUnit_Test signature in ${filename}") + endif() + + # Specifying if a test is enabled is optional + set(enable "true") + if("${capture}" MATCHES ",${param},") + string(REGEX REPLACE "${pattern}" "\\5" enable "${capture}") + endif() + + if((NOT "${enable}" STREQUAL "true") AND + (NOT "${enable}" STREQUAL "false")) + message(FATAL_ERROR "Bad CUnit_Test signature in ${filename}") + endif() + + list(APPEND tests "${suite}:${test}:${enable}") + else() + message(FATAL_ERROR "Bad CUnit signature in ${filename}") + endif() + endforeach() + endif() + endforeach() + + # Test suite signatures can be decided on only after everything is parsed. + set(lf "\n") + set(declf "") + set(deflf "") + + list(REMOVE_DUPLICATES suites) + list(SORT suites) + foreach(suite ${suites}) + set(init "NULL") + set(deinit "NULL") + if(${suite} IN_LIST suites_w_init) + set(init "CUnit_Suite_Initialize__(${suite})") + set(decls "${decls}${declf}CUnit_Suite_Initialize_Decl__(${suite});") + set(declf "${lf}") + endif() + if(${suite} IN_LIST suites_w_deinit) + set(deinit "CUnit_Suite_Cleanup__(${suite})") + set(decls "${decls}${declf}CUnit_Suite_Cleanup_Decl__(${suite});") + set(declf "${lf}") + endif() + + set(defs "${defs}${deflf}CUnit_Suite__(${suite}, ${init}, ${deinit});") + set(deflf "${lf}") + endforeach() + + list(REMOVE_DUPLICATES tests) + list(SORT tests) + foreach(entry ${tests}) + string(REPLACE ":" ";" entry ${entry}) + list(GET entry 0 suite) + list(GET entry 1 test) + list(GET entry 2 enable) + + set(decls "${decls}${declf}CUnit_Test_Decl__(${suite}, ${test});") + set(declf "${lf}") + set(defs "${defs}${deflf}CUnit_Test__(${suite}, ${test}, ${enable});") + set(deflf "${lf}") + + add_test( + NAME "CUnit_${suite}_${test}" + COMMAND ${target} -a -r "${suite}-${test}" -s ${suite} -t ${test}) + endforeach() + + set(root "${CUNIT_DIR}") + set(CUnit_Decls "${decls}") + set(CUnit_Defs "${defs}") + + configure_file("${root}/src/main.c.in" "${runner}.c" @ONLY) + add_executable(${target} "${runner}.c" "${root}/src/runner.c" ${sources}) + target_link_libraries(${target} CUnit) + target_include_directories(${target} PRIVATE "${root}/include") +endfunction() + diff --git a/src/cmake/modules/CUnit/include/CUnit/Runner.h b/src/cmake/modules/CUnit/include/CUnit/Runner.h new file mode 100644 index 0000000..e7cc113 --- /dev/null +++ b/src/cmake/modules/CUnit/include/CUnit/Runner.h @@ -0,0 +1,83 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef CUNIT_RUNNER_H +#define CUNIT_RUNNER_H + +#include +#include +#include + +#if defined (__cplusplus) +extern "C" { +#endif + +#define CUnit_Suite_Initialize_Name__(s) \ + s ## _Initialize +#define CUnit_Suite_Initialize(s) \ + int CUnit_Suite_Initialize_Name__(s)(void) +#define CUnit_Suite_Initialize_Decl__(s) \ + extern CUnit_Suite_Initialize(s) +#define CUnit_Suite_Initialize__(s) \ + CUnit_Suite_Initialize_Name__(s) + +#define CUnit_Suite_Cleanup_Name__(s) \ + s ## _Cleanup +#define CUnit_Suite_Cleanup(s) \ + int CUnit_Suite_Cleanup_Name__(s)(void) +#define CUnit_Suite_Cleanup_Decl__(s) \ + extern CUnit_Suite_Cleanup(s) +#define CUnit_Suite_Cleanup__(s) \ + CUnit_Suite_Cleanup_Name__(s) + +#define CUnit_Test_Name__(s, t) \ + s ## _ ## t +#define CUnit_Test(s, t, ...) \ + void CUnit_Test_Name__(s, t)(void) +#define CUnit_Test_Decl__(s, t) \ + extern CUnit_Test(s, t) + +#define CUnit_Suite__(s, c, d) \ + cu_runner_add_suite(#s, c, d) +#define CUnit_Test__(s, t, e) \ + cu_runner_add_test(#s, #t, CUnit_Test_Name__(s, t), e) + +CU_ErrorCode +cu_runner_init( + int argc, + char *argv[]); + +void +cu_runner_fini( + void); + +void +cu_runner_add_suite( + const char *suite, + CU_InitializeFunc pInitFunc, + CU_CleanupFunc pCleanFunc); + +void +cu_runner_add_test( + const char *suite, + const char *test, + CU_TestFunc pTestFunc, + bool enable); + +CU_ErrorCode +cu_runner_run( + void); + +#if defined (__cplusplus) +} +#endif + +#endif /* CUNIT_RUNNER_H */ diff --git a/src/cmake/modules/CUnit/src/main.c.in b/src/cmake/modules/CUnit/src/main.c.in new file mode 100644 index 0000000..ab9ed01 --- /dev/null +++ b/src/cmake/modules/CUnit/src/main.c.in @@ -0,0 +1,31 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "CUnit/Runner.h" + +@CUnit_Decls@ + +int main (int argc, char *argv[]) +{ + CU_ErrorCode err; + + if ((err = cu_runner_init(argc, argv))) { + goto err_init; + } + +@CUnit_Defs@ + + err = cu_runner_run(); + cu_runner_fini(); +err_init: + return err; +} + diff --git a/src/cmake/modules/CUnit/src/runner.c b/src/cmake/modules/CUnit/src/runner.c new file mode 100644 index 0000000..b6799e8 --- /dev/null +++ b/src/cmake/modules/CUnit/src/runner.c @@ -0,0 +1,213 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include + +#include "CUnit/Runner.h" + +static struct cunit_runner { + bool automated; + bool junit; + const char * results; + CU_BasicRunMode mode; + CU_ErrorAction error_action; + const char *suite; + const char *test; +} runner; + +static void +usage( + const char * name) +{ + fprintf(stderr, "usage: %s [flags]\n", name); + fprintf(stderr, "Supported flags:\n"); + fprintf(stderr, " -a run in automated mode\n"); + fprintf(stderr, " -r results file for automated run\n"); + fprintf(stderr, " -j junit format results \n"); + fprintf(stderr, " -f fail fast \n"); + fprintf(stderr, " -s suite\n"); + fprintf(stderr, " -t test\n"); +} + +int +patmatch( + const char *pat, + const char *str) +{ + while (*pat) { + if (*pat == '?') { + /* any character will do */ + if (*str++ == 0) { + return 0; + } + pat++; + } else if (*pat == '*') { + /* collapse a sequence of wildcards, requiring as many + characters in str as there are ?s in the sequence */ + while (*pat == '*' || *pat == '?') { + if (*pat == '?' && *str++ == 0) { + return 0; + } + pat++; + } + /* try matching on all positions where str matches pat */ + while (*str) { + if (*str == *pat && patmatch(pat+1, str+1)) { + return 1; + } + str++; + } + return *pat == 0; + } else { + /* only an exact match */ + if (*str++ != *pat++) { + return 0; + } + } + } + + return *str == 0; +} + +CU_ErrorCode +cu_runner_init( + int argc, + char* argv[]) +{ + int c, i; + CU_ErrorCode e = CUE_SUCCESS; + + runner.automated = false; + runner.junit = false; + runner.results = NULL; + runner.mode = CU_BRM_NORMAL; + runner.error_action = CUEA_IGNORE; + runner.suite = "*"; + runner.test = "*"; + + for (i = 1; e == CUE_SUCCESS && i < argc; i++) { + c = (argv[i][0] == '-') ? argv[i][1] : -1; + switch (argv[i][1]) { + case 'a': + runner.automated = true; + break; + case 'f': + runner.error_action = CUEA_FAIL; + break; + case 'j': + runner.junit = true; + break; + case 'r': + if((i+1) < argc){ + runner.results = argv[++i]; + break; + } + /* no break */ + case 's': + if ((i+1) < argc) { + runner.suite = argv[++i]; + break; + } + /* no break */ + case 't': + if ((i+1) < argc) { + runner.test = argv[++i]; + break; + } + /* no break */ + default: + e = (CU_ErrorCode)256; + CU_set_error(e); /* Will print as "Undefined Errpr" */ + usage(argv[0]); + break; + } + } + + if (e == CUE_SUCCESS) { + if ((e = CU_initialize_registry()) != CUE_SUCCESS) { + fprintf( + stderr, "Test registry initialization failed: %s\n", CU_get_error_msg()); + } + } + + CU_set_error_action (runner.error_action); + + return e; +} + +void +cu_runner_fini( + void) +{ + CU_cleanup_registry(); +} + +void +cu_runner_add_suite( + const char *suite, + CU_InitializeFunc pInitFunc, + CU_CleanupFunc pCleanFunc) +{ + CU_pSuite pSuite; + + pSuite = CU_get_suite(suite); + if (pSuite == NULL) { + pSuite = CU_add_suite(suite, pInitFunc, pCleanFunc); + //assert(pSuite != NULL); + CU_set_suite_active(pSuite, patmatch(runner.suite, suite)); + } +} + +void +cu_runner_add_test( + const char *suite, + const char *test, + CU_TestFunc pTestFunc, + bool enable) +{ + CU_pSuite pSuite; + CU_pTest pTest; + + pSuite = CU_get_suite(suite); + //assert(pSuite != NULL); + pTest = CU_add_test(pSuite, test, pTestFunc); + //assert(pTest != NULL); + CU_set_test_active(pTest, enable && patmatch(runner.test, test)); +} + +CU_ErrorCode +cu_runner_run( + void) +{ + if (runner.automated) { + /* Generate CUnit or JUnit format results */ + if (runner.results != NULL) { + CU_set_output_filename(runner.results); + } + + if (runner.junit) { + CU_automated_enable_junit_xml(CU_TRUE); + } else { + CU_list_tests_to_file(); + } + CU_automated_run_tests(); + } else { + CU_basic_set_mode(runner.mode); + CU_basic_run_tests(); + } + + if (CU_get_error() == 0) { + return (CU_get_number_of_failures() != 0); + } + + return CU_get_error(); +} diff --git a/src/cmake/modules/Coverage.cmake b/src/cmake/modules/Coverage.cmake new file mode 100644 index 0000000..5e6fc6d --- /dev/null +++ b/src/cmake/modules/Coverage.cmake @@ -0,0 +1,50 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +if("${CMAKE_BUILD_TYPE}" STREQUAL "Coverage") + set(BUILD_TYPE_SUPPORTED False) + mark_as_advanced(BUILD_TYPE_SUPPORTED) + if(CMAKE_COMPILER_IS_GNUCXX) + set(BUILD_TYPE_SUPPORTED True) + elseif(("${CMAKE_C_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") AND + ("${CMAKE_C_COMPILER_VERSION}" VERSION_GREATER "3.0.0")) + set(BUILD_TYPE_SUPPORTED True) + endif() + + if(NOT BUILD_TYPE_SUPPORTED) + message(FATAL_ERROR "Coverage build type not supported. (GCC or Clang " + ">3.0.0 required)") + endif() + + # NOTE: Since either GCC or Clang is required for now, and the coverage + # flags are the same for both, there is no need for seperate branches + # to set compiler flags. That might change in the future. + + # CMake has per build type compiler and linker flags. If 'Coverage' is + # chosen, the flags below are automatically inserted into CMAKE_C_FLAGS. + # + # Any optimizations are disabled to ensure coverage results are correct. + # See https://gcc.gnu.org/onlinedocs/gcc/Gcov-and-Optimization.html. + set(CMAKE_C_FLAGS_COVERAGE + "-DNDEBUG -g -O0 --coverage -fprofile-arcs -ftest-coverage") + set(CMAKE_CXX_FLAGS_COVERAGE + "-DNDEBUG -g -O0 --coverage -fprofile-arcs -ftest-coverage") + mark_as_advanced( + CMAKE_C_FLAGS_COVERAGE + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE) + + configure_file(${CMAKE_MODULE_PATH}/../CoverageSettings.cmake.in CoverageSettings.cmake @ONLY) + + message(STATUS "Coverage build type available") +endif() + diff --git a/src/cmake/modules/Criterion.cmake b/src/cmake/modules/Criterion.cmake new file mode 100644 index 0000000..90e809c --- /dev/null +++ b/src/cmake/modules/Criterion.cmake @@ -0,0 +1,78 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +find_package(Criterion REQUIRED) + +include(Glob) + +set(_criterion_dir "${CMAKE_CURRENT_LIST_DIR}/Criterion") + +function(add_criterion_executable _target) + set(s "[ \t\r\n]") # space + set(w "[0-9a-zA-Z_]") # word + set(b "[^0-9a-zA-Z_]") # boundary + set(arg "${s}*(${w}+)${s}*") + set(test "(^|${b})Test${s}*\\(${arg},${arg}(,[^\\)]+)?\\)") # Test + set(params "${s}*\\([^\\)]*\\)${s}*") + set(theory "(^|${b})Theory${s}*\\(${params},${arg},${arg}(,[^\\)]+)?\\)") # Theory + set(paramtest "(^|${b})ParameterizedTest${s}*\\([^,]+,${arg},${arg}(,[^\\)]+)?\\)") # ParameterizedTest + + glob(_files "c" ${ARGN}) + + foreach(_file ${_files}) + file(READ "${_file}" _contents) + string(REGEX MATCHALL "${test}" _matches "${_contents}") + + list(APPEND _sources "${_file}") + list(LENGTH _matches _length) + if(_length) + foreach(_match ${_matches}) + string(REGEX REPLACE "${test}" "\\2" _suite "${_match}") + string(REGEX REPLACE "${test}" "\\3" _name "${_match}") + list(APPEND _tests "${_suite}:${_name}") + endforeach() + endif() + + string(REGEX MATCHALL "${theory}" _matches "${_contents}") + list(LENGTH _matches _length) + if(_length) + foreach(_match ${_matches}) + string(REGEX REPLACE "${theory}" "\\2" _suite "${_match}") + string(REGEX REPLACE "${theory}" "\\3" _name "${_match}") + list(APPEND _tests "${_suite}:${_name}") + endforeach() + endif() + + string(REGEX MATCHALL "${paramtest}" _matches "${_contents}") + list(LENGTH _matches _length) + if(_length) + foreach(_match ${_matches}) + string(REGEX REPLACE "${paramtest}" "\\2" _suite "${_match}") + string(REGEX REPLACE "${paramtest}" "\\3" _name "${_match}") + list(APPEND _tests "${_suite}:${_name}") + endforeach() + endif() + endforeach() + + add_executable(${_target} "${_criterion_dir}/src/runner.c" ${_sources}) + target_link_libraries(${_target} Criterion) + + foreach(_entry ${_tests}) + string(REPLACE ":" ";" _entry ${_entry}) + list(GET _entry 0 _suite) + list(GET _entry 1 _name) + + add_test( + NAME "Criterion_${_suite}_${_name}" + COMMAND ${_target} --suite ${_suite} --test ${_name} --cunit=${_suite}-${_name} --quiet) + endforeach() +endfunction() + diff --git a/src/cmake/modules/Criterion/src/runner.c b/src/cmake/modules/Criterion/src/runner.c new file mode 100644 index 0000000..1edc4b9 --- /dev/null +++ b/src/cmake/modules/Criterion/src/runner.c @@ -0,0 +1,474 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#ifdef _WIN32 +#include +#define LF "\r\n" +#define NAME_MAX _MAX_FNAME +#else +#define LF "\n" +#endif + +static const char *suitepat = "*"; +static const char *testpat = "*"; +static char runfn[NAME_MAX + 1] = { 0 }; +static char listfn[NAME_MAX + 1] = { 0 }; +static char stamp[64] = { 0 }; + +static const char +run_hdr[] = + "" LF + "" LF + "" LF + "" LF + " " LF; + +/* TODO: Criterion version number is not available in any of the header files, + but print_version() is. The function prints a message along the lines + of: Tests compiled with Criterion v2.3.2. The version number therefore + can be retrieved by temporarily swapping out stdout for a FILE handle + opened by open_memstream. However, I don't consider the version number + important enough to go through all that trouble. */ +static const char +run_ftr[] = + " File Generated By Criterion - %s " LF + ""; + +static const char +run_result_hdr[] = + " " LF; + +static const char +run_result_ftr[] = + " " LF; + +static const char +run_suite_hdr[] = + " " LF + " " LF + " %s " LF; + +static const char +run_suite_ftr[] = + " " LF + " " LF; + +static const char +run_test_hdr[] = + " " LF; + +static const char +run_test_ftr[] = + " " LF; + +static const char +run_test_ok[] = + " " LF + " %s " LF + " " LF; + +static const char +run_test_nok[] = + " " LF + " %s " LF + " %s " LF + " %u " LF + " %s " LF + " " LF; + +static const char +run_stats[] = + " " LF + " " LF + " Suites " LF + " %zu " LF + " %zu " LF + " - NA - " LF + " %zu " LF + " %zu " LF + " " LF + " " LF + " Test Cases " LF + " %zu " LF + " %zu " LF + " %zu " LF + " %zu " LF + " %zu " LF + " " LF + " " LF + " Assertions " LF + " %zu " LF + " %zu " LF + " %zu " LF + " %zu " LF + " n/a " LF + " " LF + " " LF; + +static const char +list_hdr[] = + "" LF + "" LF + "" LF + "" LF + " " LF; + +/* TODO: Criterion version number not available. See previous comment. */ +static const char +list_ftr[] = + " File Generated By Criterion - %s " LF + ""; + +static const char +list_stats[] = + " " LF + " " LF + " Total Number of Suites " LF + " %zu " LF + " " LF + " " LF + " Total Number of Test Cases " LF + " %zu " LF + " " LF + " " LF; + +static const char +list_suites_hdr[] = + " " LF; + +static const char +list_suites_ftr[] = + " " LF; + +static const char +list_suite_hdr[] = + " " LF + " " LF + " %s " LF + " %s " LF + " %s " LF + " %s " LF + " %zu " LF + " " LF + " " LF; + +static const char +list_suite_ftr[] = + " " LF + " " LF; + +static const char +list_test[] = + " " LF + " %s " LF + " %s " LF + " " LF; + +static void +print_run_test_stats(FILE *file, struct criterion_test_stats *stats) +{ + struct criterion_assert_stats *itr; + + (void)fprintf(file, run_test_hdr); + if (stats->test_status == CR_STATUS_PASSED) { + (void)fprintf(file, run_test_ok, stats->test->name); + } else if (stats->test_status == CR_STATUS_FAILED) { + for (itr = stats->asserts; itr != NULL; itr = itr->next) { + if (!itr->passed) { + (void)fprintf( + file, + run_test_nok, + stats->test->name, + itr->file, + itr->line, + itr->message); + break; + } + } + } + (void)fprintf(file, run_test_ftr); +} + +static void +print_run_suite_stats(FILE *file, struct criterion_suite_stats *stats) +{ + struct criterion_test_stats *itr; + + (void)fprintf(file, run_suite_hdr, stats->suite->name); + for (itr = stats->tests; itr != NULL; itr = itr->next) { + if (itr->test_status != CR_STATUS_SKIPPED) { + print_run_test_stats(file, itr); + } + } + (void)fprintf(file, run_suite_ftr); +} + +static void +print_run_stats(FILE *file, struct criterion_global_stats *stats) +{ + size_t suites_failed = 0; + size_t suites_passed = 0; + struct criterion_suite_stats *itr; + + (void)fprintf(file, run_hdr); + (void)fprintf(file, run_result_hdr); + for (itr = stats->suites; itr != NULL; itr = itr->next) { + if (itr->tests_skipped != itr->nb_tests) { + print_run_suite_stats(file, itr); + if (itr->tests_failed == itr->nb_tests) { + suites_failed++; + } else { + suites_passed++; + } + } + } + (void)fprintf(file, run_result_ftr); + (void)fprintf( + file, + run_stats, + stats->nb_suites, + suites_passed, + suites_failed, + (stats->nb_suites - (suites_passed - suites_failed)), + stats->nb_tests, + (stats->tests_passed + stats->tests_failed + stats->tests_crashed), + stats->tests_passed, + (stats->tests_failed + stats->tests_crashed), + stats->tests_skipped, + stats->nb_asserts, + (stats->asserts_passed + stats->asserts_failed), + stats->asserts_passed, + stats->asserts_failed); + (void)fprintf(file, run_ftr, stamp); +} + +static void +print_list_test_stats(FILE *file, struct criterion_test_stats *stats) +{ + (void)fprintf( + file, + list_test, + stats->test->name, + (stats->test_status == CR_STATUS_SKIPPED ? "No" : "Yes")); +} + +static void +print_list_suite_stats(FILE *file, struct criterion_suite_stats *stats) +{ + struct criterion_test_stats *itr; + + (void)fprintf( + file, + list_suite_hdr, + stats->suite->name, + "No", // + "No", // + (stats->nb_tests == stats->tests_skipped ? "No" : "Yes"), + stats->nb_tests); + for (itr = stats->tests; itr != NULL; itr = itr->next) { + print_list_test_stats(file, itr); + } + (void)fprintf(file, list_suite_ftr); +} + +static void +print_list_stats(FILE *file, struct criterion_global_stats *stats) +{ + struct criterion_suite_stats *itr; + + (void)fprintf(file, list_hdr); + (void)fprintf(file, list_stats, stats->nb_suites, stats->nb_tests); + (void)fprintf(file, list_suites_hdr); + + for (itr = stats->suites; itr != NULL; itr = itr->next) { + print_list_suite_stats(file, itr); + } + + (void)fprintf(file, list_suites_ftr); + (void)fprintf(file, list_ftr, stamp); +} + +static int +patmatch( + const char *pat, + const char *str) +{ + while (*pat) { + if (*pat == '?') { + /* any character will do */ + if (*str++ == 0) { + return 0; + } + pat++; + } else if (*pat == '*') { + /* collapse a sequence of wildcards, requiring as many + characters in str as there are ?s in the sequence */ + while (*pat == '*' || *pat == '?') { + if (*pat == '?' && *str++ == 0) { + return 0; + } + pat++; + } + /* try matching on all positions where str matches pat */ + while (*str) { + if (*str == *pat && patmatch(pat+1, str+1)) { + return 1; + } + str++; + } + return *pat == 0; + } else { + /* only an exact match */ + if (*str++ != *pat++) { + return 0; + } + } + } + + return *str == 0; +} + +/* Criterion actually prescribes */ +ReportHook(POST_ALL)(struct criterion_global_stats *stats) +{ + FILE *runfh, *listfh; + + if (listfn[0] != '\0' && runfn[0] != '\0') { + runfh = NULL; + listfh = NULL; + + if ((runfh = fopen(runfn, "w")) != NULL && + (listfh = fopen(listfn, "w")) != NULL) + { + print_run_stats(runfh, stats); + print_list_stats(listfh, stats); + } else { + (void)fprintf(stderr, "Cannot write results in CUnit format\n"); + } + + if (runfh != NULL) { + (void)fclose(runfh); + } + if (listfh != NULL) { + (void)fclose(listfh); + } + } +} + +#if defined(_WIN32) +__declspec(dllexport) +#endif +int +main(int argc, char *argv[]) +{ + int result = 0; + int argno, cr_argc, sz; + char *pfx, **cr_argv; + const char runfmt[] = "%s-Results.xml"; + const char listfmt[] = "%s-Listing.xml"; + const char stampfmt[] = "%a %b %e %H:%M:%S %Y"; + time_t now; + + /* Before handing over argc and argv over to criterion, go over the list to + extract the custom options. Note that these are meant to be "hidden" */ + cr_argc = 0; + if ((cr_argv = calloc(argc, sizeof(*cr_argv))) == NULL) { + result = 1; + } else { + for (argno = 0; argno < argc; argno++) { + /* FIXME: + Eventually CUnit output format should be supported through an + actual logger implementation, but it will do for now. + See: http://criterion.readthedocs.io/en/master/output.html */ + if (strncmp(argv[argno], "--cunit", 7) == 0) { + if ((pfx = strchr(argv[argno], '=')) != NULL) { + pfx++; + } else { + pfx = "CriterionAutomated"; + } + + sz = snprintf(runfn, sizeof(runfn), runfmt, pfx); + assert(sz > 0 && sz < sizeof(runfn)); + sz = snprintf(listfn, sizeof(listfn), listfmt, pfx); + assert(sz > 0 && sz < sizeof(listfn)); + now = time(NULL); + sz = (int)strftime( + stamp, sizeof(stamp), stampfmt, localtime(&now)); + assert(sz != 0); + } else if (strncmp(argv[argno], "--suite", 7) == 0) { + if ((argno + 1) == argc) { + fprintf(stderr, "--suite requires an argument\n"); + result = 1; + goto bail; + } + suitepat = (const char *)argv[++argno]; + } else if (strncmp(argv[argno], "--test", 6) == 0) { + if ((argno + 1) == argc) { + fprintf(stderr, "--test requires an argument\n"); + result = 1; + goto bail; + } + testpat = (const char *)argv[++argno]; + } else { + cr_argv[cr_argc++] = argv[argno]; + } + } + + /* FIXME: Depending on internal knowledge is not very pretty, but it is + the only way to provide a filter that will work on both *nix + and non-*nix platforms. */ + struct criterion_test_set *tests = criterion_initialize(); + struct criterion_ordered_set_node *suite_itr, *test_itr; + struct criterion_suite_set *suite; + struct criterion_test *test; + + for (suite_itr = tests->suites->first; + suite_itr != NULL; + suite_itr = suite_itr->next) + { + suite = (struct criterion_suite_set *)(suite_itr + 1); + for (test_itr = suite->tests->first; + test_itr != NULL; + test_itr = test_itr->next) + { + test = (struct criterion_test *)(test_itr + 1); + if (!patmatch(suitepat, test->category) || + !patmatch(testpat, test->name)) + { + test->data->disabled = true; + } + } + } + + if (criterion_handle_args(cr_argc, cr_argv, true)) { + result = !criterion_run_all_tests(tests); + } + + criterion_finalize(tests); + +bail: + free(cr_argv); + } + + return result; +} diff --git a/src/cmake/modules/FileIDs.cmake b/src/cmake/modules/FileIDs.cmake new file mode 100644 index 0000000..5c79198 --- /dev/null +++ b/src/cmake/modules/FileIDs.cmake @@ -0,0 +1,146 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# + +# To uniquely identify the origin of every error all source files must be +# assigned a pseudo unique identifier (or module). Because only 32 bits are +# available in the return code (for now) to store the sign bit (1), return +# code (4), line number (8) and file identifier, using a deterministic hash +# will likely lead to collisions. To work around this issue a static map is +# applied, which also ensures that file identifiers are persisted accross +# versions/branches. Of course one could choose to specify the module manually +# with every return, but that is tedious and error prone. + +if(FILE_IDS_INCLUDED) + return() +endif() +set(FILE_IDS_INCLUDED true) + + +# Verify syntax for all .fileids files and ensure no source file id is used +# more than once. +file(GLOB_RECURSE fils__ LIST_DIRECTORIES false "${CMAKE_SOURCE_DIR}/.fileids") + +set(ids__) +foreach(fil__ ${fils__}) + file(READ "${fil__}" lines__) + string(REGEX REPLACE "\n" ";" lines__ ${lines__}) + foreach(line__ ${lines__}) + if("${line__}" MATCHES "^[ \t]*([0-9]+)[ \t]+.*$") + set(id__ "${CMAKE_MATCH_1}") + if(${id__} IN_LIST ids__) + set(dup__ true) + message(STATUS "Id ${id__} used more than once") + else() + list(APPEND ids__ ${id__}) + endif() + elseif(NOT "${line__}" MATCHES "^[ \t]*#") + message(FATAL_ERROR "Syntax error in ${fil__}") + endif() + endforeach() +endforeach() + +if(dup__) + message(FATAL_ERROR "Duplicate ids") +endif() + +function(JOIN lst glue var) + string(REPLACE ";" "${glue}" tmp "${${lst}}") + set(${var} "${tmp}" PARENT_SCOPE) +endfunction() + +function(FILE_ID src var) # private + # .fileids files may reside in subdirectories to keep them together with the + # files they assign an identifier to, much like .gitignore files + set(dir "${CMAKE_SOURCE_DIR}") + set(parts "${src}") + string(REGEX REPLACE "[/\\]+" ";" parts "${parts}") + while(parts) + set(map "${dir}/.fileids") + join(parts "/" fil) + list(APPEND maps "${map}^${fil}") + list(GET parts 0 part) + list(REMOVE_AT parts 0) + set(dir "${dir}/${part}") + endwhile() + + set(id) + foreach(entry ${maps}) + string(REPLACE "^" ";" entry "${entry}") + list(GET entry 0 map) + list(GET entry 1 fil) + if(EXISTS "${map}") + file(READ "${map}" contents) + string(REGEX REPLACE "\n" ";" lines "${contents}") + + foreach(line ${lines}) + if("${line}" MATCHES "^[ \t]*([0-9]+)[ \t]+(.*)$") + set(id "${CMAKE_MATCH_1}") + string(STRIP "${CMAKE_MATCH_2}" expr) + if("${fil}" STREQUAL "${expr}") + set(${var} ${id} PARENT_SCOPE) + return() + endif() + elseif(NOT "${line}" MATCHES "^[ \t]*#") + message(FATAL_ERROR "Syntax error in ${map}") + endif() + endforeach() + endif() + endforeach() +endfunction() + +# Source file properties are visible only to targets added in the same +# directory (CMakeLists.txt). +# https://cmake.org/cmake/help/latest/command/set_source_files_properties.html +function(SET_TARGET_FILE_IDS tgt) + get_target_property(external ${tgt} IMPORTED) + get_target_property(alias ${tgt} ALIASED_TARGET) + string(LENGTH "${CMAKE_SOURCE_DIR}" len) + math(EXPR len "${len} + 1") # strip slash following source dir too + + if((NOT external) AND (NOT alias)) + get_target_property(srcs ${tgt} SOURCES) + get_target_property(src_dir ${tgt} SOURCE_DIR) + foreach(src ${srcs}) + set(id) + if(IS_ABSOLUTE "${src}") + set(fil "${src}") + else() + set(fil "${src_dir}/${src}") + endif() + + get_filename_component(fil "${fil}" ABSOLUTE) + + string(FIND "${fil}" "${CMAKE_SOURCE_DIR}" pos) + if(${pos} EQUAL 0) + string(SUBSTRING "${fil}" ${len} -1 rel) + file_id("${rel}" id) + endif() + + if(id) + if(("${source_file_id_${id}}" STREQUAL "") OR + ("${source_file_id_${id}}" STREQUAL "${rel}")) + set("source_file_id_${id}" "${rel}" CACHE INTERNAL "") + set_source_files_properties( + "${src}" PROPERTIES COMPILE_DEFINITIONS __FILE_ID__=${id}) + else() + message(FATAL_ERROR "Same file id for ${rel} and ${source_file_id_${id}}") + endif() + else() + get_filename_component(ext "${rel}" EXT) + if (NOT "${ext}" MATCHES "\.h*") + message(FATAL_ERROR "No source file id for ${rel}") + endif() + endif() + endforeach() + endif() +endfunction() + diff --git a/src/cmake/modules/FindAbstraction.cmake b/src/cmake/modules/FindAbstraction.cmake new file mode 100644 index 0000000..e22ddae --- /dev/null +++ b/src/cmake/modules/FindAbstraction.cmake @@ -0,0 +1,42 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# + +if (NOT TARGET Abstraction) + add_library(Abstraction INTERFACE) +endif() + +# Link with the platform-specific threads library that find_package provides us +set(CMAKE_THREAD_PREFER_PTHREAD TRUE) +find_package(Threads REQUIRED) +target_link_libraries(Abstraction INTERFACE Threads::Threads) + +if(WIN32) + # Link with WIN32 core-libraries + target_link_libraries(Abstraction INTERFACE wsock32 ws2_32 iphlpapi) + + # Many of the secure versions provided by Microsoft have failure modes + # which are not supported by our abstraction layer, so efforts trying + # to use the _s versions aren't typically the proper solution and C11 + # (which contains most of the secure versions) is 'too new'. So we rely + # on static detection of misuse instead of runtime detection, so all + # these warnings can be disabled on Windows. + add_definitions(-D_CRT_SECURE_NO_WARNINGS) + add_definitions(-D_WINSOCK_DEPRECATED_NO_WARNINGS) #Disabled warnings for deprecated Winsock 2 API calls in general + add_definitions(-D_CRT_NONSTDC_NO_DEPRECATE) #Disabled warnings for deprecated POSIX names +elseif(UNIX AND NOT APPLE) + if("${CMAKE_SYSTEM_PROCESSOR}" STREQUAL "x86_64") + # Shared libs will have this by default. Static libs need it too on x86_64. + set(CMAKE_POSITION_INDEPENDENT_CODE ON) + endif() + find_package(GetTime REQUIRED) + target_link_libraries(Abstraction INTERFACE GetTime) +endif() diff --git a/src/cmake/modules/FindCUnit.cmake b/src/cmake/modules/FindCUnit.cmake new file mode 100644 index 0000000..7a883ea --- /dev/null +++ b/src/cmake/modules/FindCUnit.cmake @@ -0,0 +1,23 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +find_path(CUNIT_INC CUnit/CUnit.h) +find_library(CUNIT_LIB cunit) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(CUnit DEFAULT_MSG CUNIT_LIB CUNIT_INC) + +if(CUNIT_FOUND AND NOT TARGET CUnit) + add_library(CUnit INTERFACE IMPORTED) + + set_property(TARGET CUnit PROPERTY INTERFACE_LINK_LIBRARIES "${CUNIT_LIB}") + set_property(TARGET CUnit PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CUNIT_INC}") +endif() diff --git a/src/cmake/modules/FindCriterion.cmake b/src/cmake/modules/FindCriterion.cmake new file mode 100644 index 0000000..0108766 --- /dev/null +++ b/src/cmake/modules/FindCriterion.cmake @@ -0,0 +1,24 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +find_path(CRITERION_INC criterion/criterion.h PATH_SUFFIXES criterion) +find_library(CRITERION_LIB criterion) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Criterion DEFAULT_MSG CRITERION_LIB CRITERION_INC) + +if (CRITERION_FOUND AND NOT TARGET Criterion) + add_library(Criterion INTERFACE IMPORTED) + + set_property(TARGET Criterion PROPERTY INTERFACE_LINK_LIBRARIES "${CRITERION_LIB}") + set_property(TARGET Criterion PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${CRITERION_INC}") +endif() + diff --git a/src/cmake/modules/FindGetTime.cmake b/src/cmake/modules/FindGetTime.cmake new file mode 100644 index 0000000..566db04 --- /dev/null +++ b/src/cmake/modules/FindGetTime.cmake @@ -0,0 +1,28 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +if (NOT TARGET GetTime) + add_library(GetTime INTERFACE) +endif() + +include(CheckLibraryExists) + +# First check whether libc has clock_gettime +check_library_exists(c clock_gettime "" HAVE_CLOCK_GETTIME_IN_C) + +if(NOT HAVE_CLOCK_GETTIME_IN_C) + # Before glibc 2.17, clock_gettime was in librt + check_library_exists(rt clock_gettime "time.h" HAVE_CLOCK_GETTIME_IN_RT) + if (HAVE_CLOCK_GETTIME_IN_RT) + target_link_libraries(GetTime INTERFACE rt) + endif() +endif() + diff --git a/src/cmake/modules/FindMaven.cmake b/src/cmake/modules/FindMaven.cmake new file mode 100644 index 0000000..1f95cd6 --- /dev/null +++ b/src/cmake/modules/FindMaven.cmake @@ -0,0 +1,74 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +if(DEFINED ENV{M2}) + list(APPEND _mvn_hints "$ENV{M2}") +endif() + +if(DEFINED ENV{M2_HOME}) + list(APPEND _mvn_hints "$ENV{M2_HOME}/bin") +endif() + +# Maven documentation mentions intalling maven under C:\Program Files on +# Windows and under /opt on *NIX platforms +if(WIN32) + set(_program_files_env "ProgramFiles") + set(_program_files $ENV{${_program_files_env}}) + set(_program_files_x86_env "ProgramFiles(x86)") + set(_program_files_x86 $ENV{${_program_files_x86_env}}) + + if(_program_files) + list(APPEND _dirs "${_program_files}") + endif() + + if(_program_files_x86) + list(APPEND _dirs "${_program_files_x86}") + endif() +else() + list(APPEND _dirs "/opt") +endif() + +foreach(_dir ${_dirs}) + file(GLOB _mvn_dirs "${_dir}/apache-maven-*") + foreach(_mvn_dir ${_mvn_dirs}) + if((IS_DIRECTORY "${_mvn_dir}") AND (IS_DIRECTORY "${_mvn_dir}/bin")) + list(APPEND _mvn_paths "${_mvn_dir}/bin") + endif() + endforeach() +endforeach() + +find_program(Maven_EXECUTABLE + NAMES mvn + HINTS ${_mvn_hints} + PATHS ${_mvn_paths}) + +if(Maven_EXECUTABLE) + execute_process(COMMAND ${Maven_EXECUTABLE} -version + RESULT_VARIABLE result + OUTPUT_VARIABLE var + ERROR_VARIABLE var + OUTPUT_STRIP_TRAILING_WHITESPACE + ERROR_STRIP_TRAILING_WHITESPACE) + if(NOT res) + if(var MATCHES "Apache Maven ([0-9]+)\\.([0-9]+)\\.([0-9]+)") + set(Maven_VERSION "${CMAKE_MATCH_1}.${CMAKE_MATCH_2}.${CMAKE_MATCH_3}") + endif() + endif() +endif() + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Maven + FOUND_VAR Maven_FOUND + REQUIRED_VARS Maven_EXECUTABLE + VERSION_VAR Maven_VERSION) + +mark_as_advanced(Maven_FOUND Maven_EXECUTABLE Maven_VERSION) + diff --git a/src/cmake/modules/Glob.cmake b/src/cmake/modules/Glob.cmake new file mode 100644 index 0000000..331b979 --- /dev/null +++ b/src/cmake/modules/Glob.cmake @@ -0,0 +1,38 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +function(glob variable extension) + set(dirname "${CMAKE_CURRENT_SOURCE_DIR}") + + foreach(filename ${ARGN}) + unset(filenames) + + if((NOT IS_ABSOLUTE "${filename}") AND + (EXISTS "${dirname}/${filename}")) + set(filename "${dirname}/${filename}") + endif() + + if(IS_DIRECTORY "${filename}") + file(GLOB_RECURSE filenames "${filename}/*.${extension}") + elseif(EXISTS "${filename}") + if("${filename}" MATCHES "\.${extension}$") + set(filenames "${filename}") + endif() + else() + message(FATAL_ERROR "File ${filename} does not exist") + endif() + + list(APPEND files ${filenames}) + endforeach() + + set(${variable} "${files}" PARENT_SCOPE) +endfunction() + diff --git a/src/cmake/modules/Packaging.cmake b/src/cmake/modules/Packaging.cmake new file mode 100644 index 0000000..9f0e53d --- /dev/null +++ b/src/cmake/modules/Packaging.cmake @@ -0,0 +1,170 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +if(PACKAGING_INCLUDED) + return() +endif() +set(PACKAGING_INCLUDED true) + +include(CMakePackageConfigHelpers) +include(GNUInstallDirs) + +set(PACKAGING_MODULE_DIR "${PROJECT_SOURCE_DIR}/cmake/modules/Packaging") +set(CMAKE_INSTALL_CMAKEDIR "${CMAKE_INSTALL_DATADIR}/${CMAKE_PROJECT_NAME}") + +# Generates Config.cmake. +configure_package_config_file( + "${PACKAGING_MODULE_DIR}/PackageConfig.cmake.in" + "${CMAKE_PROJECT_NAME}Config.cmake" + INSTALL_DESTINATION "${CMAKE_INSTALL_CMAKEDIR}") + +# Generates Version.cmake. +write_basic_package_version_file( + "${CMAKE_PROJECT_NAME}Version.cmake" + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion) + +install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}Config.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}Version.cmake" + DESTINATION "${CMAKE_INSTALL_CMAKEDIR}" COMPONENT dev) + +if(DDSC_SHARED AND ((NOT DEFINED BUILD_SHARED_LIBS) OR BUILD_SHARED_LIBS)) + # Generates Targets.cmake file included by Config.cmake. + # The files are placed in CMakeFiles/Export in the build tree. + install( + EXPORT "${CMAKE_PROJECT_NAME}" + FILE "${CMAKE_PROJECT_NAME}Targets.cmake" + NAMESPACE "${CMAKE_PROJECT_NAME}::" + DESTINATION "${CMAKE_INSTALL_CMAKEDIR}" COMPONENT dev) +endif() + + +set(CPACK_PACKAGE_VERSION_MAJOR ${PROJECT_VERSION_MAJOR}) +set(CPACK_PACKAGE_VERSION_MINOR ${PROJECT_VERSION_MINOR}) +set(CPACK_PACKAGE_VERSION_PATCH ${PROJECT_VERSION_PATCH}) +set(CPACK_PACKAGE_VERSION_TWEAK ${PROJECT_VERSION_TWEAK}) +set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) + +set(VENDOR_INSTALL_ROOT "ADLINK") +set(CPACK_PACKAGE_NAME ${CMAKE_PROJECT_NAME}) +set(CPACK_PACKAGE_VENDOR "ADLINK Technology Inc.") +set(CPACK_PACKAGE_CONTACT "${CMAKE_PROJECT_NAME} core developers ") +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "Leading OMG DDS implementation from ADLINK Technology") +set(CPACK_PACKAGE_ICON "${PACKAGING_MODULE_DIR}/vortex.ico") + +# WiX requires a .txt file extension for CPACK_RESOURCE_FILE_LICENSE +file(COPY "${PROJECT_SOURCE_DIR}/../LICENSE" DESTINATION "${CMAKE_BINARY_DIR}") +file(RENAME "${CMAKE_BINARY_DIR}/LICENSE" "${CMAKE_BINARY_DIR}/license.txt") +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_BINARY_DIR}/license.txt") + +# Packages could be generated on alien systems. e.g. Debian packages could be +# created on Red Hat Enterprise Linux, but since packages also need to be +# verified on the target platform, please refrain from doing so. Another +# reason for building installer packages on the target platform is to ensure +# the binaries are linked to the libc version shipped with that platform. To +# support "generic" Linux distributions, eventually compressed tarballs will +# be shipped. +# +# NOTE: Settings for different platforms are in separate control branches. +# Although that does not make sense from a technical point-of-view, it +# does help to clearify which settings are required for a platform. + +set(CPACK_COMPONENTS_ALL dev lib) +set(CPACK_COMPONENT_LIB_DISPLAY_NAME "${CMAKE_PROJECT_NAME_FULL} library") +set(CPACK_COMPONENT_LIB_DESCRIPTION "Library used to run programs with ${CMAKE_PROJECT_NAME_FULL}") +set(CPACK_COMPONENT_DEV_DISPLAY_NAME "${CMAKE_PROJECT_NAME_FULL} development") +set(CPACK_COMPONENT_DEV_DESCRIPTION "Development files for use with ${CMAKE_PROJECT_NAME_FULL}") + +if(WIN32 AND NOT UNIX) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(__arch "win64") + else() + set(__arch "win32") + endif() + mark_as_advanced(__arch) + + set(CPACK_GENERATOR "WIX;ZIP;${CPACK_GENERATOR}" CACHE STRING "List of package generators") + + set(CPACK_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${CPACK_PACKAGE_VERSION}-${__arch}") + set(CPACK_PACKAGE_INSTALL_DIRECTORY "${VENDOR_INSTALL_ROOT}/${CMAKE_PROJECT_NAME_FULL}") + + set(CPACK_WIX_LIGHT_EXTENSIONS "WixUtilExtension") + set(CPACK_WIX_COMPONENT_INSTALL ON) + set(CPACK_WIX_ROOT_FEATURE_TITLE "${CMAKE_PROJECT_NAME_FULL}") + set(CPACK_WIX_PRODUCT_ICON "${PACKAGING_MODULE_DIR}/vortex.ico") + # Bitmap (.bmp) of size 493x58px + set(CPACK_WIX_UI_BANNER "${PACKAGING_MODULE_DIR}/banner.bmp") + # Bitmap (.bmp) of size 493x312px + set(CPACK_WIX_UI_DIALOG "${PACKAGING_MODULE_DIR}/dialog.png") + set(CPACK_WIX_PROGRAM_MENU_FOLDER "${CPACK_PACKAGE_NAME_FULL}") + set(CPACK_WIX_PATCH_FILE "${PACKAGING_MODULE_DIR}/examples.xml") + set(CPACK_WIX_PROPERTY_ARPHELPLINK "http://www.adlinktech.com/support") + set(CPACK_WIX_PROPERTY_ARPURLINFOABOUT "http://www.adlinktech.com/") + set(CPACK_WIX_PROPERTY_ARPURLUPDATEINFO "http://www.adlinktech.com/") + + # A constant GUID allows installers to replace existing installations that use the same GUID. + set(CPACK_WIX_UPGRADE_GUID "1351F59A-972B-4624-A7F1-439381BFA41D") + + include(InstallRequiredSystemLibraries) +elseif(CMAKE_SYSTEM_NAME MATCHES "Linux") + # CMake prior to v3.6 messes up the name of the packages. >= v3.6 understands CPACK_RPM/DEBIAN__FILE_NAME + cmake_minimum_required(VERSION 3.6) + + set(CPACK_COMPONENTS_GROUPING "IGNORE") + + # FIXME: Requiring lsb_release to be installed may be a viable option. + + if(EXISTS "/etc/redhat-release") + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(__arch "x86_64") + else() + set(__arch "i686") + endif() + + set(CPACK_GENERATOR "RPM;TGZ;${CPACK_GENERATOR}" CACHE STRING "List of package generators") + + set(CPACK_RPM_COMPONENT_INSTALL ON) + # FIXME: The package file name must be updated to include the distribution. + # See Fedora and Red Hat packaging guidelines for details. + set(CPACK_RPM_LIB_PACKAGE_NAME "${CMAKE_PROJECT_NAME_DASHED}") + set(CPACK_RPM_LIB_FILE_NAME "${CPACK_RPM_LIB_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${__arch}.rpm") + set(CPACK_RPM_DEV_PACKAGE_NAME "${CPACK_RPM_LIB_PACKAGE_NAME}-devel") + set(CPACK_RPM_DEV_FILE_NAME "${CPACK_RPM_DEV_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}-${__arch}.rpm") + set(CPACK_RPM_DEV_PACKAGE_REQUIRES "${CPACK_RPM_LIB_PACKAGE_NAME} = ${CPACK_PACKAGE_VERSION}") + elseif(EXISTS "/etc/debian_version") + set(CPACK_DEB_COMPONENT_INSTALL ON) + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(__arch "amd64") + else() + set(__arch "i386") + endif() + + set(CPACK_GENERATOR "DEB;TGZ;${CPACK_GENERATOR}" CACHE STRING "List of package generators") + + string(TOLOWER "${CMAKE_PROJECT_NAME_DASHED}" CPACK_DEBIAN_LIB_PACKAGE_NAME) + set(CPACK_DEBIAN_LIB_FILE_NAME "${CPACK_DEBIAN_LIB_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}_${__arch}.deb") + set(CPACK_DEBIAN_DEV_PACKAGE_DEPENDS "${CPACK_DEBIAN_LIB_PACKAGE_NAME} (= ${CPACK_PACKAGE_VERSION}), libc6 (>= 2.23)") + set(CPACK_DEBIAN_DEV_PACKAGE_NAME "${CPACK_DEBIAN_LIB_PACKAGE_NAME}-dev") + set(CPACK_DEBIAN_DEV_FILE_NAME "${CPACK_DEBIAN_DEV_PACKAGE_NAME}_${CPACK_PACKAGE_VERSION}_${__arch}.deb") + else() + # Generic tgz package + set(CPACK_GENERATOR "TGZ;${CPACK_GENERATOR}" CACHE STRING "List of package generators") + endif() +elseif(CMAKE_SYSTEM_NAME MATCHES "VxWorks") + # FIXME: Support for VxWorks packages must still be implemented (probably + # just a compressed tarball) + message(STATUS "Packaging for VxWorks is unsupported") +endif() + +# This must always be last! +include(CPack) + diff --git a/src/cmake/modules/Packaging/PackageConfig.cmake.in b/src/cmake/modules/Packaging/PackageConfig.cmake.in new file mode 100644 index 0000000..330d39c --- /dev/null +++ b/src/cmake/modules/Packaging/PackageConfig.cmake.in @@ -0,0 +1,16 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +@PACKAGE_INIT@ + +include("${CMAKE_CURRENT_LIST_DIR}/@CMAKE_PROJECT_NAME@Targets.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/idlc/IdlcGenerate.cmake") + diff --git a/src/cmake/modules/Packaging/banner.bmp b/src/cmake/modules/Packaging/banner.bmp new file mode 100644 index 0000000..e795038 Binary files /dev/null and b/src/cmake/modules/Packaging/banner.bmp differ diff --git a/src/cmake/modules/Packaging/dialog.png b/src/cmake/modules/Packaging/dialog.png new file mode 100644 index 0000000..fd8e3ae Binary files /dev/null and b/src/cmake/modules/Packaging/dialog.png differ diff --git a/src/cmake/modules/Packaging/examples.xml b/src/cmake/modules/Packaging/examples.xml new file mode 100644 index 0000000..4ca2dc8 --- /dev/null +++ b/src/cmake/modules/Packaging/examples.xml @@ -0,0 +1,228 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + WIXUI_EXITDIALOGOPTIONALCHECKBOX = 1 and NOT Installed + + + + + + + + + + + + + + + + + + + diff --git a/src/cmake/modules/Packaging/vortex.ico b/src/cmake/modules/Packaging/vortex.ico new file mode 100644 index 0000000..65d2a45 Binary files /dev/null and b/src/cmake/modules/Packaging/vortex.ico differ diff --git a/src/cmake/modules/Platform/VxWorks6.cmake b/src/cmake/modules/Platform/VxWorks6.cmake new file mode 100644 index 0000000..85de58c --- /dev/null +++ b/src/cmake/modules/Platform/VxWorks6.cmake @@ -0,0 +1,213 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# + +# +# CMake Platform file for VxWorks +# +# This file will be used as platform file if CMAKE_SYSTEM_NAME is defined +# as VxWorks in the toolchain file. +# +# Most information is resolved by analyzing the absolute location of the +# compiler on the file system, but can be overridden if required. +# +# Setting CMAKE_SYSTEM_PROCESSOR is mandatory. The variable should be set to +# e.g. ARMARCH* if the target architecture is arm. +# +# NOTES: +# * For now support for VxWorks Diab compiler will NOT be implemented. +# * If certain settings are not explicitly defined, this platform file will +# try to deduce it from the installation path. It will, however, not go out +# of it's way to validate and cross-reference settings. +# +# https://cmake.org/Wiki/CMake_Cross_Compiling +# + +if((NOT "${CMAKE_GENERATOR}" MATCHES "Makefiles") AND + (NOT "${CMAKE_GENERATOR}" MATCHES "Ninja")) + message(FATAL_ERROR "Cross compilation for VxWorks is not supported for " + "${CMAKE_GENERATOR}") +endif() + +set(WIND_PROCESSOR_TYPE_PATTERN ".*(cc|c\\+\\+)(arm|mips|pentium|ppc).*") +set(WIND_HOST_TYPE_PATTERN "^x86-(linux2|win32)$") +set(WIND_PLATFORM_PATTERN "^[0-9\.]+-vxworks-([0-9\.]+)$") + +# Try to deduce the system architecture from either CMAKE_C_COMPILER or +# CMAKE_CXX_COMPILER (one of which must be specified). +# +# Path examples: +# /gnu/4.3.3-vxworks-6.9/x86-linux2/bin +# /gnu/4.1.2-vxworks-6.8/x86-win32/bin +foreach(COMPILER CMAKE_C_COMPILER CMAKE_CXX_COMPILER) + if("${${COMPILER}}" MATCHES "${WIND_PROCESSOR_TYPE_PATTERN}") + string( + REGEX REPLACE + "${WIND_PROCESSOR_TYPE_PATTERN}" "\\2" + PROCESSOR_TYPE + ${${COMPILER}}) + if(NOT WIND_PROCESSOR_TYPE) + set(WIND_PROCESSOR_TYPE ${PROCESSOR_TYPE}) + endif() + + get_filename_component(COMPILER_NAME "${${COMPILER}}" NAME) + if((NOT "${COMPILER_NAME}" STREQUAL "${${COMPILER}}") AND + (NOT "${COMPILER_DIRECTORY}")) + get_filename_component( + COMPILER_PATH "${${COMPILER}}" REALPATH) + get_filename_component( + COMPILER_DIRECTORY "${COMPILER_PATH}" DIRECTORY) + endif() + else() + message(FATAL_ERROR "${COMPILER} did not conform to the expected " + "executable format. i.e. it did not end with " + "arm, mips, pentium, or ppc.") + endif() +endforeach() + + +get_filename_component(C_COMPILER_NAME "${CMAKE_C_COMPILER}" NAME) +get_filename_component(CXX_COMPILER_NAME "${CMAKE_CXX_COMPILER}" NAME) + +# Ideally the location of the compiler should be resolved at this, but invoke +# find_program as a last resort. +if(NOT COMPILER_DIRECTORY) + find_program( + COMPILER_PATH NAMES "${C_COMPILER_NAME}" "${CXX_COMPILER_NAME}") + if(COMPILER_PATH) + get_filename_component( + COMPILER_DIRECTORY "${COMPILER_PATH}" COMPILER_PATH) + else() + # The compiler must be successfully be detected by now. + message(FATAL_ERROR "Could not determine location of compiler path.") + endif() +endif() + + +get_filename_component(basename "${COMPILER_DIRECTORY}" NAME) +get_filename_component(basedir "${COMPILER_DIRECTORY}" DIRECTORY) +while(basename) + if("${basename}" MATCHES "${WIND_PLATFORM_PATTERN}") + string( + REGEX REPLACE "${WIND_PLATFORM_PATTERN}" "\\1" version ${basename}) + if(NOT CMAKE_SYSTEM_VERSION) + set(CMAKE_SYSTEM_VERSION ${version}) + endif() + + # The current base directory may be the WindRiver directory depending + # on wether a "gnu" directory exists or not, but that is evaluated in + # the next iteration. + set(WIND_HOME "${basedir}") + set(WIND_PLATFORM "${basename}") + elseif(CMAKE_SYSTEM_VERSION AND WIND_HOME AND WIND_HOST_TYPE) + # The "gnu" directory may not be part of the path. If it is, strip it. + if("${basename}" STREQUAL "gnu") + set(WIND_HOME "${basedir}") + endif() + break() + elseif("${basename}" MATCHES "${WIND_HOST_TYPE_PATTERN}") + set(WIND_HOST_TYPE "${basename}") + endif() + + get_filename_component(basename ${basedir} NAME) + get_filename_component(basedir ${basedir} DIRECTORY) +endwhile() + + +# VxWorks commands require the WIND_BASE environment variable, so this script +# will support it too. If the environment variable is not set, the necessary +# path information is deduced from the compiler path. +if(NOT WIND_BASE) + set(WIND_BASE $ENV{WIND_BASE}) +endif() + +if(NOT WIND_BASE) + set(WIND_BASE "${WIND_HOME}/vxworks-${CMAKE_SYSTEM_VERSION}") +endif() + +# Verify the location WIND_BASE references actually exists. +if(NOT EXISTS ${WIND_BASE}) + message(FATAL_ERROR "VxWorks base directory ${WIND_BASE} does not exist, " + "please ensure the toolchain information is correct.") +elseif(NOT ENV{WIND_BASE}) + # WIND_BASE environment variable must be exported during generation + # otherwise compiler tests will fail. + set(ENV{WIND_BASE} "${WIND_BASE}") +endif() + + +if(NOT CMAKE_C_COMPILER_VERSION) + execute_process( + COMMAND "${CMAKE_C_COMPILER}" -dumpversion + OUTPUT_VARIABLE CMAKE_C_COMPILER_VERSION) + string(STRIP "${CMAKE_C_COMPILER_VERSION}" CMAKE_C_COMPILER_VERSION) + message(STATUS "VxWorks C compiler version ${CMAKE_C_COMPILER_VERSION}") +endif() + +if(NOT CMAKE_CXX_COMPILER_VERSION) + execute_process( + COMMAND "${CMAKE_CXX_COMPILER}" -dumpversion + OUTPUT_VARIABLE CMAKE_CXX_COMPILER_VERSION) + string(STRIP "${CMAKE_CXX_COMPILER_VERSION}" CMAKE_CXX_COMPILER_VERSION) + message(STATUS "VxWorks CXX compiler version ${CMAKE_C_COMPILER_VERSION}") +endif() + +set(CMAKE_C_COMPILER_ID GNU) +set(CMAKE_CXX_COMPILER_ID GNU) + + +# CMAKE_SOURCE_DIR does not resolve to the actual source directory because +# platform files are processed to early on in the process. +set(ROOT "${CMAKE_MODULE_PATH}/../") + +if(WIN32) + set(CMAKE_C_COMPILER_LAUNCHER "${CMAKE_BINARY_DIR}/launch-c.bat") + set(CMAKE_CXX_COMPILER_LAUNCHER "${CMAKE_BINARY_DIR}/launch-cxx.bat") + configure_file( + "${ROOT}/launch-c.bat.in" "${CMAKE_C_COMPILER_LAUNCHER}" @ONLY) + configure_file( + "${ROOT}/launch-cxx.bat.in" "${CMAKE_CXX_COMPILER_LAUNCHER}" @ONLY) +else() + # Check if a directory like lmapi-* exists (VxWorks 6.9) and append it to + # LD_LIBRARY_PATH. + file(GLOB WIND_LMAPI LIST_DIRECTORIES true "${WIND_HOME}/lmapi-*") + if(WIND_LMAPI) + set(WIND_LMAPI "${WIND_LMAPI}/${WIND_HOST_TYPE}/lib") + endif() + + set(CMAKE_C_COMPILER_LAUNCHER "${CMAKE_BINARY_DIR}/launch-c") + set(CMAKE_CXX_COMPILER_LAUNCHER "${CMAKE_BINARY_DIR}/launch-cxx") + configure_file( + "${ROOT}/launch-c.in" "${CMAKE_C_COMPILER_LAUNCHER}" @ONLY) + configure_file( + "${ROOT}/launch-cxx.in" "${CMAKE_CXX_COMPILER_LAUNCHER}" @ONLY) + execute_process(COMMAND chmod a+rx "${CMAKE_C_COMPILER_LAUNCHER}") + execute_process(COMMAND chmod a+rx "${CMAKE_CXX_COMPILER_LAUNCHER}") +endif() + + +set(WIND_INCLUDE_DIRECTORY "${WIND_BASE}/target/h") + +# Versions before 6.8 have a different path for common libs. +if("${CMAKE_SYSTEM_VERSION}" VERSION_GREATER "6.8") + set(WIND_LIBRARY_DIRECTORY "${WIND_BASE}/target/lib/usr/lib/${WIND_PROCESSOR_TYPE}/${CMAKE_SYSTEM_PROCESSOR}/common") +else() + set(WIND_LIBRARY_DIRECTORY "${WIND_BASE}/target/usr/lib/${WIND_PROCESSOR_TYPE}/${CMAKE_SYSTEM_PROCESSOR}/common") +endif() + +if(NOT EXISTS "${WIND_LIBRARY_DIRECTORY}") + message(FATAL_ERROR "${CMAKE_SYSTEM_PROCESSOR} is not part of the " + "${WIND_PROCESSOR_TYPE} processor family.") +endif() + +include_directories(BEFORE SYSTEM "${WIND_INCLUDE_DIRECTORY}") +link_directories("${WIND_LIBRARY_DIRECTORY}") + diff --git a/src/cmake/scripts/CoverageConvenience.cmake b/src/cmake/scripts/CoverageConvenience.cmake new file mode 100644 index 0000000..6a952b8 --- /dev/null +++ b/src/cmake/scripts/CoverageConvenience.cmake @@ -0,0 +1,134 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# + +# +# This script will run all tests and generates various coverage reports. +# +# Example usage: +# $ cmake -DCOVERAGE_SETTINGS=/CoverageSettings.cmake -P /cmake/scripts/CoverageConvenience.cmake +# If you start the scripts while in then you don't have to provide the COVERAGE_SETTINGS file. +# +cmake_minimum_required(VERSION 3.5) + +# Get Coverage configuration file +if(NOT COVERAGE_SETTINGS) + set(COVERAGE_SETTINGS ${CMAKE_CURRENT_BINARY_DIR}/CoverageSettings.cmake) +endif() +include(${COVERAGE_SETTINGS}) + +message(STATUS "Config file: ${COVERAGE_SETTINGS}") +message(STATUS "Source directory: ${COVERAGE_SOURCE_DIR}") +message(STATUS "Test directory: ${COVERAGE_RUN_DIR}") +message(STATUS "Output directory: ${COVERAGE_OUTPUT_DIR}") + +set(COVERAGE_SCRIPTS_DIR "${COVERAGE_SOURCE_DIR}/cmake/scripts") + +############################################################################### +# +# Detect generators +# +############################################################################### +set(GENERATE_COVERAGE TRUE) +if(GENERATE_COVERAGE) + find_program(GCOV_PATH gcov PARENT_SCOPE) + if(NOT GCOV_PATH) + set(GENERATE_COVERAGE FALSE) + message(STATUS "[SKIP] Coverage generators - gcov (could not find gcov)") + endif() +endif() +if(GENERATE_COVERAGE) + message(STATUS "[ OK ] Coverage generators - gcov") +endif() + +set(GENERATE_COVERAGE_HTML TRUE) +if(GENERATE_COVERAGE_HTML) + find_program(LCOV_PATH lcov PARENT_SCOPE) + if(NOT LCOV_PATH) + set(GENERATE_COVERAGE_HTML FALSE) + message(STATUS "[SKIP] Coverage generators - HTML (could not find lcov)") + endif() +endif() +if(GENERATE_COVERAGE_HTML) + find_program(GENHTML_PATH genhtml PARENT_SCOPE) + if(NOT GENHTML_PATH) + set(GENERATE_COVERAGE_HTML FALSE) + message(STATUS "[SKIP] Coverage generators - HTML (could not find genhtml)") + endif() +endif() +if(GENERATE_COVERAGE_HTML) + message(STATUS "[ OK ] Coverage generators - HTML (lcov and genhtml)") +endif() + +set(GENERATE_COVERAGE_COBERTURA TRUE) +if(GENERATE_COVERAGE_COBERTURA) + find_program(GCOVR_PATH gcovr PARENT_SCOPE) + if(NOT GCOVR_PATH) + set(GENERATE_COVERAGE_COBERTURA FALSE) + message(STATUS "[SKIP] Coverage generators - Cobertura (could not find gcovr)") + endif() +endif() +if(GENERATE_COVERAGE_COBERTURA) + message(STATUS "[ OK ] Coverage generators - Cobertura (gcovr)") +endif() + +if(NOT GENERATE_COVERAGE) + message(FATAL_ERROR "Could not find the main coverage generator 'gcov'") +elseif(NOT GENERATE_COVERAGE_HTML AND NOT GENERATE_COVERAGE_COBERTURA) + message(FATAL_ERROR "Could not find either of the two coverage report generators") +endif() + + + +############################################################################### +# +# Setup environment +# +############################################################################### +message(STATUS "Setup environment") +if(GENERATE_COVERAGE_HTML) + execute_process(COMMAND ${CMAKE_COMMAND} -DCOVERAGE_SETTINGS=${COVERAGE_SETTINGS} -P ${COVERAGE_SCRIPTS_DIR}/CoveragePreHtml.cmake + WORKING_DIRECTORY ${COVERAGE_RUN_DIR}) +endif() +if(GENERATE_COVERAGE_COBERTURA) + execute_process(COMMAND ${CMAKE_COMMAND} -DCOVERAGE_SETTINGS=${COVERAGE_SETTINGS} -P ${COVERAGE_SCRIPTS_DIR}/CoveragePreCobertura.cmake + WORKING_DIRECTORY ${COVERAGE_RUN_DIR}) +endif() + + + +############################################################################### +# +# Generate coverage results by running all the tests +# +############################################################################### +message(STATUS "Run all test to get coverage") +execute_process(COMMAND ctest ${COVERAGE_QUIET_FLAG} -T test + WORKING_DIRECTORY ${COVERAGE_RUN_DIR}) +execute_process(COMMAND ctest ${COVERAGE_QUIET_FLAG} -T coverage + WORKING_DIRECTORY ${COVERAGE_RUN_DIR}) + + + +############################################################################### +# +# Generate coverage reports +# +############################################################################### +if(GENERATE_COVERAGE_HTML) + execute_process(COMMAND ${CMAKE_COMMAND} -DCOVERAGE_SETTINGS=${COVERAGE_SETTINGS} -P ${COVERAGE_SCRIPTS_DIR}/CoveragePostHtml.cmake + WORKING_DIRECTORY ${COVERAGE_RUN_DIR}) +endif() +if(GENERATE_COVERAGE_COBERTURA) + execute_process(COMMAND ${CMAKE_COMMAND} -DCOVERAGE_SETTINGS=${COVERAGE_SETTINGS} -P ${COVERAGE_SCRIPTS_DIR}/CoveragePostCobertura.cmake + WORKING_DIRECTORY ${COVERAGE_RUN_DIR}) +endif() + diff --git a/src/cmake/scripts/CoveragePostCobertura.cmake b/src/cmake/scripts/CoveragePostCobertura.cmake new file mode 100644 index 0000000..c28a1c8 --- /dev/null +++ b/src/cmake/scripts/CoveragePostCobertura.cmake @@ -0,0 +1,52 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# + +# +# This script assumes that all test have been run and gcov results are available. +# It will generate the Cobertura output from the gcov results. +# +# Example usage: +# $ cmake -DCOVERAGE_SETTINGS=/CoverageSettings.cmake -P /cmake/scripts/CoveragePreCobertura.cmake +# $ ctest -T test +# $ ctest -T coverage +# $ ctest -DCOVERAGE_SETTINGS=/CoverageSettings.cmake -P /cmake/scripts/CoveragePostCobertura.cmake +# If you start the scripts while in then you don't have to provide the COVERAGE_SETTINGS file. +# +cmake_minimum_required(VERSION 3.5) + +# Get Coverage configuration file +if(NOT COVERAGE_SETTINGS) + set(COVERAGE_SETTINGS ${CMAKE_CURRENT_BINARY_DIR}/CoverageSettings.cmake) +endif() +include(${COVERAGE_SETTINGS}) + +# Some debug +#message(STATUS "Config file: ${COVERAGE_SETTINGS}") +#message(STATUS "Source directory: ${COVERAGE_SOURCE_DIR}") +#message(STATUS "Test directory: ${COVERAGE_RUN_DIR}") +#message(STATUS "Output directory: ${COVERAGE_OUTPUT_DIR}") + +# Find gcovr to generate Cobertura results +find_program(GCOVR_PATH gcovr PARENT_SCOPE) +if(NOT GCOVR_PATH) + message(FATAL_ERROR "Could not find gcovr to generate Cobertura coverage.") +endif() + +# Create location to put the result file. +file(MAKE_DIRECTORY ${COVERAGE_OUTPUT_DIR}) + +execute_process(COMMAND ${GCOVR_PATH} -x -r ${COVERAGE_SOURCE_DIR} -e ".*/${COVERAGE_EXCLUDE_TESTS}/.*" -e ".*/${COVERAGE_EXCLUDE_EXAMPLES}/.*" -e ".*/${COVERAGE_EXCLUDE_BUILD_SUPPORT}/.*" -o ${COVERAGE_OUTPUT_DIR}/cobertura.xml + WORKING_DIRECTORY ${COVERAGE_RUN_DIR}) + + +message(STATUS "The Cobertura report can be found here: ${COVERAGE_OUTPUT_DIR}/cobertura.xml") + diff --git a/src/cmake/scripts/CoveragePostHtml.cmake b/src/cmake/scripts/CoveragePostHtml.cmake new file mode 100644 index 0000000..f8a5739 --- /dev/null +++ b/src/cmake/scripts/CoveragePostHtml.cmake @@ -0,0 +1,71 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# + +# +# This script assumes that all test have been run and gcov results are available. +# It will generate the HTML output from the gcov results. +# +# Example usage: +# $ cmake -DCOVERAGE_SETTINGS=/CoverageSettings.cmake -P /cmake/scripts/CoveragePreHtml.cmake +# $ ctest -T test +# $ ctest -T coverage +# $ ctest -DCOVERAGE_SETTINGS=/CoverageSettings.cmake -P /cmake/scripts/CoveragePostHtml.cmake +# If you start the scripts while in then you don't have to provide the COVERAGE_SETTINGS file. +# +cmake_minimum_required(VERSION 3.5) + +# Get Coverage configuration file +if(NOT COVERAGE_SETTINGS) + set(COVERAGE_SETTINGS ${CMAKE_CURRENT_BINARY_DIR}/CoverageSettings.cmake) +endif() +include(${COVERAGE_SETTINGS}) + +# Some debug +#message(STATUS "Config file: ${COVERAGE_SETTINGS}") +#message(STATUS "Source directory: ${COVERAGE_SOURCE_DIR}") +#message(STATUS "Test directory: ${COVERAGE_RUN_DIR}") +#message(STATUS "Output directory: ${COVERAGE_OUTPUT_DIR}") + +# Find tools to generate HTML coverage results +find_program(LCOV_PATH lcov PARENT_SCOPE) +if(NOT LCOV_PATH) + message(FATAL_ERROR "Could not find lcov to generate HTML coverage.") +endif() +find_program(GENHTML_PATH genhtml PARENT_SCOPE) +if(NOT GENHTML_PATH) + message(FATAL_ERROR "Could not find genhtml to generate HTML coverage.") +endif() + +# Create location to put the result file. +file(MAKE_DIRECTORY ${COVERAGE_OUTPUT_DIR}) +set(COVERAGE_HTML_OUTPUT "${COVERAGE_OUTPUT_DIR}/html") +file(MAKE_DIRECTORY ${COVERAGE_HTML_OUTPUT}) + +# Setup tmp analysis files +set(COVERAGE_INFO "${COVERAGE_HTML_OUTPUT}/coverage_html.info") +set(COVERAGE_CLEANED "${COVERAGE_INFO}.cleaned") + +# Execute lcov and genhtml commands to get HTML results +execute_process(COMMAND ${LCOV_PATH} ${COVERAGE_QUIET_FLAG} --directory . --capture --output-file ${COVERAGE_INFO} + WORKING_DIRECTORY ${COVERAGE_RUN_DIR}) +execute_process(COMMAND ${LCOV_PATH} ${COVERAGE_QUIET_FLAG} --remove ${COVERAGE_INFO} "${COVERAGE_EXCLUDE_TESTS}/*" "${COVERAGE_EXCLUDE_EXAMPLES}/*" "${COVERAGE_EXCLUDE_BUILD_SUPPORT}/*" "/usr/*" --output-file ${COVERAGE_CLEANED} + WORKING_DIRECTORY ${COVERAGE_RUN_DIR}) +execute_process(COMMAND ${GENHTML_PATH} ${COVERAGE_QUIET_FLAG} -o ${COVERAGE_HTML_OUTPUT} ${COVERAGE_CLEANED} + WORKING_DIRECTORY ${COVERAGE_RUN_DIR}) + +# Remove tmp analysis files +execute_process(COMMAND ${CMAKE_COMMAND} -E remove ${COVERAGE_INFO} ${COVERAGE_CLEANED} + WORKING_DIRECTORY ${COVERAGE_RUN_DIR}) + + +message(STATUS "The HTML coverage report can be found here: ${COVERAGE_HTML_OUTPUT}/index.html") + diff --git a/src/cmake/scripts/CoveragePreCobertura.cmake b/src/cmake/scripts/CoveragePreCobertura.cmake new file mode 100644 index 0000000..f774d3d --- /dev/null +++ b/src/cmake/scripts/CoveragePreCobertura.cmake @@ -0,0 +1,30 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# + +# +# This script assumes that it is called before all tests are run and gcov results are available. +# It can be used to setup the environment needed to get proper Cobertura coverage results. +# +# Example usage: +# $ cmake -DCOVERAGE_SETTINGS=/CoverageSettings.cmake -P /cmake/scripts/CoveragePreCobertura.cmake +# $ ctest -T test +# $ ctest -T coverage +# $ ctest -DCOVERAGE_SETTINGS=/CoverageSettings.cmake -P /cmake/scripts/CoveragePostCobertura.cmake +# If you start the scripts while in then you don't have to provide the COVERAGE_SETTINGS file. +# +cmake_minimum_required(VERSION 3.5) + +# +# Nothing to do really. +# This is just added to provide consistency between Coverage scripts. +# + diff --git a/src/cmake/scripts/CoveragePreHtml.cmake b/src/cmake/scripts/CoveragePreHtml.cmake new file mode 100644 index 0000000..a6deaa7 --- /dev/null +++ b/src/cmake/scripts/CoveragePreHtml.cmake @@ -0,0 +1,52 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# + +# +# This script assumes that it is called before all tests are run and gcov results are available. +# It can be used to setup the environment needed to get proper HTML coverage results. +# +# Example usage: +# $ cmake -DCOVERAGE_SETTINGS=/CoverageSettings.cmake -P /cmake/scripts/CoveragePreHtml.cmake +# $ ctest -T test +# $ ctest -T coverage +# $ ctest -DCOVERAGE_SETTINGS=/CoverageSettings.cmake -P /cmake/scripts/CoveragePostHtml.cmake +# If you start the scripts while in then you don't have to provide the COVERAGE_SETTINGS file. +# +cmake_minimum_required(VERSION 3.5) + +# Get Coverage configuration file +if(NOT COVERAGE_SETTINGS) + set(COVERAGE_SETTINGS ${CMAKE_CURRENT_BINARY_DIR}/CoverageSettings.cmake) +endif() +include(${COVERAGE_SETTINGS}) + +# Some debug +#message(STATUS "Config file: ${COVERAGE_SETTINGS}") +#message(STATUS "Source directory: ${COVERAGE_SOURCE_DIR}") +#message(STATUS "Test directory: ${COVERAGE_RUN_DIR}") +#message(STATUS "Output directory: ${COVERAGE_OUTPUT_DIR}") + +# Find tools to generate HTML coverage results +find_program(LCOV_PATH lcov PARENT_SCOPE) +if(NOT LCOV_PATH) + message(FATAL_ERROR "Could not find lcov to generate HTML coverage.") +endif() +find_program(GENHTML_PATH genhtml PARENT_SCOPE) +if(NOT GENHTML_PATH) + message(FATAL_ERROR "Could not find genhtml to generate HTML coverage.") +endif() + +# Reset LCOV environment +execute_process(COMMAND ${LCOV_PATH} ${COVERAGE_QUIET_FLAG} --directory . --zerocounters + WORKING_DIRECTORY ${COVERAGE_RUN_DIR}) + + diff --git a/src/cmake/vxworks.example.cmake b/src/cmake/vxworks.example.cmake new file mode 100644 index 0000000..9af56e8 --- /dev/null +++ b/src/cmake/vxworks.example.cmake @@ -0,0 +1,33 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +set(CMAKE_SYSTEM_NAME VxWorks) +set(CMAKE_SYSTEM_PROCESSOR PENTIUM4) + +set(WIND_HOME "/path/to/WindRiver") +set(WIND_PROCESSOR_TYPE "pentium") + +# Binaries are named e.g. ccpentium or ccarm +set(CMAKE_C_COMPILER ${WIND_HOME}/gnu/4.3.3-vxworks-6.9/x86-linux2/bin/cc${WIND_PROCESSOR_TYPE}) +set(CMAKE_CXX_COMPILER ${WIND_HOME}/gnu/4.3.3-vxworks-6.9/x86-linux2/bin/c++${WIND_PROCESSOR_TYPE}) +set(CMAKE_AR ${WIND_HOME}/gnu/4.3.3-vxworks-6.9/x86-linux2/bin/ar${WIND_PROCESSOR_TYPE}) + +set(WIND_PROGRAM_PATH ${WIND_HOME}/vxworks-6.9/host/x86-linux2/bin;${WIND_BASE}/gnu/4.3.3-vxworks-6.9/x86-linux2/bin) +set(WIND_LIBRARY_PATH ${WIND_HOME}/target/lib/${WIND_PROCESSOR_TYPE}/${CMAKE_SYSTEM_PROCESSOR}/common) +set(WIND_INCLUDE_PATH ${WIND_HOME}/vxworks-6.9/target/h) + +set(CMAKE_FIND_ROOT_PATH ${WIND_PROGRAM_PATH};${WIND_LIBRARY_PATH};${WIND_INCLUDE_PATH}) + +set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM BOTH) +set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) +set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) + diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt new file mode 100644 index 0000000..4791430 --- /dev/null +++ b/src/core/CMakeLists.txt @@ -0,0 +1,68 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +find_package(Abstraction REQUIRED) + +include (GenerateExportHeader) + +FUNCTION(PREPEND var prefix) + SET(listVar "") + FOREACH(f ${ARGN}) + LIST(APPEND listVar "${prefix}/${f}") + ENDFOREACH(f) + SET(${var} "${listVar}" PARENT_SCOPE) +ENDFUNCTION(PREPEND) + + +option(DDSC_SHARED "Build DDSC as a shared library" ON) + +if(DDSC_SHARED AND ((NOT DEFINED BUILD_SHARED_LIBS) OR BUILD_SHARED_LIBS)) + # BUILD_SHARED_LIBS is set to off by for example VxWorks DKM environment + add_library(ddsc SHARED "") +else() + if(DDSC_SHARED) + message(STATUS "Option DDSC_SHARED ignored. Only static libraries supported on this platform.") + endif() + add_library(ddsc "") +endif() + +include(ddsi/CMakeLists.txt) +include(ddsc/CMakeLists.txt) +include(security/CMakeLists.txt) + +target_link_libraries(ddsc PRIVATE util) +target_link_libraries(ddsc PRIVATE OSAPI) + +# SOVERSION should increase on incompatible ABI change +set_target_properties(ddsc PROPERTIES VERSION ${PROJECT_VERSION} SOVERSION ${PROJECT_VERSION_MAJOR}) + +get_target_property(os_api_src_dir OSAPI SOURCE_DIR) +# We need to expose some of the OS headers as well. +target_include_directories(ddsc + PUBLIC + "$") + +set_target_file_ids(ddsc) + +# Create a pseudo-target that other targets (i.e. examples, tests) can depend +# on and can also be provided as import-target by a package-file when building +# those targets outside the regular Cyclone build-tree (i.e. the installed tree) +add_library(${CMAKE_PROJECT_NAME}::ddsc ALIAS ddsc) + +install( + TARGETS ddsc + EXPORT "${CMAKE_PROJECT_NAME}" + RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" COMPONENT lib + LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT lib + ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}" COMPONENT lib +) + + diff --git a/src/core/ddsc/.fileids b/src/core/ddsc/.fileids new file mode 100644 index 0000000..93578df --- /dev/null +++ b/src/core/ddsc/.fileids @@ -0,0 +1,32 @@ +# ddsc sources +40 src/dds_alloc.c +41 src/dds_coherent.c +42 src/dds_iid.c +43 src/dds_participant.c +44 src/dds_reader.c +45 src/dds_thread.c +46 src/dds_writer.c +47 src/dds_init.c +48 src/dds_publisher.c +49 src/dds_rhc.c +50 src/dds_time.c +51 src/q_osplser.c +52 src/dds_domain.c +53 src/dds_instance.c +54 src/dds_qos.c +55 src/dds_tkmap.c +56 src/dds_entity.c +57 src/dds_key.c +58 src/dds_querycond.c +59 src/dds_topic.c +60 src/dds_err.c +61 src/dds_listener.c +62 src/dds_read.c +63 src/dds_stream.c +64 src/dds_waitset.c +65 src/dds_log.c +66 src/dds_readcond.c +67 src/dds_subscriber.c +68 src/dds_write.c +69 src/dds_report.c +70 src/dds_builtin.c diff --git a/src/core/ddsc/CMakeLists.txt b/src/core/ddsc/CMakeLists.txt new file mode 100644 index 0000000..7c2b729 --- /dev/null +++ b/src/core/ddsc/CMakeLists.txt @@ -0,0 +1,146 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +PREPEND(srcs_ddsc "${CMAKE_CURRENT_LIST_DIR}/src" + dds_alloc.c + dds_builtin.c + dds_coherent.c + dds_iid.c + dds_participant.c + dds_reader.c + dds_writer.c + dds_init.c + dds_publisher.c + dds_rhc.c + dds_time.c + q_osplser.c + dds_domain.c + dds_instance.c + dds_qos.c + dds_tkmap.c + dds_entity.c + dds_key.c + dds_querycond.c + dds_topic.c + dds_report.c + dds_err.c + dds_listener.c + dds_read.c + dds_stream.c + dds_waitset.c + dds_log.c + dds_readcond.c + dds_subscriber.c + dds_write.c +) + +PREPEND(hdrs_public_ddsc "$$" + dds.h + dds_public_error.h + dds_public_impl.h + dds_public_listener.h + dds_public_log.h + dds_public_qos.h + dds_public_status.h + dds_public_stream.h + dds_public_time.h +) + +PREPEND(hdrs_private_ddsc "${CMAKE_CURRENT_LIST_DIR}/src" + dds__alloc.h + dds__builtin.h + dds__domain.h + dds__entity.h + dds__iid.h + dds__init.h + dds__key.h + dds__listener.h + dds__participant.h + dds__publisher.h + dds__qos.h + dds__querycond.h + dds__readcond.h + dds__reader.h + dds__report.h + dds__rhc.h + dds__stream.h + dds__subscriber.h + dds__tkmap.h + dds__topic.h + dds__types.h + dds__write.h + dds__writer.h + q__osplser.h +) + +configure_file( + "${CMAKE_CURRENT_LIST_DIR}/cmake/ddsc_project.h.in" + "include/ddsc/ddsc_project.h") + +generate_export_header( + ddsc + BASE_NAME DDS + EXPORT_FILE_NAME "${CMAKE_CURRENT_BINARY_DIR}/include/ddsc/dds_export.h" +) + +target_include_directories(ddsc + PUBLIC + "$") + +# Generate builtin-topic sources +set(IDLC_ARGS "-dll" "FOO,ddsc/dds_export.h") +idlc_generate(BuiltinTypes ddsc/src/dds_dcps_builtintopics.idl ddsc/src/dds_builtinTopics.idl) +set(IDLC_ARGS) +target_link_libraries(ddsc PRIVATE BuiltinTypes) + +target_include_directories(ddsc + PUBLIC + "$") + +target_sources(ddsc + PRIVATE + ${srcs_ddsc} + ${hdrs_private_ddsc} + "include/ddsc/ddsc_project.h" + PUBLIC + ${hdrs_public_ddsc} + "$$/ddsc/dds_export.h" +) + +target_include_directories(ddsc + PUBLIC + "$" + "$" + PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/src") + +install( + DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/include/ddsc" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + COMPONENT dev) + +install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/include/ddsc/dds_export.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ddsc" + COMPONENT dev) + +install( + FILES + "${CMAKE_CURRENT_BINARY_DIR}/dds_dcps_builtintopics.h" + "${CMAKE_CURRENT_BINARY_DIR}/dds_builtinTopics.h" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ddsc" + COMPONENT dev) + +# TODO: improve test inclusion. +if((BUILD_TESTING) AND ((NOT DEFINED MSVC_VERSION) OR (MSVC_VERSION GREATER "1800"))) + add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/tests") +endif() + diff --git a/src/core/ddsc/cmake/ddsc_project.h.in b/src/core/ddsc/cmake/ddsc_project.h.in new file mode 100644 index 0000000..6eb3884 --- /dev/null +++ b/src/core/ddsc/cmake/ddsc_project.h.in @@ -0,0 +1,25 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef DDSC_PROJECT_H +#define DDSC_PROJECT_H + +#define DDSC_VERSION "@CycloneDDS_VERSION@" +#define DDSC_VERSION_MAJOR @CycloneDDS_VERSION_MAJOR@ +#define DDSC_VERSION_MINOR @CycloneDDS_VERSION_MINOR@ +#define DDSC_VERSION_PATCH @CycloneDDS_VERSION_PATCH@ +#define DDSC_VERSION_TWEAK @CycloneDDS_VERSION_TWEAK@ +#define DDSC_PROJECT_NAME_NOSPACE_CAPS "@CMAKE_PROJECT_NAME_CAPS@" +#define DDSC_PROJECT_NAME_NOSPACE_SMALL "@CMAKE_PROJECT_NAME_SMALL@" +#define DDSC_PROJECT_NAME_NOSPACE "@CMAKE_PROJECT_NAME@" +#define DDSC_PROJECT_NAME "@CMAKE_PROJECT_NAME@" + +#endif /* DDSC_PROJECT_H */ diff --git a/src/core/ddsc/include/ddsc/dds.h b/src/core/ddsc/include/ddsc/dds.h new file mode 100644 index 0000000..813399f --- /dev/null +++ b/src/core/ddsc/include/ddsc/dds.h @@ -0,0 +1,3144 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef DDS_H +#define DDS_H + +/** @file + * + * @brief C DDS header + */ + +#if defined (__cplusplus) +#define restrict +#endif + +#include "os/os_public.h" +#include "ddsc/dds_export.h" + +/* TODO: Move to appropriate location */ +/** + * Return code indicating success (DDS_RETCODE_OK) or failure. If a given + * operation failed the value will be a unique error code and dds_err_nr() must + * be used to extract the DDS_RETCODE_* value. + */ +typedef _Return_type_success_(return >= 0) int32_t dds_return_t; +/** + * Handle to an entity. A valid entity handle will always have a positive + * integer value. Should the value be negative, the value represents a unique + * error code. dds_err_nr() can be used to extract the DDS_RETCODE_* value. + */ +typedef _Return_type_success_(return > 0) int32_t dds_entity_t; + +/* Sub components */ + +#include "ddsc/dds_public_stream.h" +#include "ddsc/dds_public_impl.h" +#include "ddsc/dds_public_alloc.h" +#include "ddsc/dds_public_time.h" +#include "ddsc/dds_public_qos.h" +#include "ddsc/dds_public_error.h" +#include "ddsc/dds_public_status.h" +#include "ddsc/dds_public_listener.h" +#include "ddsc/dds_public_log.h" +#include "dds_dcps_builtintopics.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +/** + * @brief Returns the default domain identifier. + * + * The default domain identifier can be configured in the configuration file + * or be set through an evironment variable ({DDSC_PROJECT_NAME_NOSPACE_CAPS}_DOMAIN). + * + * @returns Default domain identifier + */ +DDS_EXPORT dds_domainid_t dds_domain_default (void); + +/* @defgroup builtintopic_constants Convenience constants for referring to builtin topics + * + * These constants can be used in place of an actual dds_topic_t, when creating + * readers or writers for builtin-topics. + * + * @{ + */ +extern DDS_EXPORT const dds_entity_t DDS_BUILTIN_TOPIC_DCPSPARTICIPANT; +extern DDS_EXPORT const dds_entity_t DDS_BUILTIN_TOPIC_CMPARTICIPANT; +extern DDS_EXPORT const dds_entity_t DDS_BUILTIN_TOPIC_DCPSTYPE; +extern DDS_EXPORT const dds_entity_t DDS_BUILTIN_TOPIC_DCPSTOPIC; +extern DDS_EXPORT const dds_entity_t DDS_BUILTIN_TOPIC_DCPSPUBLICATION; +extern DDS_EXPORT const dds_entity_t DDS_BUILTIN_TOPIC_CMPUBLISHER; +extern DDS_EXPORT const dds_entity_t DDS_BUILTIN_TOPIC_DCPSSUBSCRIPTION; +extern DDS_EXPORT const dds_entity_t DDS_BUILTIN_TOPIC_CMSUBSCRIBER; +extern DDS_EXPORT const dds_entity_t DDS_BUILTIN_TOPIC_CMDATAWRITER; +extern DDS_EXPORT const dds_entity_t DDS_BUILTIN_TOPIC_CMDATAREADER; +/** @}*/ + +/** @name Communication Status definitions + @{**/ +/** Another topic exists with the same name but with different characteristics. */ +#define DDS_INCONSISTENT_TOPIC_STATUS 1u +/** The deadline that the writer has committed through its deadline QoS policy was not respected for a specific instance. */ +#define DDS_OFFERED_DEADLINE_MISSED_STATUS 2u +/** The deadline that the reader was expecting through its deadline QoS policy was not respected for a specific instance. */ +#define DDS_REQUESTED_DEADLINE_MISSED_STATUS 4u +/** A QoS policy setting was incompatible with what was requested. */ +#define DDS_OFFERED_INCOMPATIBLE_QOS_STATUS 32u +/** A QoS policy setting was incompatible with what is offered. */ +#define DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS 64u +/** A sample has been lost (never received). */ +#define DDS_SAMPLE_LOST_STATUS 128u +/** A (received) sample has been rejected. */ +#define DDS_SAMPLE_REJECTED_STATUS 256u +/** New information is available. */ +#define DDS_DATA_ON_READERS_STATUS 512u +/** New information is available. */ +#define DDS_DATA_AVAILABLE_STATUS 1024u +/** The liveliness that the DDS_DataWriter has committed through its liveliness QoS policy was not respected; thus readers will consider the writer as no longer "alive". */ +#define DDS_LIVELINESS_LOST_STATUS 2048u +/** The liveliness of one or more writers, that were writing instances read through the readers has changed. Some writers have become "alive" or "not alive". */ +#define DDS_LIVELINESS_CHANGED_STATUS 4096u +/** The writer has found a reader that matches the topic and has a compatible QoS. */ +#define DDS_PUBLICATION_MATCHED_STATUS 8192u +/** The reader has found a writer that matches the topic and has a compatible QoS. */ +#define DDS_SUBSCRIPTION_MATCHED_STATUS 16384u +/** @}*/ + +/** Read state for a data value */ +typedef enum dds_sample_state +{ + DDS_SST_READ = DDS_READ_SAMPLE_STATE, /**0 + * A valid publisher handle. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +/* TODO: Link to generic dds entity relations documentation. */ +_Pre_satisfies_(((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER)) +DDS_EXPORT dds_entity_t +dds_get_publisher( + _In_ dds_entity_t writer); + +/** + * @brief Get entity subscriber. + * + * This operation returns the subscriber to which the given entity belongs. + * For instance, it will return the Subscriber that was used when + * creating a DataReader (when that DataReader was provided here). + * + * @param[in] entity Entity from which to get its subscriber. + * + * @returns A valid subscriber handle or an error code. + * + * @retval >0 + * A valid subscriber handle. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +/* TODO: Link to generic dds entity relations documentation. */ +_Pre_satisfies_(((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY) ) +DDS_EXPORT dds_entity_t +dds_get_subscriber( + _In_ dds_entity_t entity); + +/** + * @brief Get entity datareader. + * + * This operation returns the datareader to which the given entity belongs. + * For instance, it will return the DataReader that was used when + * creating a ReadCondition (when that ReadCondition was provided here). + * + * @param[in] entity Entity from which to get its datareader. + * + * @returns A valid reader handle or an error code. + * + * @retval >0 + * A valid reader handle. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +/* TODO: Link to generic dds entity relations documentation. */ +_Pre_satisfies_(((condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY) ) +DDS_EXPORT dds_entity_t +dds_get_datareader( + _In_ dds_entity_t condition); + +/** + * @brief Get the mask of a condition. + * + * This operation returns the mask that was used to create the given + * condition. + * + * @param[in] condition Read or Query condition that has a mask. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Success (given mask is set). + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The mask arg is NULL. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY) ) +DDS_EXPORT _Check_return_ dds_return_t +dds_get_mask( + _In_ dds_entity_t condition, + _Out_ uint32_t *mask); + +/** + * @brief Returns the instance handle that represents the entity. + * + * @param[in] entity Entity of which to get the instance handle. + * @param[out] ihdl Pointer to dds_instance_handle_t. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Success. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + */ +/* TODO: Check list of return codes is complete. */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_get_instance_handle( + _In_ dds_entity_t entity, + _Out_ dds_instance_handle_t *ihdl); + +/* + All entities have a set of "status conditions" (following the DCPS + spec), read peeks, take reads & resets (analogously to read & take + operations on reader). The "mask" allows operating only on a subset + of the statuses. Enabled status analogously to DCPS spec. +*/ + +/** + * @brief Read the status set for the entity + * + * This operation reads the status(es) set for the entity based on + * the enabled status and mask set. It does not clear the read status(es). + * + * @param[in] entity Entity on which the status has to be read. + * @param[out] status Returns the status set on the entity, based on the enabled status. + * @param[in] mask Filter the status condition to be read (can be NULL). + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Success. + * @retval DDS_RETCODE_BAD_PARAMETER + * The entity parameter is not a valid parameter. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_read_status( + _In_ dds_entity_t entity, + _Out_ uint32_t *status, + _In_ uint32_t mask); + +/** + * @brief Read the status set for the entity + * + * This operation reads the status(es) set for the entity based on the enabled + * status and mask set. It clears the status set after reading. + * + * @param[in] entity Entity on which the status has to be read. + * @param[out] status Returns the status set on the entity, based on the enabled status. + * @param[in] mask Filter the status condition to be read (can be NULL). + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Success. + * @retval DDS_RETCODE_BAD_PARAMETER + * The entity parameter is not a valid parameter. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_take_status( + _In_ dds_entity_t entity, + _Out_ uint32_t *status, + _In_ uint32_t mask); + +/** + * @brief Get changed status(es) + * + * This operation returns the status changes since they were last read. + * + * @param[in] entity Entity on which the statuses are read. + * @param[out] status Returns the current set of triggered statuses. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Success. + * @retval DDS_RETCODE_BAD_PARAMETER + * The entity parameter is not a valid parameter. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Must_inspect_result_ dds_return_t +dds_get_status_changes( + _In_ dds_entity_t entity, + _Out_ uint32_t *status); + +/** + * @brief Get enabled status on entity + * + * This operation returns the status enabled on the entity + * + * @param[in] entity Entity to get the status. + * @param[out] status Status set on the entity. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Success. + * @retval DDS_RETCODE_BAD_PARAMETER + * The entity parameter is not a valid parameter. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_get_enabled_status( + _In_ dds_entity_t entity, + _Out_ uint32_t *status); + +/** + * @brief Set status enabled on entity + * + * This operation enables the status(es) based on the mask set + * + * @param[in] entity Entity to enable the status. + * @param[in] mask Status value that indicates the status to be enabled. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Success. + * @retval DDS_RETCODE_BAD_PARAMETER + * The entity parameter is not a valid parameter. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT dds_return_t +dds_set_enabled_status( + _In_ dds_entity_t entity, + _In_ uint32_t mask); + +/* + Almost all entities have get/set qos operations defined on them, + again following the DCPS spec. But unlike the DCPS spec, the + "present" field in qos_t allows one to initialize just the one QoS + one wants to set & pass it to set_qos. +*/ + +/** + * @brief Get entity QoS policies. + * + * This operation allows access to the existing set of QoS policies + * for the entity. + * + * @param[in] entity Entity on which to get qos. + * @param[out] qos Pointer to the qos structure that returns the set policies. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The existing set of QoS policy values applied to the entity + * has successfully been copied into the specified qos parameter. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The qos parameter is NULL. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +/* TODO: Link to generic QoS information documentation. */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_get_qos( + _In_ dds_entity_t entity, + _Out_ dds_qos_t *qos); + +/** + * @brief Set entity QoS policies. + * + * This operation replaces the existing set of Qos Policy settings for an + * entity. The parameter qos must contain the struct with the QosPolicy + * settings which is checked for self-consistency. + * + * The set of QosPolicy settings specified by the qos parameter are applied on + * top of the existing QoS, replacing the values of any policies previously set + * (provided, the operation returned DDS_RETCODE_OK). + * + * Not all policies are changeable when the entity is enabled. + * + * @note Currently only Latency Budget and Ownership Strength are changeable QoS + * that can be set. + * + * @param[in] entity Entity from which to get qos. + * @param[in] qos Pointer to the qos structure that provides the policies. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The new QoS policies are set. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The qos parameter is NULL. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_IMMUTABLE_POLICY + * The entity is enabled and one or more of the policies of the QoS + * are immutable. + * @retval DDS_RETCODE_INCONSISTENT_POLICY + * A few policies within the QoS are not consistent with each other. + */ +/* TODO: Link to generic QoS information documentation. */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_set_qos( + _In_ dds_entity_t entity, + _In_ const dds_qos_t * qos); + +/* + Get or set listener associated with an entity, type of listener + provided much match type of entity. +*/ + +/** + * @brief Get entity listeners. + * + * This operation allows access to the existing listeners attached to + * the entity. + * + * @param[in] entity Entity on which to get the listeners. + * @param[out] listener Pointer to the listener structure that returns the + * set of listener callbacks. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The listeners of to the entity have been successfully been + * copied into the specified listener parameter. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The listener parameter is NULL. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +/* TODO: Link to (generic) Listener and status information. */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_get_listener( + _In_ dds_entity_t entity, + _Out_ dds_listener_t * listener); + +/** + * @brief Set entity listeners. + * + * This operation attaches a dds_listener_t to the dds_entity_t. Only one + * Listener can be attached to each Entity. If a Listener was already + * attached, this operation will replace it with the new one. In other + * words, all related callbacks are replaced (possibly with NULL). + * + * When listener parameter is NULL, all listener callbacks that were possibly + * set on the Entity will be removed. + * + * @note Not all listener callbacks are related to all entities. + * + * Communication Status
+ * For each communication status, the StatusChangedFlag flag is initially set to + * FALSE. It becomes TRUE whenever that plain communication status changes. For + * each plain communication status activated in the mask, the associated + * Listener callback is invoked and the communication status is reset + * to FALSE, as the listener implicitly accesses the status which is passed as a + * parameter to that operation. + * The status is reset prior to calling the listener, so if the application calls + * the get_ from inside the listener it will see the + * status already reset. + * + * Status Propagation
+ * In case a related callback within the Listener is not set, the Listener of + * the Parent entity is called recursively, until a Listener with the appropriate + * callback set has been found and called. This allows the application to set + * (for instance) a default behaviour in the Listener of the containing Publisher + * and a DataWriter specific behaviour when needed. In case the callback is not + * set in the Publishers' Listener either, the communication status will be + * propagated to the Listener of the DomainParticipant of the containing + * DomainParticipant. In case the callback is not set in the DomainParticipants' + * Listener either, the Communication Status flag will be set, resulting in a + * possible WaitSet trigger. + * + * @param[in] entity Entity on which to get the listeners. + * @param[in] listener Pointer to the listener structure that contains the + * set of listener callbacks (maybe NULL). + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The listeners of to the entity have been successfully been + * copied into the specified listener parameter. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +/* TODO: Link to (generic) Listener and status information. */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_set_listener( + _In_ dds_entity_t entity, + _In_opt_ const dds_listener_t * listener); + +/* + Creation functions for various entities. Creating a subscriber or + publisher is optional: if one creates a reader as a descendant of a + participant, it is as if a subscriber is created specially for + that reader. + + QoS default values are those of the DDS specification, but the + inheritance rules are different: + + * publishers and subscribers inherit from the participant QoS + * readers and writers always inherit from the topic QoS + * the QoS's present in the "qos" parameter override the inherited values +*/ + +/** + * @brief Creates a new instance of a DDS participant in a domain + * + * If domain is set (not DDS_DOMAIN_DEFAULT) then it must match if the domain has also + * been configured or an error status will be returned. + * Currently only a single domain can be configured by providing configuration file. + * If no configuration file exists, the default domain is configured as 0. + * + * + * @param[in] domain The domain in which to create the participant (can be DDS_DOMAIN_DEFAULT). Valid values for domain id are between 0 and 230. DDS_DOMAIN_DEFAULT is for using the domain in the configuration. + * @param[in] qos The QoS to set on the new participant (can be NULL). + * @param[in] listener Any listener functions associated with the new participant (can be NULL). + + * @returns A valid participant handle or an error code. + * + * @retval >0 + * A valid participant handle. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + */ +DDS_EXPORT _Must_inspect_result_ dds_entity_t +dds_create_participant( + _In_ const dds_domainid_t domain, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener); + +/** + * @brief Get entity parent. + * + * This operation returns the parent to which the given entity belongs. + * For instance, it will return the Participant that was used when + * creating a Publisher (when that Publisher was provided here). + * + * When a reader or a writer are created with a partition, then a + * subscriber or publisher respectively are created implicitly. These + * implicit subscribers or publishers will be deleted automatically + * when the reader or writer is deleted. However, when this function + * returns such an implicit entity, it is from there on out considered + * 'explicit'. This means that it isn't deleted automatically anymore. + * The application should explicitly call dds_delete on those entities + * now (or delete the parent participant which will delete all entities + * within its hierarchy). + * + * @param[in] entity Entity from which to get its parent. + * + * @returns A valid entity handle or an error code. + * + * @retval >0 + * A valid entity handle. + * @retval DDS_ENTITY_NIL + * Called with a participant. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +/* TODO: Link to generic dds entity relations documentation. */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_entity_t +dds_get_parent( + _In_ dds_entity_t entity); + +/** + * @brief Get entity participant. + * + * This operation returns the participant to which the given entity belongs. + * For instance, it will return the Participant that was used when + * creating a Publisher that was used to create a DataWriter (when that + * DataWriter was provided here). + * + * TODO: Link to generic dds entity relations documentation. + * + * @param[in] entity Entity from which to get its participant. + * + * @returns A valid entity or an error code. + * + * @retval >0 + * A valid participant handle. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_entity_t +dds_get_participant ( + _In_ dds_entity_t entity); + +/** + * @brief Get entity children. + * + * This operation returns the children that the entity contains. + * For instance, it will return all the Topics, Publishers and Subscribers + * of the Participant that was used to create those entities (when that + * Participant is provided here). + * + * This functions takes a pre-allocated list to put the children in and + * will return the number of found children. It is possible that the given + * size of the list is not the same as the number of found children. If + * less children are found, then the last few entries in the list are + * untouched. When more children are found, then only 'size' number of + * entries are inserted into the list, but still complete count of the + * found children is returned. Which children are returned in the latter + * case is undefined. + * + * When supplying NULL as list and 0 as size, you can use this to acquire + * the number of children without having to pre-allocate a list. + * + * When a reader or a writer are created with a partition, then a + * subscriber or publisher respectively are created implicitly. These + * implicit subscribers or publishers will be deleted automatically + * when the reader or writer is deleted. However, when this function + * returns such an implicit entity, it is from there on out considered + * 'explicit'. This means that it isn't deleted automatically anymore. + * The application should explicitly call dds_delete on those entities + * now (or delete the parent participant which will delete all entities + * within its hierarchy). + * + * @param[in] entity Entity from which to get its children. + * @param[out] children Pre-allocated array to contain the found children. + * @param[in] size Size of the pre-allocated children's list. + * + * @returns Number of children or an error code. + * + * @retval >=0 + * Number of childer found children (can be larger than 'size'). + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The children parameter is NULL, while a size is provided. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +/* TODO: Link to generic dds entity relations documentation. */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_get_children( + _In_ dds_entity_t entity, + _Out_opt_ dds_entity_t *children, + _In_ size_t size); + +/** + * @brief Get the domain id to which this entity is attached. + * + * When creating a participant entity, it is attached to a certain domain. + * All the children (like Publishers) and childrens' children (like + * DataReaders), etc are also attached to that domain. + * + * This function will return the original domain ID when called on + * any of the entities within that hierarchy. + * + * @param[in] entity Entity from which to get its children. + * @param[out] id Pointer to put the domain ID in. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Domain ID was returned. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The id parameter is NULL. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_get_domainid( + _In_ dds_entity_t entity, + _Out_ dds_domainid_t *id); + +/** + * @brief Get participants of a domain. + * + * This operation acquires the participants created on a domain and returns + * the number of found participants. + * + * This function takes a domain id with the size of pre-allocated participant's + * list in and will return the number of found participants. It is possible that + * the given size of the list is not the same as the number of found participants. + * If less participants are found, then the last few entries in an array stay + * untouched. If more participants are found and the array is too small, then the + * participants returned are undefined. + * + * @param[in] domain_id The domain id. + * @param[out] participants The participant for domain. + * @param[in] size Size of the pre-allocated participant's list. + * + * @returns Number of participants found or and error code. + * + * @retval >0 + * Number of participants found. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The participant parameter is NULL, while a size is provided. + */ +DDS_EXPORT _Check_return_ dds_return_t +dds_lookup_participant( + _In_ dds_domainid_t domain_id, + _Out_opt_ dds_entity_t *participants, + _In_ size_t size); + +/** + * @brief Creates a new topic. + * + * The type name for the topic is taken from the generated descriptor. Topic + * matching is done on a combination of topic name and type name. + * + * @param[in] participant Participant on which to create the topic. + * @param[in] descriptor An IDL generated topic descriptor. + * @param[in] name Name of the topic. + * @param[in] qos QoS to set on the new topic (can be NULL). + * @param[in] listener Any listener functions associated with the new topic (can be NULL). + * + * @returns A valid topic handle or an error code. + * + * @retval >=0 + * A valid topic handle. + * @retval DDS_RETCODE_BAD_PARAMETER + * Either participant, descriptor, name or qos is invalid. + */ +/* TODO: Check list of retcodes is complete. */ +_Pre_satisfies_((participant & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) +DDS_EXPORT dds_entity_t +dds_create_topic( + _In_ dds_entity_t participant, + _In_ const dds_topic_descriptor_t *descriptor, + _In_z_ const char *name, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener); + +/** + * @brief Finds a named topic. + * + * The returned topic should be released with dds_delete. + * + * @param[in] participant The participant on which to find the topic. + * @param[in] name The name of the topic to find. + * + * @returns A valid topic handle or an error code. + * + * @retval >0 + * A valid topic handle. + * @retval DDS_RETCODE_BAD_PARAMETER + * Participant was invalid. + */ +/* TODO: Check list of retcodes is complete. */ +_Pre_satisfies_((participant & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) +DDS_EXPORT dds_entity_t +dds_find_topic( + _In_ dds_entity_t participant, + _In_z_ const char *name); + +/** + * @brief Returns the name of a given topic. + * + * @param[in] topic The topic. + * @param[out] name Buffer to write the topic name to. + * @param[in] size Number of bytes available in the buffer. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Success. + */ +/* TODO: do we need a convenience version as well that allocates and add a _s suffix to this one? */ +/* TODO: Check annotation. Could be _Out_writes_to_(size, return + 1) as well. */ +_Pre_satisfies_((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC) +DDS_EXPORT dds_return_t +dds_get_name( + _In_ dds_entity_t topic, + _Out_writes_z_(size) char *name, + _In_ size_t size); + +/** + * @brief Returns the type name of a given topic. + * + * @param[in] topic The topic. + * @param[out] name Buffer to write the topic type name to. + * @param[in] size Number of bytes available in the buffer. + * + * @returns A dds_return_t indicating success or failure. + * + * @return DDS_RETCODE_OK + * Success. + */ +_Pre_satisfies_((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC) +DDS_EXPORT dds_return_t +dds_get_type_name( + _In_ dds_entity_t topic, + _Out_writes_z_(size) char *name, + _In_ size_t size); + +/** Topic filter function */ +typedef bool (*dds_topic_filter_fn) (const void * sample); + +/** + * @brief Sets a filter on a topic. + * + * @param[in] topic The topic on which the content filter is set. + * @param[in] filter The filter function used to filter topic samples. + */ +_Pre_satisfies_((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC) +DDS_EXPORT void +dds_topic_set_filter( + dds_entity_t topic, + dds_topic_filter_fn filter); + +/** + * @brief Gets the filter for a topic. + * + * @param[in] topic The topic from which to get the filter. + * + * @returns The topic filter. + */ +_Pre_satisfies_((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC) +DDS_EXPORT dds_topic_filter_fn +dds_topic_get_filter( + dds_entity_t topic); + +/** + * @brief Creates a new instance of a DDS subscriber + * + * @param[in] participant The participant on which the subscriber is being created. + * @param[in] qos The QoS to set on the new subscriber (can be NULL). + * @param[in] listener Any listener functions associated with the new subscriber (can be NULL). + * + * @returns A valid subscriber handle or an error code. + * + * @retval >0 + * A valid subscriber handle. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the parameters is invalid. + */ +_Pre_satisfies_((participant & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) +DDS_EXPORT _Must_inspect_result_ dds_entity_t +dds_create_subscriber( + _In_ dds_entity_t participant, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener); + +/** + * @brief Creates a new instance of a DDS publisher + * + * @param[in] participant The participant to create a publisher for. + * @param[in] qos The QoS to set on the new publisher (can be NULL). + * @param[in] listener Any listener functions associated with the new publisher (can be NULL). + * + * @returns A valid publisher handle or an error code. + * + * @retval >0 + * A valid publisher handle. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + */ +/* TODO: Check list of error codes is complete. */ +_Pre_satisfies_((participant & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) +DDS_EXPORT _Must_inspect_result_ dds_entity_t +dds_create_publisher( + _In_ dds_entity_t participant, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener); + +/** + * @brief Suspends the publications of the Publisher + * + * This operation is a hint to the Service so it can optimize its performance by e.g., collecting + * modifications to DDS writers and then batching them. The Service is not required to use the hint. + * + * Every invocation of this operation must be matched by a corresponding call to @see dds_resume + * indicating that the set of modifications has completed. + * + * @param[in] publisher The publisher for which all publications will be suspended. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Publications suspended successfully. + * @retval DDS_RETCODE_BAD_PARAMETER + * The pub parameter is not a valid publisher. + * @retval DDS_RETCODE_UNSUPPORTED + * Operation is not supported. + */ +_Pre_satisfies_((publisher & DDS_ENTITY_KIND_MASK) == DDS_KIND_PUBLISHER) +DDS_EXPORT dds_return_t +dds_suspend( + _In_ dds_entity_t publisher); + +/** + * @brief Resumes the publications of the Publisher + * + * This operation is a hint to the Service to indicate that the application has + * completed changes initiated by a previous dds_suspend(). The Service is not + * required to use the hint. + * + * The call to resume_publications must match a previous call to @see suspend_publications. + * + * @param[in] publisher The publisher for which all publications will be resumed. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Publications resumed successfully. + * @retval DDS_RETCODE_BAD_PARAMETER + * The pub parameter is not a valid publisher. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * No previous matching dds_suspend(). + * @retval DDS_RETCODE_UNSUPPORTED + * Operation is not supported. + */ +_Pre_satisfies_((publisher & DDS_ENTITY_KIND_MASK) == DDS_KIND_PUBLISHER) +DDS_EXPORT dds_return_t +dds_resume( + _In_ dds_entity_t publisher); + + +/** + * @brief Waits at most for the duration timeout for acks for data in the publisher or writer. + * + * This operation blocks the calling thread until either all data written by the publisher + * or writer is acknowledged by all matched reliable reader entities, or else the duration + * specified by the timeout parameter elapses, whichever happens first. + * + * @param[in] publisher_or_writer Publisher or writer whose acknowledgments must be waited for + * @param[in] timeout How long to wait for acknowledgments before time out + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * All acknowledgments successfully received with the timeout. + * @retval DDS_RETCODE_BAD_PARAMETER + * The publisher_or_writer is not a valid publisher or writer. + * @retval DDS_RETCODE_TIMEOUT + * Timeout expired before all acknowledgments from reliable reader entities were received. + * @retval DDS_RETCODE_UNSUPPORTED + * Operation is not supported. + */ +_Pre_satisfies_(((publisher_or_writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER ) ||\ + ((publisher_or_writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_PUBLISHER) ) +DDS_EXPORT dds_return_t +dds_wait_for_acks( + _In_ dds_entity_t publisher_or_writer, + _In_ dds_duration_t timeout); + + +/** + * @brief Creates a new instance of a DDS reader. + * + * This implicit subscriber will be deleted automatically when the created reader + * is deleted. + * + * @param[in] participant_or_subscriber The participant or subscriber on which the reader is being created. + * @param[in] topic The topic to read. + * @param[in] qos The QoS to set on the new reader (can be NULL). + * @param[in] listener Any listener functions associated with the new reader (can be NULL). + * + * @returns A valid reader handle or an error code. + * + * @retval >0 + * A valid reader handle. + * @retval DDS_RETCODE_ERROR + * An internal error occurred. + */ +/* TODO: Complete list of error codes */ +_Pre_satisfies_(((participant_or_subscriber & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER ) ||\ + ((participant_or_subscriber & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) ) +_Pre_satisfies_(((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC ) ||\ + ((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_INTERNAL) ) +DDS_EXPORT dds_entity_t +dds_create_reader( + _In_ dds_entity_t participant_or_subscriber, + _In_ dds_entity_t topic, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener); + +/** + * @brief Wait until reader receives all historic data + * + * The operation blocks the calling thread until either all "historical" data is + * received, or else the duration specified by the max_wait parameter elapses, whichever happens + * first. A return value of 0 indicates that all the "historical" data was received; a return + * value of TIMEOUT indicates that max_wait elapsed before all the data was received. + * + * @param[in] reader The reader on which to wait for historical data. + * @param[in] max_wait How long to wait for historical data before time out. + * + * @returns a status, 0 on success, TIMEOUT on timeout or a negative value to indicate error. + */ +/* TODO: Complete list of error codes */ +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +DDS_EXPORT int +dds_reader_wait_for_historical_data( + dds_entity_t reader, + dds_duration_t max_wait); + +/** + * @brief Creates a new instance of a DDS writer. + * + * This implicit publisher will be deleted automatically when the created writer + * is deleted. + * + * @param[in] participant_or_publisher The participant or publisher on which the writer is being created. + * @param[in] topic The topic to write. + * @param[in] qos The QoS to set on the new writer (can be NULL). + * @param[in] listener Any listener functions associated with the new writer (can be NULL). + * + * @returns A valid writer handle or an error code. + * + * @returns >0 + * A valid writer handle. + * @returns DDS_RETCODE_ERROR + * An internal error occurred. + */ +/* TODO: Complete list of error codes */ +_Pre_satisfies_(((participant_or_publisher & DDS_ENTITY_KIND_MASK) == DDS_KIND_PUBLISHER ) ||\ + ((participant_or_publisher & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) ) +_Pre_satisfies_( (topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC ) +DDS_EXPORT dds_entity_t +dds_create_writer( + _In_ dds_entity_t participant_or_publisher, + _In_ dds_entity_t topic, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener); + +/* + Writing data (and variants of it) is straightforward. The first set + is equivalent to the second set with -1 passed for "timestamp", + meaning, substitute the result of a call to time(). The dispose + and unregister operations take an object of the topic's type, but + only touch the key fields; the remained may be undefined. +*/ +/** + * @brief Registers an instance + * + * This operation registers an instance with a key value to the data writer and + * returns an instance handle that could be used for successive write & dispose + * operations. When the handle is not allocated, the function will return and + * error and the handle will be un-touched. + * + * @param[in] writer The writer to which instance has be associated. + * @param[out] handle The instance handle. + * @param[in] data The instance with the key value. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The operation was successful. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_register_instance( + _In_ dds_entity_t writer, + _Out_ dds_instance_handle_t *handle, + _In_ const void *data); + +/** + * @brief Unregisters an instance + * + * This operation reverses the action of register instance, removes all information regarding + * the instance and unregisters an instance with a key value from the data writer. + * + * @param[in] writer The writer to which instance is associated. + * @param[in] data The instance with the key value. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The operation was successful. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_unregister_instance( + _In_ dds_entity_t writer, + _In_opt_ const void *data); + +/** + * @brief Unregisters an instance + * + *This operation unregisters the instance which is identified by the key fields of the given + *typed instance handle. + * + * @param[in] writer The writer to which instance is associated. + * @param[in] handle The instance handle. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The operation was successful. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_unregister_instance_ih( + _In_ dds_entity_t writer, + _In_opt_ dds_instance_handle_t handle); + +/** + * @brief Unregisters an instance + * + * This operation reverses the action of register instance, removes all information regarding + * the instance and unregisters an instance with a key value from the data writer. It also + * provides a value for the timestamp explicitly. + * + * @param[in] writer The writer to which instance is associated. + * @param[in] data The instance with the key value. + * @param[in] timestamp The timestamp used at registration. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The operation was successful. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_unregister_instance_ts( + _In_ dds_entity_t writer, + _In_opt_ const void *data, + _In_ dds_time_t timestamp); + +/** + * @brief Unregisters an instance + * + * This operation unregisters an instance with a key value from the handle. Instance can be identified + * from instance handle. If an unregistered key ID is passed as an instance data, an error is logged and + * not flagged as return value. + * + * @param[in] writer The writer to which instance is associated. + * @param[in] handle The instance handle. + * @param[in] timestamp The timestamp used at registration. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The operation was successful + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_unregister_instance_ih_ts( + _In_ dds_entity_t writer, + _In_opt_ dds_instance_handle_t handle, + _In_ dds_time_t timestamp); + +/** + * @brief This operation modifies and disposes a data instance. + * + * This operation requests the Data Distribution Service to modify the instance and + * mark it for deletion. Copies of the instance and its corresponding samples, which are + * stored in every connected reader and, dependent on the QoS policy settings (also in + * the Transient and Persistent stores) will be modified and marked for deletion by + * setting their dds_instance_state_t to DDS_IST_NOT_ALIVE_DISPOSED. + * + * Blocking
+ * If the history QoS policy is set to DDS_HISTORY_KEEP_ALL, the + * dds_writedispose operation on the writer may block if the modification + * would cause data to be lost because one of the limits, specified in the + * resource_limits QoS policy, to be exceeded. In case the synchronous + * attribute value of the reliability Qos policy is set to true for + * communicating writers and readers then the writer will wait until + * all synchronous readers have acknowledged the data. Under these + * circumstances, the max_blocking_time attribute of the reliability + * QoS policy configures the maximum time the dds_writedispose operation + * may block. + * If max_blocking_time elapses before the writer is able to store the + * modification without exceeding the limits and all expected acknowledgements + * are received, the dds_writedispose operation will fail and returns + * DDS_RETCODE_TIMEOUT. + * + * @param[in] writer The writer to dispose the data instance from. + * @param[in] data The data to be written and disposed. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The sample is written and the instance is marked for deletion. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * At least one of the arguments is invalid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_TIMEOUT + * Either the current action overflowed the available resources as + * specified by the combination of the reliability QoS policy, + * history QoS policy and resource_limits QoS policy, or the + * current action was waiting for data delivery acknowledgement + * by synchronous readers. This caused blocking of this operation, + * which could not be resolved before max_blocking_time of the + * reliability QoS policy elapsed. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_writedispose( + _In_ dds_entity_t writer, + _In_ const void *data); + +/** + * @brief This operation modifies and disposes a data instance with a specific + * timestamp. + * + * This operation performs the same functions as dds_writedispose except that + * the application provides the value for the source_timestamp that is made + * available to connected reader objects. This timestamp is important for the + * interpretation of the destination_order QoS policy. + * + * @param[in] writer The writer to dispose the data instance from. + * @param[in] data The data to be written and disposed. + * @param[in] timestamp The timestamp used as source timestamp. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The sample is written and the instance is marked for deletion. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * At least one of the arguments is invalid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_TIMEOUT + * Either the current action overflowed the available resources as + * specified by the combination of the reliability QoS policy, + * history QoS policy and resource_limits QoS policy, or the + * current action was waiting for data delivery acknowledgement + * by synchronous readers. This caused blocking of this operation, + * which could not be resolved before max_blocking_time of the + * reliability QoS policy elapsed. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_writedispose_ts( + _In_ dds_entity_t writer, + _In_ const void *data, + _In_ dds_time_t timestamp); + +/** + * @brief This operation disposes an instance, identified by the data sample. + * + * This operation requests the Data Distribution Service to modify the instance and + * mark it for deletion. Copies of the instance and its corresponding samples, which are + * stored in every connected reader and, dependent on the QoS policy settings (also in + * the Transient and Persistent stores) will be modified and marked for deletion by + * setting their dds_instance_state_t to DDS_IST_NOT_ALIVE_DISPOSED. + * + * Blocking
+ * If the history QoS policy is set to DDS_HISTORY_KEEP_ALL, the + * dds_writedispose operation on the writer may block if the modification + * would cause data to be lost because one of the limits, specified in the + * resource_limits QoS policy, to be exceeded. In case the synchronous + * attribute value of the reliability Qos policy is set to true for + * communicating writers and readers then the writer will wait until + * all synchronous readers have acknowledged the data. Under these + * circumstances, the max_blocking_time attribute of the reliability + * QoS policy configures the maximum time the dds_writedispose operation + * may block. + * If max_blocking_time elapses before the writer is able to store the + * modification without exceeding the limits and all expected acknowledgements + * are received, the dds_writedispose operation will fail and returns + * DDS_RETCODE_TIMEOUT. + * + * @param[in] writer The writer to dispose the data instance from. + * @param[in] data The data sample that identifies the instance + * to be disposed. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The sample is written and the instance is marked for deletion. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * At least one of the arguments is invalid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_TIMEOUT + * Either the current action overflowed the available resources as + * specified by the combination of the reliability QoS policy, + * history QoS policy and resource_limits QoS policy, or the + * current action was waiting for data delivery acknowledgement + * by synchronous readers. This caused blocking of this operation, + * which could not be resolved before max_blocking_time of the + * reliability QoS policy elapsed. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_dispose( + _In_ dds_entity_t writer, + _In_ const void *data); + +/** + * @brief This operation disposes an instance with a specific timestamp, identified by the data sample. + * + * This operation performs the same functions as dds_dispose except that + * the application provides the value for the source_timestamp that is made + * available to connected reader objects. This timestamp is important for the + * interpretation of the destination_order QoS policy. + * + * @param[in] writer The writer to dispose the data instance from. + * @param[in] data The data sample that identifies the instance + * to be disposed. + * @param[in] timestamp The timestamp used as source timestamp. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The sample is written and the instance is marked for deletion + * @retval DDS_RETCODE_ERROR + * An internal error has occurred + * @retval DDS_RETCODE_BAD_PARAMETER + * At least one of the arguments is invalid + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted + * @retval DDS_RETCODE_TIMEOUT + * Either the current action overflowed the available resources as + * specified by the combination of the reliability QoS policy, + * history QoS policy and resource_limits QoS policy, or the + * current action was waiting for data delivery acknowledgment + * by synchronous readers. This caused blocking of this operation, + * which could not be resolved before max_blocking_time of the + * reliability QoS policy elapsed. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_dispose_ts( + _In_ dds_entity_t writer, + _In_ const void *data, + _In_ dds_time_t timestamp); + +/** + * @brief This operation disposes an instance, identified by the instance handle. + * + * This operation requests the Data Distribution Service to modify the instance and + * mark it for deletion. Copies of the instance and its corresponding samples, which are + * stored in every connected reader and, dependent on the QoS policy settings (also in + * the Transient and Persistent stores) will be modified and marked for deletion by + * setting their dds_instance_state_t to DDS_IST_NOT_ALIVE_DISPOSED. + * + * Instance Handle
+ * The given instance handle must correspond to the value that was returned by either + * the dds_register_instance operation, dds_register_instance_ts or dds_instance_lookup. + * If there is no correspondence, then the result of the operation is unspecified. + * + * @param[in] writer The writer to dispose the data instance from. + * @param[in] handle The handle to identify an instance. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The sample is written and the instance is marked for deletion. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * At least one of the arguments is invalid + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this writer + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_dispose_ih( + _In_ dds_entity_t writer, + _In_ dds_instance_handle_t handle); + +/** + * @brief This operation disposes an instance with a specific timestamp, identified by the instance handle. + * + * This operation performs the same functions as dds_dispose_ih except that + * the application provides the value for the source_timestamp that is made + * available to connected reader objects. This timestamp is important for the + * interpretation of the destination_order QoS policy. + * + * @param[in] writer The writer to dispose the data instance from. + * @param[in] handle The handle to identify an instance. + * @param[in] timestamp The timestamp used as source timestamp. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The sample is written and the instance is marked for deletion. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * At least one of the arguments is invalid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this writer. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_dispose_ih_ts( + _In_ dds_entity_t writer, + _In_ dds_instance_handle_t handle, + _In_ dds_time_t timestamp); + +/** + * @brief Write the value of a data instance + * + * With this API, the value of the source timestamp is automatically made + * available to the data reader by the service. + * + * @param[in] writer The writer entity. + * @param[in] data Value to be written. + * + * @returns dds_return_t indicating success or failure. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_write( + _In_ dds_entity_t writer, + _In_ const void *data); + +/*TODO: What is it for and is it really needed? */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT void +dds_write_flush( + dds_entity_t writer); + +/** + * @brief Write a CDR serialized value of a data instance + * + * @param[in] writer The writer entity. + * @param[in] cdr CDR serialized value to be written. + * @param[in] size Size (in bytes) of CDR encoded data to be written. + * + * @returns A dds_return_t indicating success or failure. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT int +dds_writecdr( + dds_entity_t writer, + const void *cdr, + size_t size); + +/** + * @brief Write the value of a data instance along with the source timestamp passed. + * + * @param[in] writer The writer entity. + * @param[in] data Value to be written. + * @param[in] timestamp Source timestamp. + * + * @returns A dds_return_t indicating success or failure. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_write_ts( + _In_ dds_entity_t writer, + _In_ const void *data, + _In_ dds_time_t timestamp); + +/** + * @brief Creates a readcondition associated to the given reader. + * + * The readcondition allows specifying which samples are of interest in + * a data reader's history, by means of a mask. The mask is or'd with + * the flags that are dds_sample_state_t, dds_view_state_t and + * dds_instance_state_t. + * + * Based on the mask value set, the readcondition gets triggered when + * data is available on the reader. + * + * Waitsets allow waiting for an event on some of any set of entities. + * This means that the readcondition can be used to wake up a waitset when + * data is in the reader history with states that matches the given mask. + * + * @note The parent reader and every of its associated conditions (whether + * they are readconditions or queryconditions) share the same resources. + * This means that one of these entities reads or takes data, the states + * of the data will change for other entities automatically. For instance, + * if one reads a sample, then the sample state will become 'read' for all + * associated reader/conditions. Or if one takes a sample, then it's not + * available to any other associated reader/condition. + * + * @param[in] reader Reader to associate the condition to. + * @param[in] mask Interest (dds_sample_state_t|dds_view_state_t|dds_instance_state_t). + * + * @returns A valid condition handle or an error code. + * + * @retval >0 + * A valid condition handle + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +DDS_EXPORT _Must_inspect_result_ dds_entity_t +dds_create_readcondition( + _In_ dds_entity_t reader, + _In_ uint32_t mask); + +typedef bool (*dds_querycondition_filter_fn) (const void * sample); + +/** + * @brief Creates a queryondition associated to the given reader. + * + * The queryondition allows specifying which samples are of interest in + * a data reader's history, by means of a mask and a filter. The mask is + * or'd with the flags that are dds_sample_state_t, dds_view_state_t and + * dds_instance_state_t. + * + * Based on the mask value set and data that matches the filter, the + * querycondition gets triggered when data is available on the reader. + * + * Waitsets allow waiting for an event on some of any set of entities. + * This means that the querycondition can be used to wake up a waitset when + * data is in the reader history with states that matches the given mask + * and filter. + * + * @note The parent reader and every of its associated conditions (whether + * they are readconditions or queryconditions) share the same resources. + * This means that one of these entities reads or takes data, the states + * of the data will change for other entities automatically. For instance, + * if one reads a sample, then the sample state will become 'read' for all + * associated reader/conditions. Or if one takes a sample, then it's not + * available to any other associated reader/condition. + * + * @param[in] reader Reader to associate the condition to. + * @param[in] mask Interest (dds_sample_state_t|dds_view_state_t|dds_instance_state_t). + * @param[in] filter Callback that the application can use to filter specific samples. + * + * @returns A valid condition handle or an error code + * + * @retval >=0 + * A valid condition handle. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +/* TODO: Explain the filter (aka expression & parameters) of the (to be + * implemented) new querycondition implementation. + * TODO: Update parameters when new querycondition is introduced. + */ +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +DDS_EXPORT dds_entity_t +dds_create_querycondition( + _In_ dds_entity_t reader, + _In_ uint32_t mask, + _In_ dds_querycondition_filter_fn filter); + +/** + * @brief Waitset attachment argument. + * + * Every entity that is attached to the waitset can be accompanied by such + * an attachment argument. When the waitset wait is unblocked because of an + * entity that triggered, then the returning array will be populated with + * these attachment arguments that are related to the triggered entity. + */ +typedef intptr_t dds_attach_t; + +/** + * @brief Create a waitset and allocate the resources required + * + * A WaitSet object allows an application to wait until one or more of the + * conditions of the attached entities evaluates to TRUE or until the timeout + * expires. + * + * @param[in] participant Domain participant which the WaitSet contains. + * + * @returns A valid waitset handle or an error code. + * + * @retval >=0 + * A valid waitset handle. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_((participant & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) +DDS_EXPORT _Must_inspect_result_ dds_entity_t +dds_create_waitset( + _In_ dds_entity_t participant); + +/** + * @brief Acquire previously attached entities. + * + * This functions takes a pre-allocated list to put the entities in and + * will return the number of found entities. It is possible that the given + * size of the list is not the same as the number of found entities. If + * less entities are found, then the last few entries in the list are + * untouched. When more entities are found, then only 'size' number of + * entries are inserted into the list, but still the complete count of the + * found entities is returned. Which entities are returned in the latter + * case is undefined. + * + * @param[in] waitset Waitset from which to get its attached entities. + * @param[out] entities Pre-allocated array to contain the found entities. + * @param[in] size Size of the pre-allocated entities' list. + * + * @returns A dds_return_t with the number of children or an error code. + * + * @retval >=0 + * Number of children found (can be larger than 'size'). + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The entities parameter is NULL, while a size is provided. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The waitset has already been deleted. + */ +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +DDS_EXPORT dds_return_t +dds_waitset_get_entities( + _In_ dds_entity_t waitset, + _Out_writes_to_(size, return < 0 ? 0 : return) dds_entity_t *entities, + _In_ size_t size); + + +/** + * @brief This operation attaches an Entity to the WaitSet. + * + * This operation attaches an Entity to the WaitSet. The dds_waitset_wait() + * will block when none of the attached entities are triggered. 'Triggered' + * (dds_triggered()) doesn't mean the same for every entity: + * - Reader/Writer/Publisher/Subscriber/Topic/Participant + * - These are triggered when their status changed. + * - WaitSet + * - Triggered when trigger value was set to true by the application. + * It stays triggered until application sets the trigger value to + * false (dds_waitset_set_trigger()). This can be used to wake up an + * waitset for different reasons (f.i. termination) than the 'normal' + * status change (like new data). + * - ReadCondition/QueryCondition + * - Triggered when data is available on the related Reader that matches + * the Condition. + * + * Multiple entities can be attached to a single waitset. A particular entity + * can be attached to multiple waitsets. However, a particular entity can not + * be attached to a particular waitset multiple times. + * + * @param[in] waitset The waitset to attach the given entity to. + * @param[in] entity The entity to attach. + * @param[in] x Blob that will be supplied when the waitset wait is + * triggerd by the given entity. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Entity attached. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The given waitset or entity are not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The waitset has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The entity was already attached. + */ +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +DDS_EXPORT dds_return_t +dds_waitset_attach( + _In_ dds_entity_t waitset, + _In_ dds_entity_t entity, + _In_ dds_attach_t x); + +/** + * @brief This operation detaches an Entity to the WaitSet. + * + * @param[in] waitset The waitset to detach the given entity from. + * @param[in] entity The entity to detach. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Entity attached. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The given waitset or entity are not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The waitset has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The entity is not attached. + */ +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +DDS_EXPORT dds_return_t +dds_waitset_detach( + _In_ dds_entity_t waitset, + _In_ dds_entity_t entity); + +/** + * @brief Sets the trigger_value associated with a waitset. + * + * When the waitset is attached to itself and the trigger value is + * set to 'true', then the waitset will wake up just like with an + * other status change of the attached entities. + * + * This can be used to forcefully wake up a waitset, for instance + * when the application wants to shut down. So, when the trigger + * value is true, the waitset will wake up or not wait at all. + * + * The trigger value will remain true until the application sets it + * false again deliberately. + * + * @param[in] waitset The waitset to set the trigger value on. + * @param[in] trigger The trigger value to set. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * Entity attached. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The given waitset is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The waitset has already been deleted. + */ +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +DDS_EXPORT dds_return_t +dds_waitset_set_trigger( + _In_ dds_entity_t waitset, + _In_ bool trigger); + +/** + * @brief This operation allows an application thread to wait for the a status + * change or other trigger on (one of) the entities that are attached to + * the WaitSet. + * + * The "dds_waitset_wait" operation blocks until the some of the attached + * entities have triggered or "reltimeout" has elapsed. + * 'Triggered' (dds_triggered()) doesn't mean the same for every entity: + * - Reader/Writer/Publisher/Subscriber/Topic/Participant + * - These are triggered when their status changed. + * - WaitSet + * - Triggered when trigger value was set to true by the application. + * It stays triggered until application sets the trigger value to + * false (dds_waitset_set_trigger()). This can be used to wake up an + * waitset for different reasons (f.i. termination) than the 'normal' + * status change (like new data). + * - ReadCondition/QueryCondition + * - Triggered when data is available on the related Reader that matches + * the Condition. + * + * This functions takes a pre-allocated list to put the "xs" blobs in (that + * were provided during the attach of the related entities) and will return + * the number of triggered entities. It is possible that the given size + * of the list is not the same as the number of triggered entities. If less + * entities were triggered, then the last few entries in the list are + * untouched. When more entities are triggered, then only 'size' number of + * entries are inserted into the list, but still the complete count of the + * triggered entities is returned. Which "xs" blobs are returned in the + * latter case is undefined. + * + * In case of a time out, the return value is 0. + * + * Deleting the waitset while the application is blocked results in an + * error code (i.e. < 0) returned by "wait". + * + * Multiple threads may block on a single waitset at the same time; + * the calls are entirely independent. + * + * An empty waitset never triggers (i.e., dds_waitset_wait on an empty + * waitset is essentially equivalent to a sleep). + * + * The "dds_waitset_wait_until" operation is the same as the + * "dds_waitset_wait" except that it takes an absolute timeout. + * + * @param[in] waitset The waitset to set the trigger value on. + * @param[out] xs Pre-allocated list to store the 'blobs' that were + * provided during the attach of the triggered entities. + * @param[in] nxs The size of the pre-allocated blobs list. + * @param[in] reltimeout Relative timeout + * + * @returns A dds_return_t with the number of entities triggered or an error code + * + * @retval >0 + * Number of entities triggered. + * @retval 0 + * Time out (no entities were triggered). + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The given waitset is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The waitset has already been deleted. + */ +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +DDS_EXPORT dds_return_t +dds_waitset_wait( + _In_ dds_entity_t waitset, + _Out_writes_to_opt_(nxs, return < 0 ? 0 : return) dds_attach_t *xs, + _In_ size_t nxs, + _In_ dds_duration_t reltimeout); + +/** + * @brief This operation allows an application thread to wait for the a status + * change or other trigger on (one of) the entities that are attached to + * the WaitSet. + * + * The "dds_waitset_wait" operation blocks until the some of the attached + * entities have triggered or "abstimeout" has been reached. + * 'Triggered' (dds_triggered()) doesn't mean the same for every entity: + * - Reader/Writer/Publisher/Subscriber/Topic/Participant + * - These are triggered when their status changed. + * - WaitSet + * - Triggered when trigger value was set to true by the application. + * It stays triggered until application sets the trigger value to + * false (dds_waitset_set_trigger()). This can be used to wake up an + * waitset for different reasons (f.i. termination) than the 'normal' + * status change (like new data). + * - ReadCondition/QueryCondition + * - Triggered when data is available on the related Reader that matches + * the Condition. + * + * This functions takes a pre-allocated list to put the "xs" blobs in (that + * were provided during the attach of the related entities) and will return + * the number of triggered entities. It is possible that the given size + * of the list is not the same as the number of triggered entities. If less + * entities were triggered, then the last few entries in the list are + * untouched. When more entities are triggered, then only 'size' number of + * entries are inserted into the list, but still the complete count of the + * triggered entities is returned. Which "xs" blobs are returned in the + * latter case is undefined. + * + * In case of a time out, the return value is 0. + * + * Deleting the waitset while the application is blocked results in an + * error code (i.e. < 0) returned by "wait". + * + * Multiple threads may block on a single waitset at the same time; + * the calls are entirely independent. + * + * An empty waitset never triggers (i.e., dds_waitset_wait on an empty + * waitset is essentially equivalent to a sleep). + * + * The "dds_waitset_wait" operation is the same as the + * "dds_waitset_wait_until" except that it takes an relative timeout. + * + * The "dds_waitset_wait" operation is the same as the "dds_wait" + * except that it takes an absolute timeout. + * + * @param[in] waitset The waitset to set the trigger value on. + * @param[out] xs Pre-allocated list to store the 'blobs' that were + * provided during the attach of the triggered entities. + * @param[in] nxs The size of the pre-allocated blobs list. + * @param[in] abstimeout Absolute timeout + * + * @returns A dds_return_t with the number of entities triggered or an error code. + * + * @retval >0 + * Number of entities triggered. + * @retval 0 + * Time out (no entities were triggered). + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The given waitset is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The waitset has already been deleted. + */ +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +DDS_EXPORT dds_return_t +dds_waitset_wait_until( + _In_ dds_entity_t waitset, + _Out_writes_to_opt_(nxs, return < 0 ? 0 : return) dds_attach_t *xs, + _In_ size_t nxs, + _In_ dds_time_t abstimeout); + +/* + There are a number of read and take variations. + + Return value is the number of elements returned. "max_samples" + should have the same type, as one can't return more than MAX_INT + this way, anyway. X, Y, CX, CY return to the various filtering + options, see the DCPS spec. + + O ::= read | take + + X => CX + (empty) (empty) + _next_instance instance_handle_t prev + + Y => CY + (empty) uint32_t mask + _cond cond_t cond -- refers to a read condition (or query if implemented) + */ + +/** + * @brief Access and read the collection of data values (of same type) and sample info from the + * data reader, readcondition or querycondition. + * + * Return value provides information about number of samples read, which will + * be <= maxs. Based on the count, the buffer will contain data to be read only + * when valid_data bit in sample info structure is set. + * The buffer required for data values, could be allocated explicitly or can + * use the memory from data reader to prevent copy. In the latter case, buffer and + * sample_info should be returned back, once it is no longer using the Data. + * Data values once read will remain in the buffer with the sample_state set to READ + * and view_state set to NOT_NEW. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value. + * @param[in] bufsz The size of buffer provided. + * @param[in] maxs Maximum number of samples to read. + * + * @returns A dds_return_t with the number of samples read or an error code. + * + * @retval >=0 + * Number of samples read. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_read( + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs); + +/** + * @brief Access and read loaned samples of data reader, readcondition or querycondition. + * + * After dds_read_wl function is being called and the data has been handled, dds_return_loan function must be called to possibly free memory. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] maxs Maximum number of samples to read + * + * @returns A dds_return_t with the number of samples read or an error code + * + * @retval >=0 + * Number of samples read. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_read_wl( + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs); + +/** + * @brief Read the collection of data values and sample info from the data reader, readcondition + * or querycondition based on mask. + * + * When using a readcondition or querycondition, their masks are or'd with the given mask. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value. + * @param[in] bufsz The size of buffer provided. + * @param[in] maxs Maximum number of samples to read. + * @param[in] mask Filter the data based on dds_sample_state_t|dds_view_state_t|dds_instance_state_t. + * + * @returns A dds_return_t with the number of samples read or an error code. + * + * @retval >=0 + * Number of samples read. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_read_mask( + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ uint32_t mask); + +/** + * @brief Access and read loaned samples of data reader, readcondition + * or querycondition based on mask + * + * When using a readcondition or querycondition, their masks are or'd with the given mask. + * + * After dds_read_mask_wl function is being called and the data has been handled, dds_return_loan function must be called to possibly free memory + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value. + * @param[in] maxs Maximum number of samples to read. + * @param[in] mask Filter the data based on dds_sample_state_t|dds_view_state_t|dds_instance_state_t. + * + * @returns A dds_return_t with the number of samples read or an error code. + * + * @retval >=0 + * Number of samples read. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_read_mask_wl( + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs, + _In_ uint32_t mask); + +/** + * @brief Access and read the collection of data values (of same type) and sample info from the + * data reader, readcondition or querycondition, coped by the provided instance handle. + * + * This operation implements the same functionality as dds_read, except that only data scoped to + * the provided instance handle is read. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value. + * @param[in] bufsz The size of buffer provided. + * @param[in] maxs Maximum number of samples to read. + * @param[in] handle Instance handle related to the samples to read. + * + * @returns A dds_return_t with the number of samples read or an error code. + * + * @retval >=0 + * Number of samples read. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this reader. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_read_instance( + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle); + +/** + * @brief Access and read loaned samples of data reader, readcondition or querycondition, + * scoped by the provided instance handle. + * + * This operation implements the same functionality as dds_read_wl, except that only data + * scoped to the provided instance handle is read. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value. + * @param[in] maxs Maximum number of samples to read. + * @param[in] handle Instance handle related to the samples to read. + * + * @returns A dds_return_t with the number of samples read or an error code. + * + * @retval >=0 + * Number of samples read. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this reader. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_read_instance_wl( + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle); + +/** + * @brief Read the collection of data values and sample info from the data reader, readcondition + * or querycondition based on mask and scoped by the provided instance handle. + * + * This operation implements the same functionality as dds_read_mask, except that only data + * scoped to the provided instance handle is read. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value. + * @param[in] bufsz The size of buffer provided. + * @param[in] maxs Maximum number of samples to read. + * @param[in] handle Instance handle related to the samples to read. + * @param[in] mask Filter the data based on dds_sample_state_t|dds_view_state_t|dds_instance_state_t. + * + * @returns A dds_return_t with the number of samples read or an error code. + * + * @retval >=0 + * Number of samples read. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this reader. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_read_instance_mask( + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle, + _In_ uint32_t mask); + +/** + * @brief Access and read loaned samples of data reader, readcondition or + * querycondition based on mask, scoped by the provided instance handle. + * + * This operation implements the same functionality as dds_read_mask_wl, except that + * only data scoped to the provided instance handle is read. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value. + * @param[in] maxs Maximum number of samples to read. + * @param[in] handle Instance handle related to the samples to read. + * @param[in] mask Filter the data based on dds_sample_state_t|dds_view_state_t|dds_instance_state_t. + * + * @returns A dds_return_t with the number of samples read or an error code. + * + * @retval >=0 + * Number of samples read. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this reader. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_read_instance_mask_wl( + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle, + _In_ uint32_t mask); + +/** + * @brief Access the collection of data values (of same type) and sample info from the + * data reader, readcondition or querycondition. + * + * Data value once read is removed from the Data Reader cannot to + * 'read' or 'taken' again. + * Return value provides information about number of samples read, which will + * be <= maxs. Based on the count, the buffer will contain data to be read only + * when valid_data bit in sample info structure is set. + * The buffer required for data values, could be allocated explicitly or can + * use the memory from data reader to prevent copy. In the latter case, buffer and + * sample_info should be returned back, once it is no longer using the Data. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value. + * @param[in] bufsz The size of buffer provided. + * @param[in] maxs Maximum number of samples to read. + * + * @returns A dds_return_t with the number of samples read or an error code. + * + * @retval >=0 + * Number of samples read. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_take( + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs); + +/** + * @brief Access loaned samples of data reader, readcondition or querycondition. + * + * After dds_take_wl function is being called and the data has been handled, dds_return_loan function must be called to possibly free memory + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value. + * @param[in] maxs Maximum number of samples to read. + * + * @returns A dds_return_t with the number of samples read or an error code. + * + * @retval >=0 + * Number of samples read. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_take_wl( + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs); + +/** + * @brief Take the collection of data values (of same type) and sample info from the + * data reader, readcondition or querycondition based on mask + * + * When using a readcondition or querycondition, their masks are or'd with the given mask. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value. + * @param[in] bufsz The size of buffer provided. + * @param[in] maxs Maximum number of samples to read. + * @param[in] mask Filter the data based on dds_sample_state_t|dds_view_state_t|dds_instance_state_t. + * + * @returns A dds_return_t with the number of samples read or an error code. + * + * @retval >=0 + * Number of samples read. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_take_mask( + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ uint32_t mask); + +/** + * @brief Access loaned samples of data reader, readcondition or querycondition based on mask. + * + * When using a readcondition or querycondition, their masks are or'd with the given mask. + * + * After dds_take_mask_wl function is being called and the data has been handled, dds_return_loan function must be called to possibly free memory + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value. + * @param[in] maxs Maximum number of samples to read. + * @param[in] mask Filter the data based on dds_sample_state_t|dds_view_state_t|dds_instance_state_t. + * + * @returns A dds_return_t with the number of samples read or an error code. + * + * @retval >=0 + * Number of samples read. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_take_mask_wl( + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs, + _In_ uint32_t mask); + +struct serdata; + +DDS_EXPORT int +dds_takecdr( + dds_entity_t reader_or_condition, + struct serdata **buf, + uint32_t maxs, + dds_sample_info_t *si, + uint32_t mask); + +/** + * @brief Access the collection of data values (of same type) and sample info from the + * data reader, readcondition or querycondition but scoped by the given + * instance handle. + * + * This operation mplements the same functionality as dds_take, except that only data + * scoped to the provided instance handle is taken. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value. + * @param[in] bufsz The size of buffer provided. + * @param[in] maxs Maximum number of samples to read. + * @param[in] handle Instance handle related to the samples to read. + * + * @returns A dds_return_t with the number of samples read or an error code. + * + * @retval >=0 + * Number of samples read. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this reader. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_take_instance( + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle); + +/** + * @brief Access loaned samples of data reader, readcondition or querycondition, + * scoped by the given instance handle. + * + * This operation implements the same functionality as dds_take_wl, except that + * only data scoped to the provided instance handle is read. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value. + * @param[in] maxs Maximum number of samples to read. + * @param[in] handle Instance handle related to the samples to read. + * + * @returns A dds_return_t with the number of samples read or an error code. + * + * @retval >=0 + * Number of samples read. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this reader. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_take_instance_wl( + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle); + +/** + * @brief Take the collection of data values (of same type) and sample info from the + * data reader, readcondition or querycondition based on mask and scoped + * by the given instance handle. + * + * This operation implements the same functionality as dds_take_mask, except that only + * data scoped to the provided instance handle is read. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value. + * @param[in] bufsz The size of buffer provided. + * @param[in] maxs Maximum number of samples to read. + * @param[in] handle Instance handle related to the samples to read. + * @param[in] mask Filter the data based on dds_sample_state_t|dds_view_state_t|dds_instance_state_t. + * + * @returns A dds_return_t with the number of samples read or an error code. + * + * @retval >=0 + * Number of samples read. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this reader. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_take_instance_mask( + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle, + _In_ uint32_t mask); + +/** + * @brief Access loaned samples of data reader, readcondition or querycondition based + * on mask and scoped by the given intance handle. + * + * This operation implements the same functionality as dds_take_mask_wl, except that + * only data scoped to the provided instance handle is read. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value. + * @param[in] maxs Maximum number of samples to read. + * @param[in] handle Instance handle related to the samples to read. + * @param[in] mask Filter the data based on dds_sample_state_t|dds_view_state_t|dds_instance_state_t. + * + * @returns A dds_return_t with the number of samples or an error code. + * + * @retval >= 0 + * Number of samples read. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this reader. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_take_instance_mask_wl( + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle, + _In_ uint32_t mask); + +/* + The read/take next functions return a single sample. The returned sample + has a sample state of NOT_READ, a view state of ANY_VIEW_STATE and an + instance state of ANY_INSTANCE_STATE. +*/ + +/** + * @brief Read, copy and remove the status set for the entity + * + * This operation copies the next, non-previously accessed + * data value and corresponding sample info and removes from + * the data reader. As an entity, only reader is accepted. + * + * @param[in] reader The reader entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si The pointer to \ref dds_sample_info_t returned for a data value. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The operation was successful. + * @retval DDS_RETCODE_BAD_PARAMETER + * The entity parameter is not a valid parameter. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) +DDS_EXPORT dds_return_t +dds_take_next( + _In_ dds_entity_t reader, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si); + +/** + * @brief Read, copy and remove the status set for the entity + * + * This operation copies the next, non-previously accessed + * data value and corresponding sample info and removes from + * the data reader. As an entity, only reader is accepted. + * + * After dds_take_next_wl function is being called and the data has been handled, + * dds_return_loan function must be called to possibly free memory. + * + * @param[in] reader The reader entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si The pointer to \ref dds_sample_info_t returned for a data value. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The operation was successful. + * @retval DDS_RETCODE_BAD_PARAMETER + * The entity parameter is not a valid parameter. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) +DDS_EXPORT dds_return_t +dds_take_next_wl( + _In_ dds_entity_t reader, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si); + +/** + * @brief Read and copy the status set for the entity + * + * This operation copies the next, non-previously accessed + * data value and corresponding sample info. As an entity, + * only reader is accepted. + * + * @param[in] reader The reader entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si The pointer to \ref dds_sample_info_t returned for a data value. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The operation was successful. + * @retval DDS_RETCODE_BAD_PARAMETER + * The entity parameter is not a valid parameter. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) +DDS_EXPORT dds_return_t +dds_read_next( + _In_ dds_entity_t reader, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si); + +/** + * @brief Read and copy the status set for the loaned sample + * + * This operation copies the next, non-previously accessed + * data value and corresponding loaned sample info. As an entity, + * only reader is accepted. + * + * After dds_read_next_wl function is being called and the data has been handled, + * dds_return_loan function must be called to possibly free memory. + * + * @param[in] reader The reader entity. + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL). + * @param[out] si The pointer to \ref dds_sample_info_t returned for a data value. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The operation was successful. + * @retval DDS_RETCODE_BAD_PARAMETER + * The entity parameter is not a valid parameter. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) +DDS_EXPORT dds_return_t +dds_read_next_wl( + _In_ dds_entity_t reader, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si); + +/** + * @brief Return loaned samples to data-reader or condition associated with a data-reader + * + * Used to release sample buffers returned by a read/take operation. When the application + * provides an empty buffer, memory is allocated and managed by DDS. By calling dds_return_loan, + * the memory is released so that the buffer can be reused during a successive read/take operation. + * When a condition is provided, the reader to which the condition belongs is looked up. + * + * @param[in] rd_or_cnd Reader or condition that belongs to a reader. + * @param[in] buf An array of (pointers to) samples. + * @param[in] bufsz The number of (pointers to) samples stored in buf. + * + * @returns A dds_return_t indicating success or failure + */ +/* TODO: Add list of possible return codes */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT _Must_inspect_result_ dds_return_t +dds_return_loan( + _In_ dds_entity_t reader_or_condition, + _Inout_updates_(bufsz) void **buf, + _In_ size_t bufsz); + +/* + Instance handle <=> key value mapping. + Functions exactly as read w.r.t. treatment of data + parameter. On output, only key values set. + + T x = { ... }; + T y; + dds_instance_handle_t ih; + ih = dds_instance_lookup (e, &x); + dds_instance_get_key (e, ih, &y); +*/ + +/** + * @brief This operation takes a sample and returns an instance handle to be used for subsequent operations. + * + * @param[in] entity Reader or Writer entity. + * @param[in] data Sample with a key fields set. + * + * @returns instance handle or DDS_HANDLE_NIL if instance could not be found from key. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT dds_instance_handle_t +dds_instance_lookup( + dds_entity_t entity, + const void *data); + +/** + * @brief This operation takes an instance handle and return a key-value corresponding to it. + * + * @param[in] entity Reader or writer entity. + * @param[in] inst Instance handle. + * @param[out] data pointer to an instance, to which the key ID corresponding to the instance handle will be + * returned, the sample in the instance should be ignored. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The operation was successful. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the parameters was invalid or the topic does not exist. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + */ +/* TODO: Check return codes for completeness */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT dds_return_t +dds_instance_get_key( + dds_entity_t entity, + dds_instance_handle_t inst, + void *data); + +/** + * @brief Begin coherent publishing or begin accessing a coherent set in a subscriber + * + * Invoking on a Writer or Reader behaves as if dds_begin_coherent was invoked on its parent + * Publisher or Subscriber respectively. + * + * @param[in] entity The entity that is prepared for coherent access. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The operation was successful. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The provided entity is invalid or not supported. + */ +_Pre_satisfies_(((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER) ) +DDS_EXPORT dds_return_t +dds_begin_coherent( + _In_ dds_entity_t entity); + +/** + * @brief End coherent publishing or end accessing a coherent set in a subscriber + * + * Invoking on a Writer or Reader behaves as if dds_end_coherent was invoked on its parent + * Publisher or Subscriber respectively. + * + * @param[in] entity The entity on which coherent access is finished. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The operation was successful. + * @retval DDS_RETCODE_BAD_PARAMETER + * The provided entity is invalid or not supported. + */ +_Pre_satisfies_(((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER) ) +DDS_EXPORT dds_return_t +dds_end_coherent( + _In_ dds_entity_t entity); + +/** + * @brief Trigger DATA_AVAILABLE event on contained readers + * + * The DATA_AVAILABLE event is broadcast to all readers owned by this subscriber that currently + * have new data available. Any on_data_available listener callbacks attached to respective + * readers are invoked. + * + * @param[in] subscriber A valid subscriber handle. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The operation was successful. + * @retval DDS_RETCODE_BAD_PARAMETER + * The provided subscriber is invalid. + */ +_Pre_satisfies_((subscriber & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER) +DDS_EXPORT dds_return_t +dds_notify_readers( + _In_ dds_entity_t subscriber); + +/** + * @brief Checks whether the entity has one of its enabled statuses triggered. + * + * @param[in] entity Entity for which to check for triggered status. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The operation was successful. + * @retval DDS_RETCODE_BAD_PARAMETER + * The entity parameter is not a valid parameter. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT dds_return_t +dds_triggered( + _In_ dds_entity_t entity); + +/** + * @brief Get the topic + * + * This operation returns a topic (handle) when the function call is done + * with reader, writer, read condition or query condition. For instance, it + * will return the topic when it is used for creating the reader or writer. + * For the conditions, it returns the topic that is used for creating the reader + * which was used to create the condition. + * + * @param[in] entity The entity. + * + * @returns A dds_return_t indicating success or failure. + * + * @retval DDS_RETCODE_OK + * The operation was successful. + * @retval DDS_RETCODE_BAD_PARAMETER + * The entity parameter is not a valid parameter. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT dds_entity_t +dds_get_topic( + _In_ dds_entity_t entity); + +#if defined (__cplusplus) +} +#endif +#endif /* DDS_H */ diff --git a/src/core/ddsc/include/ddsc/dds_public_alloc.h b/src/core/ddsc/include/ddsc/dds_public_alloc.h new file mode 100644 index 0000000..5ad74e9 --- /dev/null +++ b/src/core/ddsc/include/ddsc/dds_public_alloc.h @@ -0,0 +1,87 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +/* TODO: do we really need to expose this as an API? */ + +/** @file + * + * @brief DDS C Allocation API + * + * This header file defines the public API of allocation convenience functions + * in the CycloneDDS C language binding. + */ +#ifndef DDS_ALLOC_H +#define DDS_ALLOC_H + +#include "os/os_public.h" +#include "ddsc/dds_export.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +struct dds_topic_descriptor; +struct dds_sequence; + +#define DDS_FREE_KEY_BIT 0x01 +#define DDS_FREE_CONTENTS_BIT 0x02 +#define DDS_FREE_ALL_BIT 0x04 + +typedef enum +{ + DDS_FREE_ALL = DDS_FREE_KEY_BIT | DDS_FREE_CONTENTS_BIT | DDS_FREE_ALL_BIT, + DDS_FREE_CONTENTS = DDS_FREE_KEY_BIT | DDS_FREE_CONTENTS_BIT, + DDS_FREE_KEY = DDS_FREE_KEY_BIT +} +dds_free_op_t; + +typedef struct dds_allocator +{ + /* Behaviour as C library malloc, realloc and free */ + + void * (*malloc) (size_t size); + void * (*realloc) (void *ptr, size_t size); /* if needed */ + void (*free) (void *ptr); +} +dds_allocator_t; + +DDS_EXPORT void dds_set_allocator (const dds_allocator_t * __restrict n, dds_allocator_t * __restrict o); + +typedef struct dds_aligned_allocator +{ + /* size is a multiple of align, align is a power of 2 no less than + the machine's page size, returned pointer MUST be aligned to at + least align. */ + void * (*alloc) (size_t size, size_t align); + void (*free) (size_t size, void *ptr); +} +dds_aligned_allocator_t; + +DDS_EXPORT void dds_set_aligned_allocator (const dds_aligned_allocator_t * __restrict n, dds_aligned_allocator_t * __restrict o); + +DDS_EXPORT void * dds_alloc (size_t size); +DDS_EXPORT void * dds_realloc (void * ptr, size_t size); +DDS_EXPORT void * dds_realloc_zero (void * ptr, size_t size); +DDS_EXPORT void dds_free (void * ptr); + +typedef void * (*dds_alloc_fn_t) (size_t); +typedef void * (*dds_realloc_fn_t) (void *, size_t); +typedef void (*dds_free_fn_t) (void *); + +DDS_EXPORT char * dds_string_alloc (size_t size); +DDS_EXPORT char * dds_string_dup (const char * str); +DDS_EXPORT void dds_string_free (char * str); +DDS_EXPORT void dds_sample_free (void * sample, const struct dds_topic_descriptor * desc, dds_free_op_t op); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/include/ddsc/dds_public_error.h b/src/core/ddsc/include/ddsc/dds_public_error.h new file mode 100644 index 0000000..8082440 --- /dev/null +++ b/src/core/ddsc/include/ddsc/dds_public_error.h @@ -0,0 +1,155 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +/** @file + * + * @brief DDS C Error API + * + * This header file defines the public API of error values and convenience + * functions in the CycloneDDS C language binding. + */ +#ifndef DDS_ERROR_H +#define DDS_ERROR_H + +#include "os/os_public.h" +#include "ddsc/dds_export.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +/* Error masks for returned status values */ + +#define DDS_ERR_NR_MASK 0x000000ff +#define DDS_ERR_LINE_MASK 0x003fff00 +#define DDS_ERR_FILE_ID_MASK 0x7fc00000 + + +/* + State is unchanged following a function call returning an error + other than UNSPECIFIED, OUT_OF_RESOURCES and ALREADY_DELETED. + + Error handling functions. Three components to returned int status value. + + 1 - The DDS_ERR_xxx error number + 2 - The file identifier + 3 - The line number + + All functions return >= 0 on success, < 0 on error +*/ +/** @name Return codes + @{**/ +#define DDS_RETCODE_OK 0 /**< Success */ +#define DDS_RETCODE_ERROR 1 /**< Non specific error */ +#define DDS_RETCODE_UNSUPPORTED 2 /**< Feature unsupported */ +#define DDS_RETCODE_BAD_PARAMETER 3 /**< Bad parameter value */ +#define DDS_RETCODE_PRECONDITION_NOT_MET 4 /**< Precondition for operation not met */ +#define DDS_RETCODE_OUT_OF_RESOURCES 5 /**< When an operation fails because of a lack of resources */ +#define DDS_RETCODE_NOT_ENABLED 6 /**< When a configurable feature is not enabled */ +#define DDS_RETCODE_IMMUTABLE_POLICY 7 /**< When an attempt is made to modify an immutable policy */ +#define DDS_RETCODE_INCONSISTENT_POLICY 8 /**< When a policy is used with inconsistent values */ +#define DDS_RETCODE_ALREADY_DELETED 9 /**< When an attempt is made to delete something more than once */ +#define DDS_RETCODE_TIMEOUT 10 /**< When a timeout has occurred */ +#define DDS_RETCODE_NO_DATA 11 /**< When expected data is not provided */ +#define DDS_RETCODE_ILLEGAL_OPERATION 12 /**< When a function is called when it should not be */ +#define DDS_RETCODE_NOT_ALLOWED_BY_SECURITY 13 /**< When credentials are not enough to use the function */ + + +/** @}*/ + +/* For backwards compatability */ + +#define DDS_SUCCESS DDS_RETCODE_OK + +/** @name DDS_Error_Type + @{**/ +#define DDS_CHECK_REPORT 0x01 +#define DDS_CHECK_FAIL 0x02 +#define DDS_CHECK_EXIT 0x04 +/** @}*/ + +/* Error code handling functions */ + +/** @name Macros for error handling + @{**/ +#define DDS_TO_STRING(n) #n +#define DDS_INT_TO_STRING(n) DDS_TO_STRING(n) +/** @}*/ + +/** Macro to extract error number */ +#define dds_err_nr(e) ((-(e)) & DDS_ERR_NR_MASK) + +/** Macro to extract line number */ +#define dds_err_line(e) (((-(e)) & DDS_ERR_LINE_MASK) >> 8) + +/** Macro to extract file identifier */ +#define dds_err_file_id(e) (((-(e)) & DDS_ERR_FILE_ID_MASK) >> 22) + +/** + * @brief Takes the error value and outputs a string corresponding to it. + * + * @param[in] err Error value to be converted to a string + * @returns String corresponding to the error value + */ +DDS_EXPORT const char * dds_err_str (dds_return_t err); + +/** + * @brief Takes the error number, error type and filename and line number and formats it to + * a string which can be used for debugging. + * + * @param[in] err Error value + * @param[in] flags Indicates Fail, Exit or Report + * @param[in] where File and line number + * @returns true - True + * @returns false - False + */ + +DDS_EXPORT bool dds_err_check (dds_return_t err, unsigned flags, const char * where); + +/** Macro that defines dds_err_check function */ +#define DDS_ERR_CHECK(e, f) (dds_err_check ((e), (f), __FILE__ ":" DDS_INT_TO_STRING(__LINE__))) + +/* Failure handling */ + +/** Failure handler */ +typedef void (*dds_fail_fn) (const char *, const char *); + +/** Macro that defines dds_fail function */ +#define DDS_FAIL(m) (dds_fail (m, __FILE__ ":" DDS_INT_TO_STRING (__LINE__))) + +/** + * @brief Set the failure function + * + * @param[in] fn Function to invoke on failure + */ +DDS_EXPORT void dds_fail_set (dds_fail_fn fn); + +/** + * @brief Get the failure function + * + * @returns Failure function + */ +DDS_EXPORT dds_fail_fn dds_fail_get (void); + +/** + * @brief Handles failure through an installed failure handler + * + * @params[in] msg String containing failure message + * @params[in] where String containing file and location + */ +DDS_EXPORT void dds_fail (const char * msg, const char * where); + + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/include/ddsc/dds_public_impl.h b/src/core/ddsc/include/ddsc/dds_public_impl.h new file mode 100644 index 0000000..784d002 --- /dev/null +++ b/src/core/ddsc/include/ddsc/dds_public_impl.h @@ -0,0 +1,210 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +/* TODO: do we really need to expose all of this as an API? maybe some, but all? */ + +/** @file + * + * @brief DDS C Implementation API + * + * This header file defines the public API for all kinds of things in the + * CycloneDDS C language binding. + */ +#ifndef DDS_IMPL_H +#define DDS_IMPL_H + +#include "ddsc/dds_public_alloc.h" +#include "ddsc/dds_public_stream.h" +#include "os/os_public.h" +#include "ddsc/dds_export.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +typedef struct dds_sequence +{ + uint32_t _maximum; + uint32_t _length; + uint8_t * _buffer; + bool _release; +} +dds_sequence_t; + +#define DDS_LENGTH_UNLIMITED -1 + +typedef struct dds_key_descriptor +{ + const char * m_name; + uint32_t m_index; +} +dds_key_descriptor_t; + +/* + Topic definitions are output by a preprocessor and have an + implementation-private definition. The only thing exposed on the + API is a pointer to the "topic_descriptor_t" struct type. +*/ + +typedef struct dds_topic_descriptor +{ + const size_t m_size; /* Size of topic type */ + const uint32_t m_align; /* Alignment of topic type */ + const uint32_t m_flagset; /* Flags */ + const uint32_t m_nkeys; /* Number of keys (can be 0) */ + const char * m_typename; /* Type name */ + const dds_key_descriptor_t * m_keys; /* Key descriptors (NULL iff m_nkeys 0) */ + const uint32_t m_nops; /* Number of ops in m_ops */ + const uint32_t * m_ops; /* Marshalling meta data */ + const char * m_meta; /* XML topic description meta data */ +} +dds_topic_descriptor_t; + +/* Topic descriptor flag values */ + +#define DDS_TOPIC_NO_OPTIMIZE 0x0001 +#define DDS_TOPIC_FIXED_KEY 0x0002 + +/* + Masks for read condition, read, take: there is only one mask here, + which combines the sample, view and instance states. +*/ + +#define DDS_READ_SAMPLE_STATE 1u +#define DDS_NOT_READ_SAMPLE_STATE 2u +#define DDS_ANY_SAMPLE_STATE (1u | 2u) + +#define DDS_NEW_VIEW_STATE 4u +#define DDS_NOT_NEW_VIEW_STATE 8u +#define DDS_ANY_VIEW_STATE (4u | 8u) + +#define DDS_ALIVE_INSTANCE_STATE 16u +#define DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE 32u +#define DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE 64u +#define DDS_ANY_INSTANCE_STATE (16u | 32u | 64u) + +#define DDS_ANY_STATE (DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE) + +#define DDS_DOMAIN_DEFAULT -1 +#define DDS_HANDLE_NIL 0 +#define DDS_ENTITY_NIL 0 + +#define DDS_ENTITY_KIND_MASK (0x7F000000) /* Should be same as UT_HANDLE_KIND_MASK. */ +typedef enum dds_entity_kind +{ + DDS_KIND_DONTCARE = 0x00000000, + DDS_KIND_TOPIC = 0x01000000, + DDS_KIND_PARTICIPANT = 0x02000000, + DDS_KIND_READER = 0x03000000, + DDS_KIND_WRITER = 0x04000000, + DDS_KIND_SUBSCRIBER = 0x05000000, + DDS_KIND_PUBLISHER = 0x06000000, + DDS_KIND_COND_READ = 0x07000000, + DDS_KIND_COND_QUERY = 0x08000000, + DDS_KIND_WAITSET = 0x09000000, + DDS_KIND_INTERNAL = 0x0A000000, +} +dds_entity_kind_t; + +/* Handles are opaque pointers to implementation types */ +typedef uint64_t dds_instance_handle_t; +typedef int32_t dds_domainid_t; + + +/* Topic encoding instruction types */ + +#define DDS_OP_RTS 0x00000000 +#define DDS_OP_ADR 0x01000000 +#define DDS_OP_JSR 0x02000000 +#define DDS_OP_JEQ 0x03000000 + +/* Core type flags + + 1BY : One byte simple type + 2BY : Two byte simple type + 4BY : Four byte simple type + 8BY : Eight byte simple type + STR : String + BST : Bounded string + SEQ : Sequence + ARR : Array + UNI : Union + STU : Struct +*/ + +#define DDS_OP_VAL_1BY 0x01 +#define DDS_OP_VAL_2BY 0x02 +#define DDS_OP_VAL_4BY 0x03 +#define DDS_OP_VAL_8BY 0x04 +#define DDS_OP_VAL_STR 0x05 +#define DDS_OP_VAL_BST 0x06 +#define DDS_OP_VAL_SEQ 0x07 +#define DDS_OP_VAL_ARR 0x08 +#define DDS_OP_VAL_UNI 0x09 +#define DDS_OP_VAL_STU 0x0a + +#define DDS_OP_TYPE_1BY (DDS_OP_VAL_1BY << 16) +#define DDS_OP_TYPE_2BY (DDS_OP_VAL_2BY << 16) +#define DDS_OP_TYPE_4BY (DDS_OP_VAL_4BY << 16) +#define DDS_OP_TYPE_8BY (DDS_OP_VAL_8BY << 16) +#define DDS_OP_TYPE_STR (DDS_OP_VAL_STR << 16) +#define DDS_OP_TYPE_SEQ (DDS_OP_VAL_SEQ << 16) +#define DDS_OP_TYPE_ARR (DDS_OP_VAL_ARR << 16) +#define DDS_OP_TYPE_UNI (DDS_OP_VAL_UNI << 16) +#define DDS_OP_TYPE_STU (DDS_OP_VAL_STU << 16) +#define DDS_OP_TYPE_BST (DDS_OP_VAL_BST << 16) + +#define DDS_OP_TYPE_BOO DDS_OP_TYPE_1BY +#define DDS_OP_SUBTYPE_BOO DDS_OP_SUBTYPE_1BY + +#define DDS_OP_SUBTYPE_1BY (DDS_OP_VAL_1BY << 8) +#define DDS_OP_SUBTYPE_2BY (DDS_OP_VAL_2BY << 8) +#define DDS_OP_SUBTYPE_4BY (DDS_OP_VAL_4BY << 8) +#define DDS_OP_SUBTYPE_8BY (DDS_OP_VAL_8BY << 8) +#define DDS_OP_SUBTYPE_STR (DDS_OP_VAL_STR << 8) +#define DDS_OP_SUBTYPE_SEQ (DDS_OP_VAL_SEQ << 8) +#define DDS_OP_SUBTYPE_ARR (DDS_OP_VAL_ARR << 8) +#define DDS_OP_SUBTYPE_UNI (DDS_OP_VAL_UNI << 8) +#define DDS_OP_SUBTYPE_STU (DDS_OP_VAL_STU << 8) +#define DDS_OP_SUBTYPE_BST (DDS_OP_VAL_BST << 8) + +#define DDS_OP_FLAG_KEY 0x01 +#define DDS_OP_FLAG_DEF 0x02 + +/** + * Description : Enable or disable write batching. Overrides default configuration + * setting for write batching (DDSI2E/Internal/WriteBatch). + * + * Arguments : + * -# enable Enables or disables write batching for all writers. + */ +DDS_EXPORT void dds_write_set_batch (bool enable); + +/** + * Description : Install tcp/ssl and encryption support. Depends on openssl. + * + * Arguments : + * -# None + */ +DDS_EXPORT void dds_ssl_plugin (void); + +/** + * Description : Install client durability support. Depends on OSPL server. + * + * Arguments : + * -# None + */ +DDS_EXPORT void dds_durability_plugin (void); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/include/ddsc/dds_public_listener.h b/src/core/ddsc/include/ddsc/dds_public_listener.h new file mode 100644 index 0000000..e5465d9 --- /dev/null +++ b/src/core/ddsc/include/ddsc/dds_public_listener.h @@ -0,0 +1,323 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +/** @file + * + * @brief DDS C Listener API + * + * This header file defines the public API of listeners in the + * CycloneDDS C language binding. + */ +#ifndef _DDS_PUBLIC_LISTENER_H_ +#define _DDS_PUBLIC_LISTENER_H_ + +#include "ddsc/dds_export.h" +#include "ddsc/dds_public_impl.h" +#include "ddsc/dds_public_status.h" +#include "os/os_public.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +/* Listener callbacks */ +typedef void (*dds_on_inconsistent_topic_fn) (dds_entity_t topic, const dds_inconsistent_topic_status_t status, void* arg); +typedef void (*dds_on_liveliness_lost_fn) (dds_entity_t writer, const dds_liveliness_lost_status_t status, void* arg); +typedef void (*dds_on_offered_deadline_missed_fn) (dds_entity_t writer, const dds_offered_deadline_missed_status_t status, void* arg); +typedef void (*dds_on_offered_incompatible_qos_fn) (dds_entity_t writer, const dds_offered_incompatible_qos_status_t status, void* arg); +typedef void (*dds_on_data_on_readers_fn) (dds_entity_t subscriber, void* arg); +typedef void (*dds_on_sample_lost_fn) (dds_entity_t reader, const dds_sample_lost_status_t status, void* arg); +typedef void (*dds_on_data_available_fn) (dds_entity_t reader, void* arg); +typedef void (*dds_on_sample_rejected_fn) (dds_entity_t reader, const dds_sample_rejected_status_t status, void* arg); +typedef void (*dds_on_liveliness_changed_fn) (dds_entity_t reader, const dds_liveliness_changed_status_t status, void* arg); +typedef void (*dds_on_requested_deadline_missed_fn) (dds_entity_t reader, const dds_requested_deadline_missed_status_t status, void* arg); +typedef void (*dds_on_requested_incompatible_qos_fn) (dds_entity_t reader, const dds_requested_incompatible_qos_status_t status, void* arg); +typedef void (*dds_on_publication_matched_fn) (dds_entity_t writer, const dds_publication_matched_status_t status, void* arg); +typedef void (*dds_on_subscription_matched_fn) (dds_entity_t reader, const dds_subscription_matched_status_t status, void* arg); + +#if 0 +/* TODO: Why use (*dds_on_any_fn) (); and DDS_LUNSET? Why not just set the callbacks to NULL? */ +typedef void (*dds_on_any_fn) (); /**< Empty parameter list on purpose; should be assignable without cast to all of the above. @todo check with an actual compiler; I'm a sloppy compiler */ +#define DDS_LUNSET ((dds_on_any_fn)1) /**< Callback indicating a callback isn't set */ +#else +#define DDS_LUNSET (NULL) +#endif + +struct c_listener; +typedef struct c_listener dds_listener_t; + +/** + * @brief Allocate memory and initializes to default values (::DDS_LUNSET) of a listener + * + * @param[in] arg optional pointer that will be passed on to the listener callbacks + * + * @return Returns a pointer to the allocated memory for dds_listener_t structure. + */ +_Ret_notnull_ +DDS_EXPORT dds_listener_t* dds_listener_create (_In_opt_ void* arg); + +/** + * @brief Delete the memory allocated to listener structure + * + * @param[in] listener pointer to the listener struct to delete + */ +DDS_EXPORT void dds_listener_delete (_In_ _Post_invalid_ dds_listener_t * __restrict listener); + +/** + * @brief Reset the listener structure contents to ::DDS_LUNSET + * + * @param[in,out] listener pointer to the listener struct to reset + */ +DDS_EXPORT void dds_listener_reset (_Out_ dds_listener_t * __restrict listener); + +/** + * @brief Copy the listener callbacks from source to destination + * + * @param[in,out] dst The pointer to the destination listener structure, where the content is to copied + * @param[in] src The pointer to the source listener structure to be copied + */ +DDS_EXPORT void dds_listener_copy (_Out_ dds_listener_t * __restrict dst, _In_ const dds_listener_t * __restrict src); + +/** + * @brief Copy the listener callbacks from source to destination, unless already set + * + * Any listener callbacks already set in @p dst (including NULL) are skipped, only + * those set to DDS_LUNSET are copied from @p src. + * + * @param[in,out] dst The pointer to the destination listener structure, where the content is merged + * @param[in] src The pointer to the source listener structure to be copied + */ +DDS_EXPORT void dds_listener_merge (_Inout_ dds_listener_t * __restrict dst, _In_ const dds_listener_t * __restrict src); + + +/************************************************************************************************ + * Setters + ************************************************************************************************/ + +/** + * @brief Set the inconsistent_topic callback in the listener structure. + * + * @param listener The pointer to the listener structure, where the callback will be set + * @param callback The callback to set in the listener, can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lset_inconsistent_topic (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_inconsistent_topic_fn callback); + +/** + * @brief Set the liveliness_lost callback in the listener structure. + * + * @param[out] listener The pointer to the listener structure, where the callback will be set + * @param[in] callback The callback to set in the listener, can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lset_liveliness_lost (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_liveliness_lost_fn callback); + +/** + * @brief Set the offered_deadline_missed callback in the listener structure. + * + * @param[in,out] listener The pointer to the listener structure, where the callback will be set + * @param[in] callback The callback to set in the listener, can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lset_offered_deadline_missed (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_offered_deadline_missed_fn callback); + +/** + * @brief Set the offered_incompatible_qos callback in the listener structure. + * + * @param[in,out] listener The pointer to the listener structure, where the callback will be set + * @param[in] callback The callback to set in the listener, can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lset_offered_incompatible_qos (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_offered_incompatible_qos_fn callback); + +/** + * @brief Set the data_on_readers callback in the listener structure. + * + * @param[in,out] listener The pointer to the listener structure, where the callback will be set + * @param[in] callback The callback to set in the listener, can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lset_data_on_readers (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_data_on_readers_fn callback); + +/** + * @brief Set the sample_lost callback in the listener structure. + * + * @param[in,out] listener The pointer to the listener structure, where the callback will be set + * @param[in] callback The callback to set in the listener, can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lset_sample_lost (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_sample_lost_fn callback); + +/** + * @brief Set the data_available callback in the listener structure. + * + * @param[in,out] listener The pointer to the listener structure, where the callback will be set + * @param[in] callback The callback to set in the listener, can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lset_data_available (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_data_available_fn callback); + +/** + * @brief Set the sample_rejected callback in the listener structure. + * + * @param[in,out] listener The pointer to the listener structure, where the callback will be set + * @param[in] callback The callback to set in the listener, can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lset_sample_rejected (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_sample_rejected_fn callback); + +/** + * @brief Set the liveliness_changed callback in the listener structure. + * + * @param[in,out] listener The pointer to the listener structure, where the callback will be set + * @param[in] callback The callback to set in the listener, can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lset_liveliness_changed (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_liveliness_changed_fn callback); + +/** + * @brief Set the requested_deadline_missed callback in the listener structure. + * + * @param[in,out] listener The pointer to the listener structure, where the callback will be set + * @param[in] callback The callback to set in the listener, can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lset_requested_deadline_missed (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_requested_deadline_missed_fn callback); + +/** + * @brief Set the requested_incompatible_qos callback in the listener structure. + * + * @param[in,out] listener The pointer to the listener structure, where the callback will be set + * @param[in] callback The callback to set in the listener, can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lset_requested_incompatible_qos (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_requested_incompatible_qos_fn callback); + +/** + * @brief Set the publication_matched callback in the listener structure. + * + * @param[in,out] listener The pointer to the listener structure, where the callback will be set + * @param[in] callback The callback to set in the listener, can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lset_publication_matched (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_publication_matched_fn callback); + +/** + * @brief Set the subscription_matched callback in the listener structure. + * + * @param[in,out] listener The pointer to the listener structure, where the callback will be set + * @param[in] callback The callback to set in the listener, can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lset_subscription_matched (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_subscription_matched_fn callback); + + +/************************************************************************************************ + * Getters + ************************************************************************************************/ + +/** + * @brief Get the inconsistent_topic callback from the listener structure. + * + * @param[in] listener The pointer to the listener structure, where the callback will be retrieved from + * @param[in,out] callback Pointer where the retrieved callback can be stored; can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lget_inconsistent_topic (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_inconsistent_topic_fn *callback); + +/** + * @brief Get the liveliness_lost callback from the listener structure. + * + * @param[in] listener The pointer to the listener structure, where the callback will be retrieved from + * @param[in,out] callback Pointer where the retrieved callback can be stored; can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lget_liveliness_lost (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_liveliness_lost_fn *callback); + +/** + * @brief Get the offered_deadline_missed callback from the listener structure. + * + * @param[in] listener The pointer to the listener structure, where the callback will be retrieved from + * @param[in,out] callback Pointer where the retrieved callback can be stored; can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lget_offered_deadline_missed (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_offered_deadline_missed_fn *callback); + +/** + * @brief Get the offered_incompatible_qos callback from the listener structure. + * + * @param[in] listener The pointer to the listener structure, where the callback will be retrieved from + * @param[in,out] callback Pointer where the retrieved callback can be stored; can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lget_offered_incompatible_qos (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_offered_incompatible_qos_fn *callback); + +/** + * @brief Get the data_on_readers callback from the listener structure. + * + * @param[in] listener The pointer to the listener structure, where the callback will be retrieved from + * @param[in,out] callback Pointer where the retrieved callback can be stored; can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lget_data_on_readers (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_data_on_readers_fn *callback); + +/** + * @brief Get the sample_lost callback from the listener structure. + * + * @param[in] listener The pointer to the listener structure, where the callback will be retrieved from + * @param[in,out] callback Pointer where the retrieved callback can be stored; can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lget_sample_lost (_In_ const dds_listener_t *__restrict listener, _Outptr_result_maybenull_ dds_on_sample_lost_fn *callback); + +/** + * @brief Get the data_available callback from the listener structure. + * + * @param[in] listener The pointer to the listener structure, where the callback will be retrieved from + * @param[in,out] callback Pointer where the retrieved callback can be stored; can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lget_data_available (_In_ const dds_listener_t *__restrict listener, _Outptr_result_maybenull_ dds_on_data_available_fn *callback); + +/** + * @brief Get the sample_rejected callback from the listener structure. + * + * @param[in] listener The pointer to the listener structure, where the callback will be retrieved from + * @param[in,out] callback Pointer where the retrieved callback can be stored; can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lget_sample_rejected (_In_ const dds_listener_t *__restrict listener, _Outptr_result_maybenull_ dds_on_sample_rejected_fn *callback); + +/** + * @brief Get the liveliness_changed callback from the listener structure. + * + * @param[in] listener The pointer to the listener structure, where the callback will be retrieved from + * @param[in,out] callback Pointer where the retrieved callback can be stored; can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lget_liveliness_changed (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_liveliness_changed_fn *callback); + +/** + * @brief Get the requested_deadline_missed callback from the listener structure. + * + * @param[in] listener The pointer to the listener structure, where the callback will be retrieved from + * @param[in,out] callback Pointer where the retrieved callback can be stored; can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lget_requested_deadline_missed (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_requested_deadline_missed_fn *callback); + +/** + * @brief Get the requested_incompatible_qos callback from the listener structure. + * + * @param[in] listener The pointer to the listener structure, where the callback will be retrieved from + * @param[in,out] callback Pointer where the retrieved callback can be stored; can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lget_requested_incompatible_qos (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_requested_incompatible_qos_fn *callback); + +/** + * @brief Get the publication_matched callback from the listener structure. + * + * @param[in] listener The pointer to the listener structure, where the callback will be retrieved from + * @param[in,out] callback Pointer where the retrieved callback can be stored; can be NULL, ::DDS_LUNSET or a valid callback pointer + */ +DDS_EXPORT void dds_lget_publication_matched (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_publication_matched_fn *callback); + +/** + * @brief Get the subscription_matched callback from the listener structure. + * + * @param[in] callback Pointer where the retrieved callback can be stored; can be NULL, ::DDS_LUNSET or a valid callback pointer + * @param[in,out] listener The pointer to the listener structure, where the callback will be retrieved from + */ +DDS_EXPORT void dds_lget_subscription_matched (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_subscription_matched_fn *callback); + +#if defined (__cplusplus) +} +#endif + +#endif /*_DDS_PUBLIC_LISTENER_H_*/ diff --git a/src/core/ddsc/include/ddsc/dds_public_log.h b/src/core/ddsc/include/ddsc/dds_public_log.h new file mode 100644 index 0000000..4556cdf --- /dev/null +++ b/src/core/ddsc/include/ddsc/dds_public_log.h @@ -0,0 +1,39 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +/* TODO: do we really need to expose this as an API? */ + +/** @file + * + * @brief DDS C Logging API + * + * This header file defines the public API for logging in the + * CycloneDDS C language binding. + */ +#ifndef DDS_LOG_H +#define DDS_LOG_H + +#include "os/os_public.h" +#include "ddsc/dds_export.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +DDS_EXPORT void dds_log_info (const char * fmt, ...); +DDS_EXPORT void dds_log_warn (const char * fmt, ...); +DDS_EXPORT void dds_log_error (const char * fmt, ...); +DDS_EXPORT void dds_log_fatal (const char * fmt, ...); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/include/ddsc/dds_public_qos.h b/src/core/ddsc/include/ddsc/dds_public_qos.h new file mode 100644 index 0000000..187e03d --- /dev/null +++ b/src/core/ddsc/include/ddsc/dds_public_qos.h @@ -0,0 +1,815 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +/** @file + * + * @brief DDS C QoS API + * + * This header file defines the public API of QoS and Policies in the + * CycloneDDS C language binding. + */ +#ifndef DDS_QOS_H +#define DDS_QOS_H + +#include "os/os_public.h" +#include "ddsc/dds_export.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +/* QoS identifiers */ +/** @name QoS identifiers + @{**/ +#define DDS_INVALID_QOS_POLICY_ID 0 +#define DDS_USERDATA_QOS_POLICY_ID 1 +#define DDS_DURABILITY_QOS_POLICY_ID 2 +#define DDS_PRESENTATION_QOS_POLICY_ID 3 +#define DDS_DEADLINE_QOS_POLICY_ID 4 +#define DDS_LATENCYBUDGET_QOS_POLICY_ID 5 +#define DDS_OWNERSHIP_QOS_POLICY_ID 6 +#define DDS_OWNERSHIPSTRENGTH_QOS_POLICY_ID 7 +#define DDS_LIVELINESS_QOS_POLICY_ID 8 +#define DDS_TIMEBASEDFILTER_QOS_POLICY_ID 9 +#define DDS_PARTITION_QOS_POLICY_ID 10 +#define DDS_RELIABILITY_QOS_POLICY_ID 11 +#define DDS_DESTINATIONORDER_QOS_POLICY_ID 12 +#define DDS_HISTORY_QOS_POLICY_ID 13 +#define DDS_RESOURCELIMITS_QOS_POLICY_ID 14 +#define DDS_ENTITYFACTORY_QOS_POLICY_ID 15 +#define DDS_WRITERDATALIFECYCLE_QOS_POLICY_ID 16 +#define DDS_READERDATALIFECYCLE_QOS_POLICY_ID 17 +#define DDS_TOPICDATA_QOS_POLICY_ID 18 +#define DDS_GROUPDATA_QOS_POLICY_ID 19 +#define DDS_TRANSPORTPRIORITY_QOS_POLICY_ID 20 +#define DDS_LIFESPAN_QOS_POLICY_ID 21 +#define DDS_DURABILITYSERVICE_QOS_POLICY_ID 22 +/** @}*/ + + +/* QoS structure is opaque */ +/** QoS structure */ +typedef struct nn_xqos dds_qos_t; + +/** Durability QoS: Applies to Topic, DataReader, DataWriter */ +typedef enum dds_durability_kind +{ + DDS_DURABILITY_VOLATILE, + DDS_DURABILITY_TRANSIENT_LOCAL, + DDS_DURABILITY_TRANSIENT, + DDS_DURABILITY_PERSISTENT +} +dds_durability_kind_t; + +/** History QoS: Applies to Topic, DataReader, DataWriter */ +typedef enum dds_history_kind +{ + DDS_HISTORY_KEEP_LAST, + DDS_HISTORY_KEEP_ALL +} +dds_history_kind_t; + +/** Ownership QoS: Applies to Topic, DataReader, DataWriter */ +typedef enum dds_ownership_kind +{ + DDS_OWNERSHIP_SHARED, + DDS_OWNERSHIP_EXCLUSIVE +} +dds_ownership_kind_t; + +/** Liveliness QoS: Applies to Topic, DataReader, DataWriter */ +typedef enum dds_liveliness_kind +{ + DDS_LIVELINESS_AUTOMATIC, + DDS_LIVELINESS_MANUAL_BY_PARTICIPANT, + DDS_LIVELINESS_MANUAL_BY_TOPIC +} +dds_liveliness_kind_t; + +/** Reliability QoS: Applies to Topic, DataReader, DataWriter */ +typedef enum dds_reliability_kind +{ + DDS_RELIABILITY_BEST_EFFORT, + DDS_RELIABILITY_RELIABLE +} +dds_reliability_kind_t; + +/** DestinationOrder QoS: Applies to Topic, DataReader, DataWriter */ +typedef enum dds_destination_order_kind +{ + DDS_DESTINATIONORDER_BY_RECEPTION_TIMESTAMP, + DDS_DESTINATIONORDER_BY_SOURCE_TIMESTAMP +} +dds_destination_order_kind_t; + +/** History QoS: Applies to Topic, DataReader, DataWriter */ +typedef struct dds_history_qospolicy +{ + dds_history_kind_t kind; + int32_t depth; +} +dds_history_qospolicy_t; + +/** ResourceLimits QoS: Applies to Topic, DataReader, DataWriter */ +typedef struct dds_resource_limits_qospolicy +{ + int32_t max_samples; + int32_t max_instances; + int32_t max_samples_per_instance; +} +dds_resource_limits_qospolicy_t; + +/** Presentation QoS: Applies to Publisher, Subscriber */ +typedef enum dds_presentation_access_scope_kind +{ + DDS_PRESENTATION_INSTANCE, + DDS_PRESENTATION_TOPIC, + DDS_PRESENTATION_GROUP +} +dds_presentation_access_scope_kind_t; + +/** + * @brief Allocate memory and initialize default QoS-policies + * + * @returns - Pointer to the initialized dds_qos_t structure, NULL if unsuccessful. + */ +_Ret_notnull_ +DDS_EXPORT +dds_qos_t * dds_qos_create (void); + +/** + * @brief Delete memory allocated to QoS-policies structure + * + * @param[in] qos - Pointer to dds_qos_t structure + */ +DDS_EXPORT +void dds_qos_delete ( + _In_ _Post_invalid_ dds_qos_t * __restrict qos +); + +/** + * @brief Reset a QoS-policies structure to default values + * + * @param[in,out] qos - Pointer to the dds_qos_t structure + */ +DDS_EXPORT +void dds_qos_reset ( + _Out_ dds_qos_t * __restrict qos +); + +/** + * @brief Copy all QoS-policies from one structure to another + * + * @param[in,out] dst - Pointer to the destination dds_qos_t structure + * @param[in] src - Pointer to the source dds_qos_t structure + * + * @returns - Return-code indicating success or failure + */ +DDS_EXPORT +dds_return_t dds_qos_copy ( + _Out_ dds_qos_t * __restrict dst, + _In_ const dds_qos_t * __restrict src +); + +/** + * @brief Copy all QoS-policies from one structure to another, unless already set + * + * Policies are copied from src to dst, unless src already has the policy set to a non-default value. + * + * @param[in,out] dst - Pointer to the destination qos structure + * @param[in] src - Pointer to the source qos structure + */ +DDS_EXPORT +void dds_qos_merge +( + _Inout_ dds_qos_t * __restrict dst, + _In_ const dds_qos_t * __restrict src +); + +/** + * @brief Set the userdata of a qos structure. + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the userdata + * @param[in] value - Pointer to the userdata + * @param[in] sz - Size of userdata stored in value + */ +DDS_EXPORT +void dds_qset_userdata +( + _Inout_ dds_qos_t * __restrict qos, + _In_reads_bytes_opt_(sz) const void * __restrict value, + _In_ size_t sz +); + +/** + * @brief Set the topicdata of a qos structure. + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the topicdata + * @param[in] value - Pointer to the topicdata + * @param[in] sz - Size of the topicdata stored in value + */ +DDS_EXPORT +void dds_qset_topicdata +( + _Inout_ dds_qos_t * __restrict qos, + _In_reads_bytes_opt_(sz) const void * __restrict value, + _In_ size_t sz +); + +/** + * @brief Set the groupdata of a qos structure. + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the groupdata + * @param[in] value - Pointer to the group data + * @param[in] sz - Size of groupdata stored in value + */ +DDS_EXPORT +void dds_qset_groupdata +( + _Inout_ dds_qos_t * __restrict qos, + _In_reads_bytes_opt_(sz) const void * __restrict value, + _In_ size_t sz +); + +/** + * @brief Set the durability policy of a qos structure. + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * @param[in] kind - Durability kind value \ref DCPS_QoS_Durability + */ +DDS_EXPORT +void dds_qset_durability +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(DDS_DURABILITY_VOLATILE, DDS_DURABILITY_PERSISTENT) dds_durability_kind_t kind +); + +/** + * @brief Set the history policy of a qos structure. + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * @param[in] kind - History kind value \ref DCPS_QoS_History + * @param[in] depth - History depth value \ref DCPS_QoS_History + */ +DDS_EXPORT +void dds_qset_history +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(DDS_HISTORY_KEEP_LAST, DDS_HISTORY_KEEP_ALL) dds_history_kind_t kind, + _In_range_(>=, DDS_LENGTH_UNLIMITED) int32_t depth +); + +/** + * @brief Set the resource limits policy of a qos structure. + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * @param[in] max_samples - Number of samples resource-limit value + * @param[in] max_instances - Number of instances resource-limit value + * @param[in] max_samples_per_instance - Number of samples per instance resource-limit value + */ +DDS_EXPORT +void dds_qset_resource_limits +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(>=, DDS_LENGTH_UNLIMITED) int32_t max_samples, + _In_range_(>=, DDS_LENGTH_UNLIMITED) int32_t max_instances, + _In_range_(>=, DDS_LENGTH_UNLIMITED) int32_t max_samples_per_instance +); + + +/** + * @brief Set the presentation policy of a qos structure. + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * @param[in] access_scope - Access-scope kind + * @param[in] coherent_access - Coherent access enable value + * @param[in] ordered_access - Ordered access enable value + */ +DDS_EXPORT void dds_qset_presentation +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(DDS_PRESENTATION_INSTANCE, DDS_PRESENTATION_GROUP) dds_presentation_access_scope_kind_t access_scope, + _In_ bool coherent_access, + _In_ bool ordered_access +); + +/** + * @brief Set the lifespan policy of a qos structure. + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * @param[in] lifespan - Lifespan duration (expiration time relative to source timestamp of a sample) + */ +DDS_EXPORT +void dds_qset_lifespan +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(0, DDS_INFINITY) dds_duration_t lifespan +); + +/** + * @brief Set the deadline policy of a qos structure. + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * @param[in] deadline - Deadline duration + */ +DDS_EXPORT +void dds_qset_deadline +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(0, DDS_INFINITY) dds_duration_t deadline +); + +/** + * @brief Set the latency-budget policy of a qos structure + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * @param[in] duration - Latency budget duration + */ +DDS_EXPORT +void dds_qset_latency_budget +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(0, DDS_INFINITY) dds_duration_t duration +); + +/** + * @brief Set the ownership policy of a qos structure + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * @param[in] kind - Ownership kind + */ +DDS_EXPORT +void dds_qset_ownership +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(DDS_OWNERSHIP_SHARED, DDS_OWNERSHIP_EXCLUSIVE) dds_ownership_kind_t kind +); + +/** + * @brief Set the ownership strength policy of a qos structure + * + * param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * param[in] value - Ownership strength value + */ +DDS_EXPORT +void dds_qset_ownership_strength +( + _Inout_ dds_qos_t * __restrict qos, + _In_ int32_t value +); + +/** + * @brief Set the liveliness policy of a qos structure + * + * param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * param[in] kind - Liveliness kind + * param[in[ lease_duration - Lease duration + */ +DDS_EXPORT +void dds_qset_liveliness +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(DDS_LIVELINESS_AUTOMATIC, DDS_LIVELINESS_MANUAL_BY_TOPIC) dds_liveliness_kind_t kind, + _In_range_(0, DDS_INFINITY) dds_duration_t lease_duration +); + +/** + * @brief Set the time-based filter policy of a qos structure + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * @param[in] minimum_separation - Minimum duration between sample delivery for an instance + */ +DDS_EXPORT +void dds_qset_time_based_filter +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(0, DDS_INFINITY) dds_duration_t minimum_separation +); + +/** + * @brief Set the partition policy of a qos structure + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * @param[in] n - Number of partitions stored in ps + * @param[in[ ps - Pointer to string(s) storing partition name(s) + */ +DDS_EXPORT +void dds_qset_partition +( + _Inout_ dds_qos_t * __restrict qos, + _In_ uint32_t n, + _In_count_(n) _Deref_pre_z_ const char ** __restrict ps +); + +/** + * @brief Set the reliability policy of a qos structure + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * @param[in] kind - Reliability kind + * @param[in] max_blocking_time - Max blocking duration applied when kind is reliable. + */ +DDS_EXPORT +void dds_qset_reliability +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(DDS_RELIABILITY_BEST_EFFORT, DDS_RELIABILITY_RELIABLE) dds_reliability_kind_t kind, + _In_range_(0, DDS_INFINITY) dds_duration_t max_blocking_time +); + +/** + * @brief Set the transport-priority policy of a qos structure + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * @param[in] value - Priority value + */ +DDS_EXPORT +void dds_qset_transport_priority +( + _Inout_ dds_qos_t * __restrict qos, + _In_ int32_t value +); + +/** + * @brief Set the destination-order policy of a qos structure + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * @param[in] kind - Destination-order kind + */ +DDS_EXPORT +void dds_qset_destination_order +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(DDS_DESTINATIONORDER_BY_RECEPTION_TIMESTAMP, + DDS_DESTINATIONORDER_BY_SOURCE_TIMESTAMP) dds_destination_order_kind_t kind +); + +/** + * @brief Set the writer data-lifecycle policy of a qos structure + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * @param[in] autodispose_unregistered_instances - Automatic disposal of unregistered instances + */ +DDS_EXPORT +void dds_qset_writer_data_lifecycle +( + _Inout_ dds_qos_t * __restrict qos, + _In_ bool autodispose +); + +/** + * @brief Set the reader data-lifecycle policy of a qos structure + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * @param[in] autopurge_nowriter_samples_delay - Delay for purging of samples from instances in a no-writers state + * @param[in] autopurge_disposed_samples_delay - Delay for purging of samples from disposed instances + */ +DDS_EXPORT +void dds_qset_reader_data_lifecycle +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(0, DDS_INFINITY) dds_duration_t autopurge_nowriter_samples_delay, + _In_range_(0, DDS_INFINITY) dds_duration_t autopurge_disposed_samples_delay +); + +/** + * @brief Set the durability-service policy of a qos structure + * + * @param[in,out] qos - Pointer to a dds_qos_t structure that will store the policy + * @param[in] service_cleanup_delay - Delay for purging of abandoned instances from the durability service + * @param[in] history_kind - History policy kind applied by the durability service + * @param[in] history_depth - History policy depth applied by the durability service + * @param[in] max_samples - Number of samples resource-limit policy applied by the durability service + * @param[in] max_instances - Number of instances resource-limit policy applied by the durability service + * @param[in] max_samples_per_instance - Number of samples per instance resource-limit policy applied by the durability service + */ +DDS_EXPORT +void dds_qset_durability_service +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(0, DDS_INFINITY) dds_duration_t service_cleanup_delay, + _In_range_(DDS_HISTORY_KEEP_LAST, DDS_HISTORY_KEEP_ALL) dds_history_kind_t history_kind, + _In_range_(>=, DDS_LENGTH_UNLIMITED) int32_t history_depth, + _In_range_(>=, DDS_LENGTH_UNLIMITED) int32_t max_samples, + _In_range_(>=, DDS_LENGTH_UNLIMITED) int32_t max_instances, + _In_range_(>=, DDS_LENGTH_UNLIMITED) int32_t max_samples_per_instance +); + +/** + * @brief Get the userdata from a qos structure + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] value - Pointer that will store the userdata + * @param[in,out] sz - Pointer that will store the size of userdata + */ +DDS_EXPORT +void dds_qget_userdata +( + _In_ const dds_qos_t * __restrict qos, + _Outptr_result_bytebuffer_maybenull_(*sz) void ** value, + _Out_ size_t * sz +); + +/** + * @brief Get the topicdata from a qos structure + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] value - Pointer that will store the topicdata + * @param[in,out] sz - Pointer that will store the size of topicdata + */ +DDS_EXPORT +void dds_qget_topicdata +( + _In_ const dds_qos_t * __restrict qos, + _Outptr_result_bytebuffer_maybenull_(*sz) void ** value, + _Out_ size_t * sz +); + +/** + * @brief Get the groupdata from a qos structure + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] value - Pointer that will store the groupdata + * @param[in,out] sz - Pointer that will store the size of groupdata + */ +DDS_EXPORT +void dds_qget_groupdata +( + _In_ const dds_qos_t * __restrict qos, + _Outptr_result_bytebuffer_maybenull_(*sz) void ** value, + _Out_ size_t * sz +); + +/** + * @brief Get the durability policy from a qos structure + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] kind - Pointer that will store the durability kind + */ +DDS_EXPORT +void dds_qget_durability +( + _In_ const dds_qos_t * __restrict qos, + _Out_ dds_durability_kind_t *kind +); + +/** + * @brief Get the history policy from a qos structure + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] kind - Pointer that will store the history kind (optional) + * @param[in,out] depth - Pointer that will store the history depth (optional) + */ +DDS_EXPORT +void dds_qget_history +( + _In_ const dds_qos_t * __restrict qos, + _Out_opt_ dds_history_kind_t * kind, + _Out_opt_ int32_t *depth +); + +/** + * @brief Get the resource-limits policy from a qos structure + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] max_samples - Pointer that will store the number of samples resource-limit (optional) + * @param[in,out] max_instances - Pointer that will store the number of instances resource-limit (optional) + * @param[in,out] max_samples_per_instance - Pointer that will store the number of samples per instance resource-limit (optional) + */ +DDS_EXPORT +void dds_qget_resource_limits +( + _In_ const dds_qos_t * __restrict qos, + _Out_opt_ int32_t *max_samples, + _Out_opt_ int32_t *max_instances, + _Out_opt_ int32_t *max_samples_per_instance +); + +/** + * @brief Get the presentation policy from a qos structure + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] access_scope - Pointer that will store access scope kind (optional) + * @param[in,out] coherent_access - Pointer that will store coherent access enable value (optional) + * @param[in,out] ordered_access - Pointer that will store orderede access enable value (optional) + */ +DDS_EXPORT +void dds_qget_presentation +( + _In_ const dds_qos_t * __restrict qos, + _Out_opt_ dds_presentation_access_scope_kind_t *access_scope, + _Out_opt_ bool *coherent_access, + _Out_opt_ bool *ordered_access +); + +/** + * @brief Get the lifespan policy from a qos structure + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] lifespan - Pointer that will store lifespan duration + */ +DDS_EXPORT +void dds_qget_lifespan +( + _In_ const dds_qos_t * __restrict qos, + _Out_ dds_duration_t * lifespan +); + +/** + * @brief Get the deadline policy from a qos structure + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] deadline - Pointer that will store deadline duration + */ +DDS_EXPORT +void dds_qget_deadline +( + _In_ const dds_qos_t * __restrict qos, + _Out_ dds_duration_t * deadline +); + +/** + * @brief Get the latency-budget policy from a qos structure + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] duration - Pointer that will store latency-budget duration + */ +DDS_EXPORT +void dds_qget_latency_budget +( + _In_ const dds_qos_t * __restrict qos, + _Out_ dds_duration_t *duration +); + +/** + * @brief Get the ownership policy from a qos structure + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] kind - Pointer that will store the ownership kind + */ +DDS_EXPORT +void dds_qget_ownership +( + _In_ const dds_qos_t * __restrict qos, + _Out_ dds_ownership_kind_t *kind +); + +/** + * @brief Get the ownership strength qos policy + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] value - Pointer that will store the ownership strength value + */ +DDS_EXPORT +void dds_qget_ownership_strength +( + _In_ const dds_qos_t * __restrict qos, + _Out_ int32_t *value +); + +/** + * @brief Get the liveliness qos policy + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] kind - Pointer that will store the liveliness kind (optional) + * @param[in,out] lease_duration - Pointer that will store the liveliness lease duration (optional) + */ +DDS_EXPORT +void dds_qget_liveliness +( + _In_ const dds_qos_t * __restrict qos, + _Out_opt_ dds_liveliness_kind_t *kind, + _Out_opt_ dds_duration_t *lease_duration +); + +/** + * @brief Get the time-based filter qos policy + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] minimum_separation - Pointer that will store the minimum separation duration (optional) + */ +DDS_EXPORT +void dds_qget_time_based_filter +( + _In_ const dds_qos_t * __restrict qos, + _Out_ dds_duration_t *minimum_separation +); + +/** + * @brief Get the partition qos policy + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] n - Pointer that will store the number of partitions (optional) + * @param[in,out] ps - Pointer that will store the string(s) containing partition name(s) (optional) + */ +DDS_EXPORT +void dds_qget_partition +( + _In_ const dds_qos_t * __restrict qos, + _Out_ uint32_t *n, + _Outptr_opt_result_buffer_all_maybenull_(*n) char *** ps +); + +/** + * @brief Get the reliability qos policy + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] kind - Pointer that will store the reliability kind (optional) + * @param[in,out] max_blocking_time - Pointer that will store the max blocking time for reliable reliability (optional) + */ +DDS_EXPORT +void dds_qget_reliability +( + _In_ const dds_qos_t * __restrict qos, + _Out_opt_ dds_reliability_kind_t *kind, + _Out_opt_ dds_duration_t *max_blocking_time +); + +/** + * @brief Get the transport priority qos policy + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] value - Pointer that will store the transport priority value + */ +DDS_EXPORT +void dds_qget_transport_priority +( + _In_ const dds_qos_t * __restrict qos, + _Out_ int32_t *value +); + +/** + * @brief Get the destination-order qos policy + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] kind - Pointer that will store the destination-order kind + */ +DDS_EXPORT +void dds_qget_destination_order +( + _In_ const dds_qos_t * __restrict qos, + _Out_ dds_destination_order_kind_t *kind +); + +/** + * @brief Get the writer data-lifecycle qos policy + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] autodispose_unregistered_instances - Pointer that will store the autodispose unregistered instances enable value + */ +DDS_EXPORT +void dds_qget_writer_data_lifecycle +( + _In_ const dds_qos_t * __restrict qos, + _Out_ bool * autodispose +); + +/** + * @brief Get the reader data-lifecycle qos policy + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] autopurge_nowriter_samples_delay - Pointer that will store the delay for auto-purging samples from instances in a no-writer state (optional) + * @param[in,out] autopurge_disposed_samples_delay - Pointer that will store the delay for auto-purging of disposed instances (optional) + */ +DDS_EXPORT +void dds_qget_reader_data_lifecycle +( + _In_ const dds_qos_t * __restrict qos, + _Out_opt_ dds_duration_t *autopurge_nowriter_samples_delay, + _Out_opt_ dds_duration_t *autopurge_disposed_samples_delay +); + +/** + * @brief Get the durability-service qos policy values. + * + * @param[in] qos - Pointer to a dds_qos_t structure storing the policy + * @param[in,out] service_cleanup_delay - Pointer that will store the delay for purging of abandoned instances from the durability service (optional) + * @param[in,out] history_kind - Pointer that will store history policy kind applied by the durability service (optional) + * @param[in,out] history_depth - Pointer that will store history policy depth applied by the durability service (optional) + * @param[in,out] max_samples - Pointer that will store number of samples resource-limit policy applied by the durability service (optional) + * @param[in,out] max_instances - Pointer that will store number of instances resource-limit policy applied by the durability service (optional) + * @param[in,out] max_samples_per_instance - Pointer that will store number of samples per instance resource-limit policy applied by the durability service (optional) + */ +DDS_EXPORT void dds_qget_durability_service +( + _In_ const dds_qos_t * qos, + _Out_opt_ dds_duration_t * service_cleanup_delay, + _Out_opt_ dds_history_kind_t * history_kind, + _Out_opt_ int32_t * history_depth, + _Out_opt_ int32_t * max_samples, + _Out_opt_ int32_t * max_instances, + _Out_opt_ int32_t * max_samples_per_instance +); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/include/ddsc/dds_public_status.h b/src/core/ddsc/include/ddsc/dds_public_status.h new file mode 100644 index 0000000..7ef5d32 --- /dev/null +++ b/src/core/ddsc/include/ddsc/dds_public_status.h @@ -0,0 +1,491 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +/** @file + * + * @brief DDS C Communication Status API + * + * This header file defines the public API of the Communication Status in the + * CycloneDDS C language binding. + */ +#ifndef DDS_STATUS_H +#define DDS_STATUS_H + +#include "os/os_public.h" +#include "ddsc/dds_export.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +/* + Listeners implemented as structs containing callback functions + that take listener status types as arguments. +*/ + +/* Listener status types */ +/** + * \ref DCPS_Status_OfferedDeadlineMissed + */ +typedef struct dds_offered_deadline_missed_status +{ + uint32_t total_count; + int32_t total_count_change; + dds_instance_handle_t last_instance_handle; +} +dds_offered_deadline_missed_status_t; + +/** + * \ref DCPS_Status_OfferedIncompatibleQoS + */ +typedef struct dds_offered_incompatible_qos_status +{ + uint32_t total_count; + int32_t total_count_change; + uint32_t last_policy_id; +} +dds_offered_incompatible_qos_status_t; + +/** + * \ref DCPS_Status_PublicationMatched + */ +typedef struct dds_publication_matched_status +{ + uint32_t total_count; + int32_t total_count_change; + uint32_t current_count; + int32_t current_count_change; + dds_instance_handle_t last_subscription_handle; +} +dds_publication_matched_status_t; + +/** + * \ref DCPS_Status_LivelinessLost + */ +typedef struct dds_liveliness_lost_status +{ + uint32_t total_count; + int32_t total_count_change; +} +dds_liveliness_lost_status_t; + +/** + * \ref DCPS_Status_SubscriptionMatched + */ +typedef struct dds_subscription_matched_status +{ + uint32_t total_count; + int32_t total_count_change; + uint32_t current_count; + int32_t current_count_change; + dds_instance_handle_t last_publication_handle; +} +dds_subscription_matched_status_t; + +/** + * dds_sample_rejected_status_kind + */ +typedef enum +{ + DDS_NOT_REJECTED, + DDS_REJECTED_BY_INSTANCES_LIMIT, + DDS_REJECTED_BY_SAMPLES_LIMIT, + DDS_REJECTED_BY_SAMPLES_PER_INSTANCE_LIMIT +} +dds_sample_rejected_status_kind; + +/** + * \ref DCPS_Status_SampleRejected + */ +typedef struct dds_sample_rejected_status +{ + uint32_t total_count; + int32_t total_count_change; + dds_sample_rejected_status_kind last_reason; + dds_instance_handle_t last_instance_handle; +} +dds_sample_rejected_status_t; + +/** + * \ref DCPS_Status_LivelinessChanged + */ +typedef struct dds_liveliness_changed_status +{ + uint32_t alive_count; + uint32_t not_alive_count; + int32_t alive_count_change; + int32_t not_alive_count_change; + dds_instance_handle_t last_publication_handle; +} +dds_liveliness_changed_status_t; + +/** + * \ref DCPS_Status_RequestedDeadlineMissed + */ +typedef struct dds_requested_deadline_missed_status +{ + uint32_t total_count; + int32_t total_count_change; + dds_instance_handle_t last_instance_handle; +} +dds_requested_deadline_missed_status_t; + +/** + * \ref DCPS_Status_RequestedIncompatibleQoS + */ +typedef struct dds_requested_incompatible_qos_status +{ + uint32_t total_count; + int32_t total_count_change; + uint32_t last_policy_id; +} +dds_requested_incompatible_qos_status_t; + +/** + * \ref DCPS_Status_SampleLost + */ +typedef struct dds_sample_lost_status +{ + uint32_t total_count; + int32_t total_count_change; +} +dds_sample_lost_status_t; + +/** + * \ref DCPS_Status_InconsistentTopic + */ +typedef struct dds_inconsistent_topic_status +{ + uint32_t total_count; + int32_t total_count_change; +} +dds_inconsistent_topic_status_t; + + +/* + get_ APIs return the status of an entity and resets the status +*/ + +/** + * @brief Get INCONSISTENT_TOPIC status + * + * This operation gets the status value corresponding to INCONSISTENT_TOPIC + * and reset the status. The value can be obtained, only if the status is enabled for an entity. + * NULL value for status is allowed and it will reset the trigger value when status is enabled. + * + * @param[in] topic The entity to get the status + * @param[out] status The pointer to \ref DCPS_Status_InconsistentTopic to get the status + * + * @returns 0 - Success + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC) +DDS_EXPORT dds_return_t +dds_get_inconsistent_topic_status ( + _In_ dds_entity_t topic, + _Out_opt_ dds_inconsistent_topic_status_t * status); + +/** + * @brief Get PUBLICATION_MATCHED status + * + * This operation gets the status value corresponding to PUBLICATION_MATCHED + * and reset the status. The value can be obtained, only if the status is enabled for an entity. + * NULL value for status is allowed and it will reset the trigger value when status is enabled. + * + * @param[in] writer The entity to get the status + * @param[out] status The pointer to \ref DCPS_Status_PublicationMatched to get the status + * + * @returns 0 - Success + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER)) +DDS_EXPORT dds_return_t +dds_get_publication_matched_status ( + _In_ dds_entity_t writer, + _Out_opt_ dds_publication_matched_status_t * status); + +/** + * @brief Get LIVELINESS_LOST status + * + * This operation gets the status value corresponding to LIVELINESS_LOST + * and reset the status. The value can be obtained, only if the status is enabled for an entity. + * NULL value for status is allowed and it will reset the trigger value when status is enabled. + * + * @param[in] writer The entity to get the status + * @param[out] status The pointer to \ref DCPS_Status_LivelinessLost to get the status + * + * @returns 0 - Success + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER)) +DDS_EXPORT dds_return_t dds_get_liveliness_lost_status ( + _In_ dds_entity_t writer, + _Out_opt_ dds_liveliness_lost_status_t * status); + +/** + * @brief Get OFFERED_DEADLINE_MISSED status + * + * This operation gets the status value corresponding to OFFERED_DEADLINE_MISSED + * and reset the status. The value can be obtained, only if the status is enabled for an entity. + * NULL value for status is allowed and it will reset the trigger value when status is enabled. + * + * @param[in] writer The entity to get the status + * @param[out] status The pointer to \ref DCPS_Status_OfferedDeadlineMissed to get the status + * + * @returns 0 - Success + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER)) +DDS_EXPORT dds_return_t +dds_get_offered_deadline_missed_status( + _In_ dds_entity_t writer, + _Out_opt_ dds_offered_deadline_missed_status_t *status); + +/** + * @brief Get OFFERED_INCOMPATIBLE_QOS status + * + * This operation gets the status value corresponding to OFFERED_INCOMPATIBLE_QOS + * and reset the status. The value can be obtained, only if the status is enabled for an entity. + * NULL value for status is allowed and it will reset the trigger value when status is enabled. + * + * @param[in] writer The writer entity to get the status + * @param[out] status The pointer to \ref DCPS_Status_OfferedIncompatibleQoS to get the status + * + * @returns 0 - Success + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER)) +DDS_EXPORT dds_return_t +dds_get_offered_incompatible_qos_status ( + _In_ dds_entity_t writer, + _Out_opt_ dds_offered_incompatible_qos_status_t * status); + +/** + * @brief Get SUBSCRIPTION_MATCHED status + * + * This operation gets the status value corresponding to SUBSCRIPTION_MATCHED + * and reset the status. The value can be obtained, only if the status is enabled for an entity. + * NULL value for status is allowed and it will reset the trigger value when status is enabled. + * + * @param[in] reader The reader entity to get the status + * @param[out] status The pointer to \ref DCPS_Status_SubscriptionMatched to get the status + * + * @returns 0 - Success + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +DDS_EXPORT dds_return_t +dds_get_subscription_matched_status ( + _In_ dds_entity_t reader, + _Out_opt_ dds_subscription_matched_status_t * status); + +/** + * @brief Get LIVELINESS_CHANGED status + * + * This operation gets the status value corresponding to LIVELINESS_CHANGED + * and reset the status. The value can be obtained, only if the status is enabled for an entity. + * NULL value for status is allowed and it will reset the trigger value when status is enabled. + * + * @param[in] reader The entity to get the status + * @param[out] status The pointer to \ref DCPS_Status_LivelinessChanged to get the status + * + * @returns 0 - Success + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +DDS_EXPORT dds_return_t +dds_get_liveliness_changed_status ( + _In_ dds_entity_t reader, + _Out_opt_ dds_liveliness_changed_status_t * status); + +/** + * @brief Get SAMPLE_REJECTED status + * + * This operation gets the status value corresponding to SAMPLE_REJECTED + * and reset the status. The value can be obtained, only if the status is enabled for an entity. + * NULL value for status is allowed and it will reset the trigger value when status is enabled. + * + * @param[in] reader The entity to get the status + * @param[out] status The pointer to \ref DCPS_Status_SampleRejected to get the status + * + * @returns 0 - Success + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +DDS_EXPORT dds_return_t +dds_get_sample_rejected_status ( + _In_ dds_entity_t reader, + _Out_opt_ dds_sample_rejected_status_t * status); + +/** + * @brief Get SAMPLE_LOST status + * + * This operation gets the status value corresponding to SAMPLE_LOST + * and reset the status. The value can be obtained, only if the status is enabled for an entity. + * NULL value for status is allowed and it will reset the trigger value when status is enabled. + * + * @param[in] reader The entity to get the status + * @param[out] status The pointer to \ref DCPS_Status_SampleLost to get the status + * + * @returns A dds_return_t indicating success or failure + * + * @retval DDS_RETCODE_OK + * Success + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +DDS_EXPORT dds_return_t +dds_get_sample_lost_status ( + _In_ dds_entity_t reader, + _Out_opt_ dds_sample_lost_status_t * status); + +/** + * @brief Get REQUESTED_DEADLINE_MISSED status + * + * This operation gets the status value corresponding to REQUESTED_DEADLINE_MISSED + * and reset the status. The value can be obtained, only if the status is enabled for an entity. + * NULL value for status is allowed and it will reset the trigger value when status is enabled. + * + * @param[in] reader The entity to get the status + * @param[out] status The pointer to \ref DCPS_Status_RequestedDeadlineMissed to get the status + * + * @returns A dds_return_t indicating success or failure + * + * @retval DDS_RETCODE_OK + * Success + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +DDS_EXPORT dds_return_t +dds_get_requested_deadline_missed_status ( + _In_ dds_entity_t reader, + _Out_opt_ dds_requested_deadline_missed_status_t * status); + +/** + * @brief Get REQUESTED_INCOMPATIBLE_QOS status + * + * This operation gets the status value corresponding to REQUESTED_INCOMPATIBLE_QOS + * and reset the status. The value can be obtained, only if the status is enabled for an entity. + * NULL value for status is allowed and it will reset the trigger value when status is enabled. + * + * @param[in] reader The entity to get the status + * @param[out] status The pointer to \ref DCPS_Status_RequestedIncompatibleQoS to get the status + * + * @returns A dds_return_t indicating success or failure + * + * @retval DDS_RETCODE_OK + * Success + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +DDS_EXPORT dds_return_t +dds_get_requested_incompatible_qos_status ( + _In_ dds_entity_t reader, + _Out_opt_ dds_requested_incompatible_qos_status_t * status); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/include/ddsc/dds_public_stream.h b/src/core/ddsc/include/ddsc/dds_public_stream.h new file mode 100644 index 0000000..db26b83 --- /dev/null +++ b/src/core/ddsc/include/ddsc/dds_public_stream.h @@ -0,0 +1,101 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +/** @file + * + * @brief DDS C Stream API + * + * This header file defines the public API of the Streams in the + * CycloneDDS C language binding. + */ +#ifndef DDS_STREAM_H +#define DDS_STREAM_H + +#include "os/os_public.h" +#include +#include "ddsc/dds_export.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +struct dds_sequence; + +typedef union +{ + uint8_t * p8; + uint16_t * p16; + uint32_t * p32; + uint64_t * p64; + float * pf; + double * pd; + void * pv; +} +dds_uptr_t; + +typedef struct dds_stream +{ + dds_uptr_t m_buffer; /* Union of pointers to start of buffer */ + size_t m_size; /* Buffer size */ + size_t m_index; /* Read/write offset from start of buffer */ + bool m_endian; /* Endian: big (false) or little (true) */ + bool m_failed; /* Attempt made to read beyond end of buffer */ +} +dds_stream_t; + +#define DDS_STREAM_BE false +#define DDS_STREAM_LE true + +DDS_EXPORT dds_stream_t * dds_stream_create (size_t size); +DDS_EXPORT void dds_stream_delete (dds_stream_t * st); +DDS_EXPORT void dds_stream_fini (dds_stream_t * st); +DDS_EXPORT void dds_stream_reset (dds_stream_t * st); +DDS_EXPORT void dds_stream_init (dds_stream_t * st, size_t size); +DDS_EXPORT void dds_stream_grow (dds_stream_t * st, size_t size); +DDS_EXPORT bool dds_stream_endian (void); + +DDS_EXPORT bool dds_stream_read_bool (dds_stream_t * is); +DDS_EXPORT uint8_t dds_stream_read_uint8 (dds_stream_t * is); +DDS_EXPORT uint16_t dds_stream_read_uint16 (dds_stream_t * is); +DDS_EXPORT uint32_t dds_stream_read_uint32 (dds_stream_t * is); +DDS_EXPORT uint64_t dds_stream_read_uint64 (dds_stream_t * is); +DDS_EXPORT float dds_stream_read_float (dds_stream_t * is); +DDS_EXPORT double dds_stream_read_double (dds_stream_t * is); +DDS_EXPORT char * dds_stream_read_string (dds_stream_t * is); +DDS_EXPORT void dds_stream_read_buffer (dds_stream_t * is, uint8_t * buffer, uint32_t len); + +#define dds_stream_read_char(s) ((char) dds_stream_read_uint8 (s)) +#define dds_stream_read_int8(s) ((int8_t) dds_stream_read_uint8 (s)) +#define dds_stream_read_int16(s) ((int16_t) dds_stream_read_uint16 (s)) +#define dds_stream_read_int32(s) ((int32_t) dds_stream_read_uint32 (s)) +#define dds_stream_read_int64(s) ((int64_t) dds_stream_read_uint64 (s)) + +DDS_EXPORT void dds_stream_write_bool (dds_stream_t * os, bool val); +DDS_EXPORT void dds_stream_write_uint8 (dds_stream_t * os, uint8_t val); +DDS_EXPORT void dds_stream_write_uint16 (dds_stream_t * os, uint16_t val); +DDS_EXPORT void dds_stream_write_uint32 (dds_stream_t * os, uint32_t val); +DDS_EXPORT void dds_stream_write_uint64 (dds_stream_t * os, uint64_t val); +DDS_EXPORT void dds_stream_write_float (dds_stream_t * os, float val); +DDS_EXPORT void dds_stream_write_double (dds_stream_t * os, double val); +DDS_EXPORT void dds_stream_write_string (dds_stream_t * os, const char * val); +DDS_EXPORT void dds_stream_write_buffer (dds_stream_t * os, uint32_t len, uint8_t * buffer); + +#define dds_stream_write_char(s,v) (dds_stream_write_uint8 ((s), (uint8_t)(v))) +#define dds_stream_write_int8(s,v) (dds_stream_write_uint8 ((s), (uint8_t)(v))) +#define dds_stream_write_int16(s,v) (dds_stream_write_uint16 ((s), (uint16_t)(v))) +#define dds_stream_write_int32(s,v) (dds_stream_write_uint32 ((s), (uint32_t)(v))) +#define dds_stream_write_int64(s,v) (dds_stream_write_uint64 ((s), (uint64_t)(v))) + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/include/ddsc/dds_public_time.h b/src/core/ddsc/include/ddsc/dds_public_time.h new file mode 100644 index 0000000..9828b79 --- /dev/null +++ b/src/core/ddsc/include/ddsc/dds_public_time.h @@ -0,0 +1,96 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +/** @file + * + * @brief DDS C Time support API + * + * This header file defines the public API of the in the + * CycloneDDS C language binding. + */ +#ifndef DDS_TIME_H +#define DDS_TIME_H + +#include "os/os_public.h" +#include "ddsc/dds_export.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +/* + Times are represented using a 64-bit signed integer, encoding + nanoseconds since the epoch. Considering the nature of these + systems, one would best use TAI, International Atomic Time, rather + than something UTC, but availability may be limited. + + Valid times are non-negative and times up to 2**63-2 can be + represented. 2**63-1 is defined to represent, essentially, "never". + This is good enough for a couple of centuries. +*/ + +/** Absolute Time definition */ +typedef int64_t dds_time_t; + +/** Relative Time definition */ +typedef int64_t dds_duration_t; + +/** @name Macro definition for time units in nanoseconds. + @{**/ +#define DDS_NSECS_IN_SEC 1000000000LL +#define DDS_NSECS_IN_MSEC 1000000LL +#define DDS_NSECS_IN_USEC 1000LL +/** @}*/ + +/** @name Infinite timeout for indicate absolute time */ +#define DDS_NEVER ((dds_time_t) INT64_MAX) + +/** @name Infinite timeout for relative time */ +#define DDS_INFINITY ((dds_duration_t) INT64_MAX) + +/** @name Macro definition for time conversion from nanoseconds + @{**/ +#define DDS_SECS(n) ((n) * DDS_NSECS_IN_SEC) +#define DDS_MSECS(n) ((n) * DDS_NSECS_IN_MSEC) +#define DDS_USECS(n) ((n) * DDS_NSECS_IN_USEC) +/** @}*/ + +/** + * Description : This operation returns the current time (in nanoseconds) + * + * Arguments : + * -# Returns current time + */ +DDS_EXPORT dds_time_t dds_time (void); + +/** + * Description : This operation blocks the calling thread until the relative time + * n has elapsed + * + * Arguments : + * -# n Relative Time to block a thread + */ +DDS_EXPORT void dds_sleepfor (dds_duration_t n); + +/** + * Description : This operation blocks the calling thread until the absolute time + * n has elapsed + * + * Arguments : + * -# n absolute Time to block a thread + */ +DDS_EXPORT void dds_sleepuntil (dds_time_t n); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/include/ddsc/ddsv2.h b/src/core/ddsc/include/ddsc/ddsv2.h new file mode 100644 index 0000000..9e9731e --- /dev/null +++ b/src/core/ddsc/include/ddsc/ddsv2.h @@ -0,0 +1,2927 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef DDS_H +#define DDS_H + +/** @file + * + * @brief C DDS header + */ + +#if defined (__cplusplus) +#define restrict +#endif + +#include "os/os_public.h" +#include "ddsc/dds_export.h" + +/* TODO: Move to appropriate location */ +typedef _Return_type_success_(return >= 0) int32_t dds_return_t; +typedef _Return_type_success_(return > 0) int32_t dds_entity_t; + +/* Sub components */ + +#include "ddsc/dds_public_stream.h" +#include "ddsc/dds_public_impl.h" +#include "ddsc/dds_public_alloc.h" +#include "ddsc/dds_public_time.h" +#include "ddsc/dds_public_qos.h" +#include "ddsc/dds_public_error.h" +#include "ddsc/dds_public_status.h" +#include "ddsc/dds_public_listener.h" +#include "ddsc/dds_public_log.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +/** + * Description : Returns the default DDS domain id. This can be configured + * in xml or set as an evironment variable ({DDSC_PROJECT_NAME_NOSPACE_CAPS}_DOMAIN). + * + * Arguments : + * -# None + * -# Returns the default domain id + */ +DDS_EXPORT dds_domainid_t dds_get_default_domainid (void); + +/** @name Communication Status definitions + @{**/ +#define DDS_INCONSISTENT_TOPIC_STATUS 1u +#define DDS_OFFERED_DEADLINE_MISSED_STATUS 2u +#define DDS_REQUESTED_DEADLINE_MISSED_STATUS 4u +#define DDS_OFFERED_INCOMPATIBLE_QOS_STATUS 32u +#define DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS 64u +#define DDS_SAMPLE_LOST_STATUS 128u +#define DDS_SAMPLE_REJECTED_STATUS 256u +#define DDS_DATA_ON_READERS_STATUS 512u +#define DDS_DATA_AVAILABLE_STATUS 1024u +#define DDS_LIVELINESS_LOST_STATUS 2048u +#define DDS_LIVELINESS_CHANGED_STATUS 4096u +#define DDS_PUBLICATION_MATCHED_STATUS 8192u +#define DDS_SUBSCRIPTION_MATCHED_STATUS 16384u +/** @}*/ + +/** + * dds_sample_state_t + * \brief defines the state for a data value + * -# DDS_SST_READ - DataReader has already accessed the sample by read + * -# DDS_SST_NOT_READ - DataReader has not accessed that sample before + */ +typedef enum dds_sample_state +{ + DDS_SST_READ = DDS_READ_SAMPLE_STATE, + DDS_SST_NOT_READ = DDS_NOT_READ_SAMPLE_STATE +} +dds_sample_state_t; + +/** + * dds_view_state_t + * \brief defines the view state of an instance relative to the samples + * -# DDS_VST_NEW - DataReader is accessing the sample for the first time when the + * instance is alive + * -# DDS_VST_OLD - DataReader has accessed the sample before + */ +typedef enum dds_view_state +{ + DDS_VST_NEW = DDS_NEW_VIEW_STATE, + DDS_VST_OLD = DDS_NOT_NEW_VIEW_STATE +} +dds_view_state_t; + +/** + * dds_instance_state_t + * \brief defines the state of the instance + * -# DDS_IST_ALIVE - Samples received for the instance from the live data writers + * -# DDS_IST_NOT_ALIVE_DISPOSED - Instance was explicitly disposed by the data writer + * -# DDS_IST_NOT_ALIVE_NO_WRITERS - Instance has been declared as not alive by data reader + * as there are no live data writers writing that instance + */ +typedef enum dds_instance_state +{ + DDS_IST_ALIVE = DDS_ALIVE_INSTANCE_STATE, + DDS_IST_NOT_ALIVE_DISPOSED = DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE, + DDS_IST_NOT_ALIVE_NO_WRITERS = DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE +} +dds_instance_state_t; + +/** + * Structure dds_sample_info_t - contains information about the associated data value + * -# sample_state - \ref dds_sample_state_t + * -# view_state - \ref dds_view_state_t + * -# instance_state - \ref dds_instance_state_t + * -# valid_data - indicates whether there is a data associated with a sample + * - true, indicates the data is valid + * - false, indicates the data is invalid, no data to read + * -# source_timestamp - timestamp of a data instance when it is written + * -# instance_handle - handle to the data instance + * -# publication_handle - handle to the publisher + * -# disposed_generation_count - count of instance state change from + * NOT_ALIVE_DISPOSED to ALIVE + * -# no_writers_generation_count - count of instance state change from + * NOT_ALIVE_NO_WRITERS to ALIVE + * -# sample_rank - indicates the number of samples of the same instance + * that follow the current one in the collection + * -# generation_rank - difference in generations between the sample and most recent sample + * of the same instance that appears in the returned collection + * -# absolute_generation_rank - difference in generations between the sample and most recent sample + * of the same instance when read/take was called + * -# reception_timestamp - timestamp of a data instance when it is added to a read queue + */ +typedef struct dds_sample_info +{ + dds_sample_state_t sample_state; + dds_view_state_t view_state; + dds_instance_state_t instance_state; + bool valid_data; + dds_time_t source_timestamp; + dds_instance_handle_t instance_handle; + dds_instance_handle_t publication_handle; + uint32_t disposed_generation_count; + uint32_t no_writers_generation_count; + uint32_t sample_rank; + uint32_t generation_rank; + uint32_t absolute_generation_rank; + dds_time_t reception_timestamp; /* NOTE: VLite extension */ +} +dds_sample_info_t; + +/* + All entities are represented by a process-private handle, with one + call to enable an entity when it was created disabled. + An entity is created enabled by default. + Note: disabled creation is currently not supported. +*/ + +/** + * @brief Enable entity. + * + * @note Delayed entity enabling is not supported yet (CHAM-96). + * + * This operation enables the dds_entity_t. Created dds_entity_t objects can start in + * either an enabled or disabled state. This is controlled by the value of the + * entityfactory policy on the corresponding parent entity for the given + * entity. Enabled entities are immediately activated at creation time meaning + * all their immutable QoS settings can no longer be changed. Disabled Entities are not + * yet activated, so it is still possible to change their immutable QoS settings. However, + * once activated the immutable QoS settings can no longer be changed. + * Creating disabled entities can make sense when the creator of the DDS_Entity + * does not yet know which QoS settings to apply, thus allowing another piece of code + * to set the QoS later on. + * + * The default setting of DDS_EntityFactoryQosPolicy is such that, by default, + * entities are created in an enabled state so that it is not necessary to explicitly call + * dds_enable on newly-created entities. + * + * The dds_enable operation produces the same results no matter how + * many times it is performed. Calling dds_enable on an already + * enabled DDS_Entity returns DDS_RETCODE_OK and has no effect. + * + * If an Entity has not yet been enabled, the only operations that can be invoked + * on it are: the ones to set, get or copy the QosPolicy settings, the ones that set + * (or get) the Listener, the ones that get the Status and the dds_get_status_changes + * operation (although the status of a disabled entity never changes). Other operations + * will return the error DDS_RETCODE_NOT_ENABLED. + * + * Entities created with a parent that is disabled, are created disabled regardless of + * the setting of the entityfactory policy. + * + * Calling dds_enable on an Entity whose parent is not enabled + * will fail and return DDS_RETCODE_PRECONDITION_NOT_MET. + * + * If the entityfactory policy has autoenable_created_entities + * set to TRUE, the dds_enable operation on the parent will + * automatically enable all child entities created with the parent. + * + * The Listeners associated with an Entity are not called until the + * Entity is enabled. Conditions associated with an Entity that + * is not enabled are "inactive", that is, have a trigger_value which is FALSE. + * + * @param[in] e The entity to enable. + * + * @returns 0 - Success (DDS_RETCODE_OK). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_OK + * The listeners of to the entity have been successfully been + * copied into the specified listener parameter. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The parent of the given Entity is not enabled. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_enable( + _In_ dds_entity_t entity); + +/* + All entities are represented by a process-private handle, with one + call to delete an entity and all entities it logically contains. + That is, it is equivalent to combination of + delete_contained_entities and delete_xxx in the DCPS API. +*/ + +/** + * @brief Delete given entity. + * + * This operation will delete the given entity. It will also automatically + * delete all its children, childrens' children, etc entities. + * + * TODO: Link to generic dds entity relations documentation. + * + * @param[in] entity Entity from which to get its parent. + * + * @returns 0 - Success (DDS_RETCODE_OK). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * The entity and its children (recursive are deleted). + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT dds_return_t +dds_delete( + _In_ dds_entity_t entity); + + +/** + * @brief Get entity publisher. + * + * This operation returns the publisher to which the given entity belongs. + * For instance, it will return the Publisher that was used when + * creating a DataWriter (when that DataWriter was provided here). + * + * TODO: Link to generic dds entity relations documentation. + * + * @param[in] entity Entity from which to get its publisher. + * + * @returns >0 - Success (valid entity handle). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER)) +DDS_EXPORT dds_entity_t +dds_get_publisher( + _In_ dds_entity_t writer); + + +/** + * @brief Get entity subscriber. + * + * This operation returns the subscriber to which the given entity belongs. + * For instance, it will return the Subscriber that was used when + * creating a DataReader (when that DataReader was provided here). + * + * TODO: Link to generic dds entity relations documentation. + * + * @param[in] entity Entity from which to get its subscriber. + * + * @returns >0 - Success (valid entity handle). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY) ) +DDS_EXPORT dds_entity_t +dds_get_subscriber( + _In_ dds_entity_t entity); + + +/** + * @brief Get entity datareader. + * + * This operation returns the datareader to which the given entity belongs. + * For instance, it will return the DataReader that was used when + * creating a ReadCondition (when that ReadCondition was provided here). + * + * TODO: Link to generic dds entity relations documentation. + * + * @param[in] entity Entity from which to get its datareader. + * + * @returns >0 - Success (valid entity handle). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY) ) +DDS_EXPORT dds_entity_t +dds_get_datareader( + _In_ dds_entity_t condition); + + + +/** + * @brief Get the mask of a condition. + * + * This operation returns the mask that was used to create the given + * condition. + * + * @param[in] condition Read or Query condition that has a mask. + * + * @returns 0 - Success (given mask is set). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The mask arg is NULL. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY) ) +DDS_EXPORT _Check_return_ dds_return_t +dds_get_mask( + _In_ dds_entity_t condition, + _Out_ uint32_t *mask); + +/* TODO: document. */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_instancehandle_get( + _In_ dds_entity_t entity, + _Out_ dds_instance_handle_t *ihdl); + +/* + All entities have a set of "status conditions" (following the DCPS + spec), read peeks, take reads & resets (analogously to read & take + operations on reader). The "mask" allows operating only on a subset + of the statuses. Enabled status analogously to DCPS spec. +*/ + + +/** + * Description : Read the status(es) set for the entity based on the enabled + * status and mask set. This operation does not clear the read status(es). + * + * Arguments : + * -# e Entity on which the status has to be read + * -# status Returns the status set on the entity, based on the enabled status + * -# mask Filter the status condition to be read (can be NULL) + * -# Returns 0 on success, or a non-zero error value if the mask does not + * correspond to the entity + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_read_status( + _In_ dds_entity_t entity, + _Out_ uint32_t *status, + _In_ uint32_t mask); + +/** + * Description : Read the status(es) set for the entity based on the enabled + * status and mask set. This operation clears the status set after reading. + * + * Arguments : + * -# e Entity on which the status has to be read + * -# status Returns the status set on the entity, based on the enabled status + * -# mask Filter the status condition to be read (can be NULL) + * -# Returns 0 on success, or a non-zero error value if the mask does not + * correspond to the entity + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_take_status( + _In_ dds_entity_t entity, + _Out_ uint32_t *status, + _In_ uint32_t mask); + +/** + * Description : Returns the status changes since they were last read. + * + * Arguments : + * -# e Entity on which the statuses are read + * -# Returns the curent set of triggered statuses. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_get_status_changes( + _In_ dds_entity_t entity, + _Out_ uint32_t *status); + +/** + * Description : This operation returns the status enabled on the entity + * + * Arguments : + * -# e Entity to get the status + * -# Returns the status that are enabled for the entity + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_get_enabled_status( + _In_ dds_entity_t entity, + _Out_ uint32_t *status); + + +/** + * Description : This operation enables the status(es) based on the mask set + * + * Arguments : + * -# e Entity to enable the status + * -# mask Status value that indicates the status to be enabled + * -# Returns 0 on success, or a non-zero error value indicating failure if the mask + * does not correspond to the entity. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT dds_return_t +dds_set_enabled_status( + _In_ dds_entity_t entity, + _In_ uint32_t mask); + +/* + Almost all entities have get/set qos operations defined on them, + again following the DCPS spec. But unlike the DCPS spec, the + "present" field in qos_t allows one to initialize just the one QoS + one wants to set & pass it to set_qos. +*/ + +/** + * @brief Get entity QoS policies. + * + * This operation allows access to the existing set of QoS policies + * for the entity. + * + * TODO: Link to generic QoS information documentation. + * + * @param[in] e Entity on which to get qos + * @param[out] qos Pointer to the qos structure that returns the set policies + * + * @returns 0 - Success (DDS_RETCODE_OK). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_OK + * The existing set of QoS policy values applied to the + * entity has successfully been copied into the specified + * qos parameter. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The qos parameter is NULL. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_get_qos( + _In_ dds_entity_t entity, + _Out_ dds_qos_t *qos); + + +/** + * @brief Set entity QoS policies. + * + * This operation replaces the existing set of Qos Policy settings for an + * entity. The parameter qos must contain the struct with the QosPolicy + * settings which is checked for self-consistency. + * + * The set of QosPolicy settings specified by the qos parameter are applied on + * top of the existing QoS, replacing the values of any policies previously set + * (provided, the operation returned DDS_RETCODE_OK). + * + * Not all policies are changeable when the entity is enabled. + * + * TODO: Link to generic QoS information documentation. + * + * @note Currently only Latency Budget and Ownership Strength are changeable QoS + * that can be set. + * + * @param[in] e Entity from which to get qos + * @param[in] qos Pointer to the qos structure that provides the policies + * + * @returns 0 - Success (DDS_RETCODE_OK). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_OK + * The new QoS policies are set. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The qos parameter is NULL. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_IMMUTABLE_POLICY + * The entity is enabled and one or more of the policies of + * the QoS are immutable. + * @retval DDS_RETCODE_INCONSISTENT_POLICY + * A few policies within the QoS are not consistent with + * each other. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_set_qos( + _In_ dds_entity_t entity, + _In_ const dds_qos_t * qos); + +/* + Get or set listener associated with an entity, type of listener + provided much match type of entity. +*/ + +/** + * @brief Get entity listeners. + * + * This operation allows access to the existing listeners attached to + * the entity. + * + * TODO: Link to (generic) Listener and status information. + * + * @param[in] e Entity on which to get the listeners + * @param[out] listener Pointer to the listener structure that returns the + * set of listener callbacks. + * + * @returns 0 - Success (DDS_RETCODE_OK). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_OK + * The listeners of to the entity have been successfully been + * copied into the specified listener parameter. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The listener parameter is NULL. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_get_listener( + _In_ dds_entity_t entity, + _Out_ dds_listener_t * listener); + + +/** + * @brief Set entity listeners. + * + * This operation attaches a dds_listener_t to the dds_entity_t. Only one + * Listener can be attached to each Entity. If a Listener was already + * attached, this operation will replace it with the new one. In other + * words, all related callbacks are replaced (possibly with NULL). + * + * When listener parameter is NULL, all listener callbacks that were possibly + * set on the Entity will be removed. + * + * @note Not all listener callbacks are related to all entities. + * + * TODO: Link to (generic) Listener and status information. + * + * Communication Status
+ * For each communication status, the StatusChangedFlag flag is initially set to + * FALSE. It becomes TRUE whenever that plain communication status changes. For + * each plain communication status activated in the mask, the associated + * Listener callback is invoked and the communication status is reset + * to FALSE, as the listener implicitly accesses the status which is passed as a + * parameter to that operation. + * The status is reset prior to calling the listener, so if the application calls + * the get_ from inside the listener it will see the + * status already reset. + * + * Status Propagation
+ * In case a related callback within the Listener is not set, the Listener of + * the Parent entity is called recursively, until a Listener with the appropriate + * callback set has been found and called. This allows the application to set + * (for instance) a default behaviour in the Listener of the containing Publisher + * and a DataWriter specific behaviour when needed. In case the callback is not + * set in the Publishers' Listener either, the communication status will be + * propagated to the Listener of the DomainParticipant of the containing + * DomainParticipant. In case the callback is not set in the DomainParticipants' + * Listener either, the Communication Status flag will be set, resulting in a + * possible WaitSet trigger. + * + * @param[in] e Entity on which to get the listeners + * @param[in] listener Pointer to the listener structure that contains the + * set of listener callbacks (maybe NULL). + * + * @returns 0 - Success (DDS_RETCODE_OK). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_OK + * The listeners of to the entity have been successfully been + * copied into the specified listener parameter. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_set_listener( + _In_ dds_entity_t entity, + _In_opt_ const dds_listener_t * listener); + +/* + Creation functions for various entities. Creating a subscriber or + publisher is optional: if one creates a reader as a descendant of a + participant, it is as if a subscriber is created specially for + that reader. + + QoS default values are those of the DDS specification, but the + inheritance rules are different: + + * publishers and subscribers inherit from the participant QoS + * readers and writers always inherit from the topic QoS + * the QoS's present in the "qos" parameter override the inherited values +*/ + +/** + * @brief Creates a new instance of a DDS participant in a domain + * + * If domain is set (not DDS_DOMAIN_DEFAULT) then it must match if the domain has also + * been configured or an error status will be returned. Currently only a single domain + * can be configured by setting the environment variable {DDSC_PROJECT_NAME_NOSPACE_CAPS}_DOMAIN, + * if this is not set the the default domain is 0. Valid values for domain id are between 0 and 230. + * + * + * @param[in] domain - The domain in which to create the participant (can be DDS_DOMAIN_DEFAULT) + * @param[in] qos - The QoS to set on the new participant (can be NULL) + * @param[in] listener - Any listener functions associated with the new participant (can be NULL) + * + * @returns >0 - Success (valid handle of a participant entity). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + */ +DDS_EXPORT _Must_inspect_result_ dds_entity_t +dds_create_participant( + _In_ const dds_domainid_t domain, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener); + + + +/** + * @brief Get entity parent. + * + * This operation returns the parent to which the given entity belongs. + * For instance, it will return the Participant that was used when + * creating a Publisher (when that Publisher was provided here). + * + * TODO: Link to generic dds entity relations documentation. + * + * @param[in] entity Entity from which to get its parent. + * + * @returns >0 - Success (valid entity handle). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_entity_t +dds_get_parent( + _In_ dds_entity_t entity); + + +/** + * @brief Get entity participant. + * + * This operation returns the participant to which the given entity belongs. + * For instance, it will return the Participant that was used when + * creating a Publisher that was used to create a DataWriter (when that + * DataWriter was provided here). + * + * TODO: Link to generic dds entity relations documentation. + * + * @param[in] entity Entity from which to get its participant. + * + * @returns >0 - Success (valid entity handle). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_entity_t +dds_get_participant ( + _In_ dds_entity_t entity); + + +/** + * @brief Get entity children. + * + * This operation returns the children that the entity contains. + * For instance, it will return all the Topics, Publishers and Subscribers + * of the Participant that was used to create those entities (when that + * Participant is provided here). + * + * This functions takes a pre-allocated list to put the children in and + * will return the number of found children. It is possible that the given + * size of the list is not the same as the number of found children. If + * less children are found, then the last few entries in the list are + * untouched. When more children are found, then only 'size' number of + * entries are inserted into the list, but still complete count of the + * found children is returned. Which children are returned in the latter + * case is undefined. + * + * When supplying NULL as list and 0 as size, you can use this to acquire + * the number of children without having to pre-allocate a list. + * + * TODO: Link to generic dds entity relations documentation. + * + * @param[in] entity Entity from which to get its children. + * @param[out] children Pre-allocated array to contain the found children. + * @param[in] size Size of the pre-allocated children's list. + * + * @returns >=0 - Success (number of found children, can be larger than 'size'). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The children parameter is NULL, while a size is provided. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_get_children( + _In_ dds_entity_t entity, + _Out_opt_ dds_entity_t *children, + _In_ size_t size); + + +/** + * @brief Get the domain id to which this entity is attached. + * + * When creating a participant entity, it is attached to a certain domain. + * All the children (like Publishers) and childrens' children (like + * DataReaders), etc are also attached to that domain. + * + * This function will return the original domain ID when called on + * any of the entities within that hierarchy. + * + * @param[in] entity Entity from which to get its children. + * @param[out] id Pointer to put the domain ID in. + * + * @returns 0 - Success (DDS_RETCODE_OK). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_OK + * Domain ID was returned. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The id parameter is NULL. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT _Check_return_ dds_return_t +dds_get_domainid( + _In_ dds_entity_t entity, + _Out_ dds_domainid_t *id); + +/** + * @brief Get participants of a domain. + * + * This operation acquires the participants created on a domain and returns + * the number of found participants. + * + * This function takes a domain id with the size of pre-allocated participant's + * list in and will return the number of found participants. It is possible that + * the given size of the list is not the same as the number of found participants. + * If less participants are found, then the last few entries in an array stay + * untouched. If more participants are found and the array is too small, then the + * participants returned are undefined. + * + * @param[in] domain_id The domain id + * @param[out] participants The participant for domain + * @param[in] size Size of the pre-allocated participant's list. + * + * @returns >=0 - Success (number of found participants). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The participant parameter is NULL, while a size is provided. + */ +DDS_EXPORT _Check_return_ dds_return_t +dds_lookup_participant( + _In_ dds_domainid_t domain_id, + _Out_opt_ dds_entity_t *participants, + _In_ size_t size); + +/** + * Description : Creates a new DDS topic. The type name for the topic + * is taken from the generated descriptor. Topic matching is done on a + * combination of topic name and type name. + * + * Arguments : + * -# pp The participant on which the topic is being created + * -# descriptor The IDL generated topic descriptor + * -# name The name of the created topic + * -# qos The QoS to set on the new topic (can be NULL) + * -# listener Any listener functions associated with the new topic (can be NULL) + * -# Returns a status, 0 on success or non-zero value to indicate an error + */ +_Pre_satisfies_((participant & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) +DDS_EXPORT dds_entity_t +dds_create_topic( + _In_ dds_entity_t participant, + _In_ const dds_topic_descriptor_t *descriptor, + _In_z_ const char *name, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener); + +/** + * Description : Finds a named topic. Returns NULL if does not exist. + * The returned topic should be released with dds_delete. + * + * Arguments : + * -# pp The participant on which to find the topic + * -# name The name of the topic to find + * -# Returns a topic, NULL if could not be found or error + */ +_Pre_satisfies_((participant & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) +DDS_EXPORT dds_entity_t +dds_find_topic( + _In_ dds_entity_t participant, + _In_z_ const char *name); + +/** + * Description : Returns a topic name. + * + * Arguments : + * -# topic The topic + * -# Returns The topic name or NULL to indicate an error + */ +/* TODO: do we need a convenience version as well that allocates and add a _s suffix to this one? */ +/* TODO: Check annotation. Could be _Out_writes_to_(size, return + 1) as well. */ +_Pre_satisfies_((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC) +DDS_EXPORT dds_return_t +dds_get_name( + _In_ dds_entity_t topic, + _Out_writes_z_(size) char *name, + _In_ size_t size); + + +/** + * Description : Returns a topic type name. + * + * Arguments : + * -# topic The topic + * -# Returns The topic type name or NULL to indicate an error + */ +_Pre_satisfies_((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC) +DDS_EXPORT dds_return_t +dds_get_type_name( + _In_ dds_entity_t topic, + _Out_writes_z_(size) char *name, + _In_ size_t size); + +/** + * @brief Creates a new instance of a DDS subscriber + * + * @param[in] participant The participant on which the subscriber is being created + * @param[in] qos The QoS to set on the new subscriber (can be NULL) + * @param[in] listener Any listener functions associated with the new subscriber (can be NULL) + + * @returns >0 - Success (valid handle of a subscriber entity). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * DDS_RETCODE_BAD_PARAMETER + * One of the parameters is invalid + */ +_Pre_satisfies_((participant & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) +DDS_EXPORT _Must_inspect_result_ dds_entity_t +dds_create_subscriber( + _In_ dds_entity_t participant, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener); + + +/** + * @brief Creates a new instance of a DDS publisher + * + * @param[in] participant The participant to create a publisher for + * @param[in] qos The QoS to set on the new publisher (can be NULL) + * @param[in] listener Any listener functions associated with the new publisher (can be NULL) + * + * @returns >0 - Success (valid handle of a publisher entity). + * @returns <0 - Failure (use dds_err_nr() to get error value). + */ +_Pre_satisfies_((participant & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) +DDS_EXPORT _Must_inspect_result_ dds_entity_t +dds_create_publisher( + _In_ dds_entity_t participant, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener); + + +/** + * @brief Suspends the publications of the Publisher + * + * This operation is a hint to the Service so it can optimize its performance by e.g., collecting + * modifications to DDS writers and then batching them. The Service is not required to use the hint. + * + * Every invocation of this operation must be matched by a corresponding call to @see dds_resume + * indicating that the set of modifications has completed. + * + * @param[in] publisher The publisher for which all publications will be suspended + * + * @returns >0 - Success. + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_OK + * Publications suspended successfully. + * @retval DDS_RETCODE_BAD_PARAMETER + * The pub parameter is not a valid publisher. + * @retval DDS_RETCODE_UNSUPPORTED + * Operation is not supported + */ +_Pre_satisfies_((publisher & DDS_ENTITY_KIND_MASK) == DDS_KIND_PUBLISHER) +DDS_EXPORT dds_return_t +dds_suspend( + _In_ dds_entity_t publisher); + + +/** + * @brief Resumes the publications of the Publisher + * + * This operation is a hint to the Service to indicate that the application has completed changes + * initiated by a previous @see suspend. The Service is not required to use the hint. + * + * The call to resume_publications must match a previous call to @see suspend_publications. + * + * @param[in] publisher The publisher for which all publications will be resumed + * + * @returns >0 - Success. + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_OK + * Publications resumed successfully. + * @retval DDS_RETCODE_BAD_PARAMETER + * The pub parameter is not a valid publisher. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * No previous matching @see dds_suspend. + * @retval DDS_RETCODE_UNSUPPORTED + * Operation is not supported. + */ +_Pre_satisfies_((publisher & DDS_ENTITY_KIND_MASK) == DDS_KIND_PUBLISHER) +DDS_EXPORT dds_return_t +dds_resume( + _In_ dds_entity_t publisher); + + +/** + * @brief Waits at most for the duration timeout for acks for data in the publisher or writer. + * + * This operation blocks the calling thread until either all data written by the publisher + * or writer is acknowledged by all matched reliable reader entities, or else the duration + * specified by the timeout parameter elapses, whichever happens first. + * + * @param[in] pub_or_w The publisher or writer whose acknowledgements must be waited for. + * + * @returns >0 - Success. + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_OK + * All acknowledgements successfully received with the timeout. + * @retval DDS_RETCODE_BAD_PARAMETER + * The pub_or_w parameter is not a valid publisher or writer. + * @retval DDS_RETCODE_TIMEOUT + * Timeout expired before all acknowledgements from reliable reader entities were received. + * @retval DDS_RETCODE_UNSUPPORTED + * Operation is not supported. + */ +_Pre_satisfies_(((publisher_or_writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER ) ||\ + ((publisher_or_writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_PUBLISHER) ) +DDS_EXPORT dds_return_t +dds_wait_for_acks( + _In_ dds_entity_t publisher_or_writer, + _In_ dds_duration_t timeout); + + +/** + * @brief Creates a new instance of a DDS reader + * + * @param[in] participant_or_subscriber The participant or subscriber on which the reader is being created + * + * @param[in] topic The topic to read + * + * @param[in] qos The QoS to set on the new reader (can be NULL) + * + * @param[in] listener Any listener functions associated with the new reader (can be NULL) + * + * @returns >0 - Success (valid handle of a reader entity) + * @returns <0 - Failure (use dds_err_nr() to get error value) + * + */ +_Pre_satisfies_(((participant_or_subscriber & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER ) ||\ + ((participant_or_subscriber & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) ) +_Pre_satisfies_( (topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC ) +DDS_EXPORT dds_entity_t +dds_create_reader( + _In_ dds_entity_t participant_or_subscriber, + _In_ dds_entity_t topic, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener); + +/** + * Description : The operation blocks the calling thread until either all "historical" data is + * received, or else the duration specified by the max_wait parameter elapses, whichever happens + * first. A return value of 0 indicates that all the "historical" data was received; a return + * value of TIMEOUT indicates that max_wait elapsed before all the data was received. + * + * Arguments : + * -# reader The reader on which to wait for historical data + * -# max_wait How long to wait for historical data before time out + * -# Returns a status, 0 on success, TIMEOUT on timeout or a negative value to indicate error + */ +/* TODO: SAL-annotate TIMEOUT as a succesfull return as well? */ +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +DDS_EXPORT dds_return_t +dds_wait_for_historical_data( + _In_ dds_entity_t reader, + _In_ dds_duration_t max_wait); + +/** + * @brief Creates a new instance of a DDS writer + * + * @param[in] participant_or_publisher The participant or publisher on which the writer is being created + * @param[in] topic The topic to write + * @param[in] qos The QoS to set on the new writer (can be NULL) + * @param[in] listener Any listener functions associated with the new writer (can be NULL) + * + * @returns >0 - Success (valid handle of a writer entity) + * @returns <0 - Failure (use dds_err_nr() to get error value) + */ +_Pre_satisfies_(((participant_or_publisher & DDS_ENTITY_KIND_MASK) == DDS_KIND_PUBLISHER ) ||\ + ((participant_or_publisher & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) ) +_Pre_satisfies_( (topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC ) +DDS_EXPORT dds_entity_t +dds_create_writer( + _In_ dds_entity_t participant_or_publisher, + _In_ dds_entity_t topic, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener); + + +/* + Writing data (and variants of it) is straightforward. The first set + is equivalent to the second set with -1 passed for "timestamp", + meaning, substitute the result of a call to time(). The dispose + and unregister operations take an object of the topic's type, but + only touch the key fields; the remained may be undefined. +*/ +/** + * Description : Registers an instance with a key value to the data writer + * + * Arguments : + * -# wr The writer to which instance has be associated + * -# data Instance with the key value + * -# Returns an instance handle that could be used for successive write & dispose operations or + * NULL, if handle is not allocated + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_register_instance ( + _In_ dds_entity_t writer, + _Out_ dds_instance_handle_t *handle, + _In_ const void *data); + +/** + * Description : Unregisters an instance with a key value from the data writer. Instance can be identified + * either from data sample or from instance handle (at least one must be provided). + * + * Arguments : + * -# wr The writer to which instance is associated + * -# data Instance with the key value (can be NULL if handle set) + * -# handle Instance handle (can be DDS_HANDLE_NIL if data set) + * -# Returns 0 on success, or non-zero value to indicate an error + * + * Note : If an unregistered key ID is passed as instance data, an error is logged and not flagged as return value + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_unregister_instance( + _In_ dds_entity_t writer, + _In_ const void *data); + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_unregister_instance_ih( + _In_ dds_entity_t writer, + _In_ dds_instance_handle_t handle); + + /** + * Description : Unregisters an instance with a key value from the data writer. Instance can be identified + * either from data sample or from instance handle (at least one must be provided). + * + * Arguments : + * -# wr The writer to which instance is associated + * -# data Instance with the key value (can be NULL if handle set) + * -# handle Instance handle (can be DDS_HANDLE_NIL if data set) + * -# timestamp used at registration. + * -# Returns 0 on success, or non-zero value to indicate an error + * + * Note : If an unregistered key ID is passed as instance data, an error is logged and not flagged as return value + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_unregister_instance_ts( + _In_ dds_entity_t writer, + _In_ const void *data, + _In_ dds_time_t timestamp); + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_unregister_instance_ih_ts( + _In_ dds_entity_t writer, + _In_ dds_instance_handle_t handle, + _In_ dds_time_t timestamp); + +/** + * @brief This operation modifies and disposes a data instance. + * + * This operation requests the Data Distribution Service to modify the instance and + * mark it for deletion. Copies of the instance and its corresponding samples, which are + * stored in every connected reader and, dependent on the QoS policy settings (also in + * the Transient and Persistent stores) will be modified and marked for deletion by + * setting their dds_instance_state_t to DDS_IST_NOT_ALIVE_DISPOSED. + * + * Blocking
+ * If the history QoS policy is set to DDS_HISTORY_KEEP_ALL, the + * dds_writedispose operation on the writer may block if the modification + * would cause data to be lost because one of the limits, specified in the + * resource_limits QoS policy, to be exceeded. In case the synchronous + * attribute value of the reliability Qos policy is set to true for + * communicating writers and readers then the writer will wait until + * all synchronous readers have acknowledged the data. Under these + * circumstances, the max_blocking_time attribute of the reliability + * QoS policy configures the maximum time the dds_writedispose operation + * may block. + * If max_blocking_time elapses before the writer is able to store the + * modification without exceeding the limits and all expected acknowledgements + * are received, the dds_writedispose operation will fail and returns + * DDS_RETCODE_TIMEOUT. + * + * @param[in] writer The writer to dispose the data instance from. + * @param[in] data The data to be written and disposed. + * + * @returns 0 - Success. + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_OK + * The sample is written and the instance is marked for deletion. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * At least one of the arguments is invalid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_TIMEOUT + * Either the current action overflowed the available resources + * as specified by the combination of the reliability QoS policy, + * history QoS policy and resource_limits QoS policy, or the + * current action was waiting for data delivery acknowledgement + * by synchronous readers. This caused blocking of this operation, + * which could not be resolved before max_blocking_time of the + * reliability QoS policy elapsed. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_writedispose( + _In_ dds_entity_t writer, + _In_ const void *data); + +/** + * Description : This operation modifies and disposes a data instance with + * a specific timestamp. + * + * This operation performs the same functions as dds_writedispose except that + * the application provides the value for the source_timestamp that is made + * available to connected reader objects. This timestamp is important for the + * interpretation of the destination_order QoS policy. + * + * @param[in] writer The writer to dispose the data instance from. + * @param[in] data The data to be written and disposed. + * @param[in] timestamp The timestamp used as source timestamp. + * + * @returns 0 - Success. + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_OK + * The sample is written and the instance is marked for deletion. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * At least one of the arguments is invalid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_TIMEOUT + * Either the current action overflowed the available resources + * as specified by the combination of the reliability QoS policy, + * history QoS policy and resource_limits QoS policy, or the + * current action was waiting for data delivery acknowledgement + * by synchronous readers. This caused blocking of this operation, + * which could not be resolved before max_blocking_time of the + * reliability QoS policy elapsed. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_writedispose_ts( + _In_ dds_entity_t writer, + _In_ const void *data, + _In_ dds_time_t timestamp); + +/** + * @brief This operation disposes an instance, identified by the data sample. + * + * This operation requests the Data Distribution Service to modify the instance and + * mark it for deletion. Copies of the instance and its corresponding samples, which are + * stored in every connected reader and, dependent on the QoS policy settings (also in + * the Transient and Persistent stores) will be modified and marked for deletion by + * setting their dds_instance_state_t to DDS_IST_NOT_ALIVE_DISPOSED. + * + * Blocking
+ * If the history QoS policy is set to DDS_HISTORY_KEEP_ALL, the + * dds_writedispose operation on the writer may block if the modification + * would cause data to be lost because one of the limits, specified in the + * resource_limits QoS policy, to be exceeded. In case the synchronous + * attribute value of the reliability Qos policy is set to true for + * communicating writers and readers then the writer will wait until + * all synchronous readers have acknowledged the data. Under these + * circumstances, the max_blocking_time attribute of the reliability + * QoS policy configures the maximum time the dds_writedispose operation + * may block. + * If max_blocking_time elapses before the writer is able to store the + * modification without exceeding the limits and all expected acknowledgements + * are received, the dds_writedispose operation will fail and returns + * DDS_RETCODE_TIMEOUT. + * + * @param[in] writer The writer to dispose the data instance from. + * @param[in] data The data sample that identifies the instance + * to be disposed. + * + * @returns 0 - Success. + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_OK + * The sample is written and the instance is marked for deletion. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * At least one of the arguments is invalid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_TIMEOUT + * Either the current action overflowed the available resources + * as specified by the combination of the reliability QoS policy, + * history QoS policy and resource_limits QoS policy, or the + * current action was waiting for data delivery acknowledgement + * by synchronous readers. This caused blocking of this operation, + * which could not be resolved before max_blocking_time of the + * reliability QoS policy elapsed. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_dispose( + _In_ dds_entity_t writer, + _In_ const void *data); + +/** + * Description : This operation disposes an instance with a specific timestamp, + * identified by the data sample. + * + * This operation performs the same functions as dds_dispose except that + * the application provides the value for the source_timestamp that is made + * available to connected reader objects. This timestamp is important for the + * interpretation of the destination_order QoS policy. + * + * @param[in] writer The writer to dispose the data instance from. + * @param[in] data The data sample that identifies the instance + * to be disposed. + * @param[in] timestamp The timestamp used as source timestamp. + * + * @returns 0 - Success. + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_OK + * The sample is written and the instance is marked for deletion. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * At least one of the arguments is invalid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_TIMEOUT + * Either the current action overflowed the available resources + * as specified by the combination of the reliability QoS policy, + * history QoS policy and resource_limits QoS policy, or the + * current action was waiting for data delivery acknowledgement + * by synchronous readers. This caused blocking of this operation, + * which could not be resolved before max_blocking_time of the + * reliability QoS policy elapsed. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_dispose_ts( + _In_ dds_entity_t writer, + _In_ const void *data, + _In_ dds_time_t timestamp); + +/** + * @brief This operation disposes an instance, identified by the instance handle. + * + * This operation requests the Data Distribution Service to modify the instance and + * mark it for deletion. Copies of the instance and its corresponding samples, which are + * stored in every connected reader and, dependent on the QoS policy settings (also in + * the Transient and Persistent stores) will be modified and marked for deletion by + * setting their dds_instance_state_t to DDS_IST_NOT_ALIVE_DISPOSED. + * + * Instance Handle
+ * The given instance handle must correspond to the value that was returned by either + * the dds_register_instance operation, dds_register_instance_ts or dds_instance_lookup. + * If there is no correspondence, then the result of the operation is unspecified. + * + * @param[in] writer The writer to dispose the data instance from. + * @param[in] handle The handle to identify an instance. + * + * @returns 0 - Success. + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_OK + * The sample is written and the instance is marked for deletion. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * At least one of the arguments is invalid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this writer. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_dispose_ih( + _In_ dds_entity_t writer, + _In_ dds_instance_handle_t handle); + +/** + * Description : This operation disposes an instance with a specific timestamp, + * identified by the instance handle. + * + * This operation performs the same functions as dds_dispose_ih except that + * the application provides the value for the source_timestamp that is made + * available to connected reader objects. This timestamp is important for the + * interpretation of the destination_order QoS policy. + * + * @param[in] writer The writer to dispose the data instance from. + * @param[in] handle The handle to identify an instance. + * @param[in] timestamp The timestamp used as source timestamp. + * + * @returns 0 - Success. + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_OK + * The sample is written and the instance is marked for deletion. + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * At least one of the arguments is invalid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this writer. + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_dispose_ih_ts( + _In_ dds_entity_t writer, + _In_ dds_instance_handle_t handle, + _In_ dds_time_t timestamp); + +/** + * @brief Write the value of a data instance + * + * With this API, the value of the source timestamp is automatically made + * available to the data reader by the service. + * + * @param[in] writer The writer entity + * @param[in] data Value to be written + * + * @returns - dds_return_t indicating success or failure + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_write( + _In_ dds_entity_t writer, + _In_ const void *data); + + +/** + * @brief Write a CDR serialized value of a data instance + * + * Untyped API, which take serialized blobs now. + * Whether they remain exposed like this with X-types isn't entirely clear yet. + * TODO: make a decide about dds_takecdr + * + * @param[in] writer The writer entity + * @param[in] cdr CDR serialized value to be written + * @param[in] size Size (in bytes) of CDR encoded data to be written + * + * @returns - A dds_return_t indicating success or failure + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_writecdr( + _In_ dds_entity_t writer, + _In_reads_bytes_(size) const void *cdr, + _In_ size_t size); + +/** + * @brief Write the value of a data instance along with the source timestamp passed. + * + * @param[in] writer The writer entity + * @param[in] data Value to be written + * @param[in] timestamp Source timestamp + * + * @returns - A dds_return_t indicating success or failure + */ +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +DDS_EXPORT dds_return_t +dds_write_ts( + _In_ dds_entity_t writer, + _In_ const void *data, + _In_ dds_time_t timestamp); + +/** + * @brief Creates a readcondition associated to the given reader. + * + * The readcondition allows specifying which samples are of interest in + * a data reader's history, by means of a mask. The mask is or'd with + * the flags that are dds_sample_state_t, dds_view_state_t and + * dds_instance_state_t. + * + * Based on the mask value set, the readcondition gets triggered when + * data is available on the reader. + * + * Waitsets allow waiting for an event on some of any set of entities. + * This means that the readcondition can be used to wake up a waitset when + * data is in the reader history with states that matches the given mask. + * + * @note The parent reader and every of its associated conditions (whether + * they are readconditions or queryconditions) share the same resources. + * This means that one of these entities reads or takes data, the states + * of the data will change for other entities automatically. For instance, + * if one reads a sample, then the sample state will become 'read' for all + * associated reader/conditions. Or if one takes a sample, then it's not + * available to any other associated reader/condition. + * + * @param[in] reader Reader to associate the condition to. + * @param[in] mask Interest (dds_sample_state_t|dds_view_state_t|dds_instance_state_t). + * + * @returns >0 - Success (valid condition). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +DDS_EXPORT _Must_inspect_result_ dds_entity_t +dds_create_readcondition( + _In_ dds_entity_t reader, + _In_ uint32_t mask); + +/** + * @brief Creates a queryondition associated to the given reader. + * + * The queryondition allows specifying which samples are of interest in + * a data reader's history, by means of a mask and a filter. The mask is + * or'd with the flags that are dds_sample_state_t, dds_view_state_t and + * dds_instance_state_t. + * + * TODO: Explain the filter (aka expression & parameters) of the (to be + * implemented) new querycondition implementation. + * + * Based on the mask value set and data that matches the filter, the + * querycondition gets triggered when data is available on the reader. + * + * Waitsets allow waiting for an event on some of any set of entities. + * This means that the querycondition can be used to wake up a waitset when + * data is in the reader history with states that matches the given mask + * and filter. + * + * @note The parent reader and every of its associated conditions (whether + * they are readconditions or queryconditions) share the same resources. + * This means that one of these entities reads or takes data, the states + * of the data will change for other entities automatically. For instance, + * if one reads a sample, then the sample state will become 'read' for all + * associated reader/conditions. Or if one takes a sample, then it's not + * available to any other associated reader/condition. + * + * TODO: Update parameters when new querycondition is introduced. + * + * @param[in] reader Reader to associate the condition to. + * @param[in] mask Interest (dds_sample_state_t|dds_view_state_t|dds_instance_state_t). + * @param[in] filter Callback that the application can use to filter specific samples. + * + * @returns >0 - Success (valid condition). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +typedef bool (*dds_querycondition_filter_fn) (const void * sample); +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +DDS_EXPORT dds_entity_t +dds_create_querycondition( + _In_ dds_entity_t reader, + _In_ uint32_t mask, + _In_ dds_querycondition_filter_fn filter); + + +/** + * @brief Waitset attachment argument. + * + * Every entity that is attached to the waitset can be accompanied by such + * an attachment argument. When the waitset wait is unblocked because of an + * entity that triggered, then the returning array will be populated with + * these attachment arguments that are related to the triggered entity. +*/ +typedef void * dds_attach_t; + +/** + * @brief Create a waitset and allocate the resources required + * + * A WaitSet object allows an application to wait until one or more of the + * conditions of the attached entities evaluates to TRUE or until the timeout + * expires. + * + * @param[in] participant Domain participant which the WaitSet contains. + * + * @returns >0 - Success (valid waitset). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_((participant & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) +DDS_EXPORT _Must_inspect_result_ dds_entity_t +dds_create_waitset( + _In_ dds_entity_t participant); + + +/** + * @brief Acquire previously attached entities. + * + * This functions takes a pre-allocated list to put the entities in and + * will return the number of found entities. It is possible that the given + * size of the list is not the same as the number of found entities. If + * less entities are found, then the last few entries in the list are + * untouched. When more entities are found, then only 'size' number of + * entries are inserted into the list, but still the complete count of the + * found entities is returned. Which entities are returned in the latter + * case is undefined. + * + * @param[in] waitset Waitset from which to get its attached entities. + * @param[out] entities Pre-allocated array to contain the found entities. + * @param[in] size Size of the pre-allocated entities' list. + * + * @returns >=0 - Success (number of found children, can be larger than 'size'). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The entities parameter is NULL, while a size is provided. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The waitset has already been deleted. + */ +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +DDS_EXPORT dds_return_t +dds_waitset_get_entities( + _In_ dds_entity_t waitset, + _Out_writes_to_(size, return < 0 ? 0 : return) dds_entity_t *entities, + _In_ size_t size); + + +/** + * @brief This operation attaches an Entity to the WaitSet. + * + * This operation attaches an Entity to the WaitSet. The dds_waitset_wait() + * will block when none of the attached entities are triggered. 'Triggered' + * (dds_triggered()) doesn't mean the same for every entity: + * - Reader/Writer/Publisher/Subscriber/Topic/Participant + * - These are triggered when their status changed. + * - WaitSet + * - Triggered when trigger value was set to true by the application. + * It stays triggered until application sets the trigger value to + * false (dds_waitset_set_trigger()). This can be used to wake up an + * waitset for different reasons (f.i. termination) than the 'normal' + * status change (like new data). + * - ReadCondition/QueryCondition + * - Triggered when data is available on the related Reader that matches + * the Condition. + * + * Multiple entities can be attached to a single waitset. A particular entity + * can be attached to multiple waitsets. However, a particular entity can not + * be attached to a particular waitset multiple times. + * + * @param[in] waitset The waitset to attach the given entity to. + * @param[in] entity The entity to attach. + * @param[in] x Blob that will be supplied when the waitset wait is + * triggerd by the given entity. + * + * @returns 0 - Success (entity attached). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The given waitset or entity are not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The waitset has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The entity was already attached. + */ +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +DDS_EXPORT dds_return_t +dds_waitset_attach( + _In_ dds_entity_t waitset, + _In_ dds_entity_t entity, + _In_ dds_attach_t x); + + +/** + * @brief This operation detaches an Entity to the WaitSet. + * + * @param[in] waitset The waitset to detach the given entity from. + * @param[in] entity The entity to detach. + * + * @returns 0 - Success (entity attached). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The given waitset or entity are not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The waitset has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The entity is not attached. + */ +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +DDS_EXPORT dds_return_t +dds_waitset_detach( + _In_ dds_entity_t waitset, + _In_ dds_entity_t entity); + +/** + * @brief Sets the trigger_value associated with a waitset. + * + * When the waitset is attached to itself and the trigger value is + * set to 'true', then the waitset will wake up just like with an + * other status change of the attached entities. + * + * This can be used to forcefully wake up a waitset, for instance + * when the application wants to shut down. So, when the trigger + * value is true, the waitset will wake up or not wait at all. + * + * The trigger value will remain true until the application sets it + * false again deliberately. + * + * @param[in] waitset The waitset to set the trigger value on. + * @param[in] trigger The trigger value to set. + * + * @returns 0 - Success (entity attached). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The given waitset is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The waitset has already been deleted. + */ +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +DDS_EXPORT dds_return_t +dds_waitset_set_trigger( + _In_ dds_entity_t waitset, + _In_ bool trigger); + +/** + * @brief This operation allows an application thread to wait for the a status + * change or other trigger on (one of) the entities that are attached to + * the WaitSet. + * + * The "dds_waitset_wait" operation blocks until the some of the attached + * entities have triggered or "reltimeout" has elapsed. + * 'Triggered' (dds_triggered()) doesn't mean the same for every entity: + * - Reader/Writer/Publisher/Subscriber/Topic/Participant + * - These are triggered when their status changed. + * - WaitSet + * - Triggered when trigger value was set to true by the application. + * It stays triggered until application sets the trigger value to + * false (dds_waitset_set_trigger()). This can be used to wake up an + * waitset for different reasons (f.i. termination) than the 'normal' + * status change (like new data). + * - ReadCondition/QueryCondition + * - Triggered when data is available on the related Reader that matches + * the Condition. + * + * This functions takes a pre-allocated list to put the "xs" blobs in (that + * were provided during the attach of the related entities) and will return + * the number of triggered entities. It is possible that the given size + * of the list is not the same as the number of triggered entities. If less + * entities were triggered, then the last few entries in the list are + * untouched. When more entities are triggered, then only 'size' number of + * entries are inserted into the list, but still the complete count of the + * triggered entities is returned. Which "xs" blobs are returned in the + * latter case is undefined. + * + * In case of a time out, the return value is 0. + * + * Deleting the waitset while the application is blocked results in an + * error code (i.e. < 0) returned by "wait". + * + * Multiple threads may block on a single waitset at the same time; + * the calls are entirely independent. + * + * An empty waitset never triggers (i.e., dds_waitset_wait on an empty + * waitset is essentially equivalent to a sleep). + * + * The "dds_waitset_wait_until" operation is the same as the + * "dds_waitset_wait" except that it takes an absolute timeout. + * + * @param[in] waitset The waitset to set the trigger value on. + * @param[out] xs Pre-allocated list to store the 'blobs' that were + * provided during the attach of the triggered entities. + * @param[in] nxs The size of the pre-allocated blobs list. + * @param[in] reltimeout Relative timeout + * + * @returns >0 - Success (number of entities triggered). + * @returns 0 - Time out (no entities were triggered). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The given waitset is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The waitset has already been deleted. + */ +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +DDS_EXPORT dds_return_t +dds_waitset_wait( + _In_ dds_entity_t waitset, + _Out_writes_to_(nxs, return < 0 ? 0 : return) dds_attach_t *xs, + _In_ size_t nxs, + _In_ dds_duration_t reltimeout); + +/** + * @brief This operation allows an application thread to wait for the a status + * change or other trigger on (one of) the entities that are attached to + * the WaitSet. + * + * The "dds_waitset_wait" operation blocks until the some of the attached + * entities have triggered or "abstimeout" has been reached. + * 'Triggered' (dds_triggered()) doesn't mean the same for every entity: + * - Reader/Writer/Publisher/Subscriber/Topic/Participant + * - These are triggered when their status changed. + * - WaitSet + * - Triggered when trigger value was set to true by the application. + * It stays triggered until application sets the trigger value to + * false (dds_waitset_set_trigger()). This can be used to wake up an + * waitset for different reasons (f.i. termination) than the 'normal' + * status change (like new data). + * - ReadCondition/QueryCondition + * - Triggered when data is available on the related Reader that matches + * the Condition. + * + * This functions takes a pre-allocated list to put the "xs" blobs in (that + * were provided during the attach of the related entities) and will return + * the number of triggered entities. It is possible that the given size + * of the list is not the same as the number of triggered entities. If less + * entities were triggered, then the last few entries in the list are + * untouched. When more entities are triggered, then only 'size' number of + * entries are inserted into the list, but still the complete count of the + * triggered entities is returned. Which "xs" blobs are returned in the + * latter case is undefined. + * + * In case of a time out, the return value is 0. + * + * Deleting the waitset while the application is blocked results in an + * error code (i.e. < 0) returned by "wait". + * + * Multiple threads may block on a single waitset at the same time; + * the calls are entirely independent. + * + * An empty waitset never triggers (i.e., dds_waitset_wait on an empty + * waitset is essentially equivalent to a sleep). + * + * The "dds_waitset_wait" operation is the same as the + * "dds_waitset_wait_until" except that it takes an relative timeout. + * + * The "dds_waitset_wait" operation is the same as the "dds_wait" + * except that it takes an absolute timeout. + * + * @param[in] waitset The waitset to set the trigger value on. + * @param[out] xs Pre-allocated list to store the 'blobs' that were + * provided during the attach of the triggered entities. + * @param[in] nxs The size of the pre-allocated blobs list. + * @param[in] abstimeout Absolute timeout + * + * @returns >0 - Success (number of entities triggered). + * @returns 0 - Time out (no entities were triggered). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * The given waitset is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The waitset has already been deleted. + */ +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +DDS_EXPORT dds_return_t +dds_waitset_wait_until( + _In_ dds_entity_t waitset, + _Out_writes_to_(nxs, return < 0 ? 0 : return) dds_attach_t *xs, + _In_ size_t nxs, + _In_ dds_time_t abstimeout); + +/* + There are a number of read and take variations. + + Return value is the number of elements returned. "max_samples" + should have the same type, as one can't return more than MAX_INT + this way, anyway. X, Y, CX, CY return to the various filtering + options, see the DCPS spec. + + O ::= read | take + + X => CX + (empty) (empty) + _next_instance instance_handle_t prev + + Y => CY + (empty) uint32_t mask + _cond cond_t cond -- refers to a read condition (or query if implemented) +*/ + +/** + * @brief Access and read the collection of data values (of same type) and sample info from the + * data reader, readcondition or querycondition. + * + * Return value provides information about number of samples read, which will + * be <= maxs. Based on the count, the buffer will contain data to be read only + * when valid_data bit in sample info structure is set. + * The buffer required for data values, could be allocated explicitly or can + * use the memory from data reader to prevent copy. In the latter case, buffer and + * sample_info should be returned back, once it is no longer using the Data. + * Data values once read will remain in the buffer with the sample_state set to READ + * and view_state set to NOT_NEW. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] bufsz The size of buffer provided + * @param[in] maxs Maximum number of samples to read + * + * @returns >=0 - Success (number of samples read). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_read( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs); + +/** + * @brief Access and read loaned samples of data reader, readcondition or querycondition. + * + * After dds_read_wl function is being called and the data has been handled, dds_return_loan function must be called to possibly free memory + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] maxs Maximum number of samples to read + * + * @returns >=0 - Success (number of samples read). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_read_wl( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs); + +/** + * @brief Read the collection of data values and sample info from the data reader, readcondition + * or querycondition based on mask. + * + * When using a readcondition or querycondition, their masks are or'd with the given mask. + * + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] bufsz The size of buffer provided + * @param[in] maxs Maximum number of samples to read + * @param[in] mask Filter the data based on dds_sample_state_t|dds_view_state_t|dds_instance_state_t. + * + * @returns >=0 - Success (number of samples read). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_read_mask( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ uint32_t mask /* In case of ReadCondition, both masks are applied (OR'd) */); + +/** + * @brief Access and read loaned samples of data reader, readcondition + * or querycondition based on mask + * + * When using a readcondition or querycondition, their masks are or'd with the given mask. + * + * After dds_read_mask_wl function is being called and the data has been handled, dds_return_loan function must be called to possibly free memory + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] maxs Maximum number of samples to read + * @param[in] mask Filter the data based on dds_sample_state_t|dds_view_state_t|dds_instance_state_t. + * + * @returns >=0 - Success (number of samples read). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_read_mask_wl( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs, + _In_ uint32_t mask /* In case of ReadCondition, both masks are applied (OR'd) */); + + +/** + * @brief Access and read the collection of data values (of same type) and sample info from the + * data reader, readcondition or querycondition, coped by the provided instance handle. + * + * This operation implements the same functionality as dds_read, except that only data scoped to + * the provided instance handle is read. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] bufsz The size of buffer provided + * @param[in] maxs Maximum number of samples to read + * @param[in] handle Instance handle related to the samples to read + * + * @returns >=0 - Success (number of samples read). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this reader. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_read_instance( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle); + +/** + * @brief Access and read loaned samples of data reader, readcondition or querycondition, + * scoped by the provided instance handle. + * + * This operation implements the same functionality as dds_read_wl, except that only data + * scoped to the provided instance handle is read. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] maxs Maximum number of samples to read + * @param[in] handle Instance handle related to the samples to read + * + * @returns >=0 - Success (number of samples read). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this reader. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_read_instance_wl( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle); + +/** + * @brief Read the collection of data values and sample info from the data reader, readcondition + * or querycondition based on mask and scoped by the provided instance handle. + * + * This operation implements the same functionality as dds_read_mask, except that only data + * scoped to the provided instance handle is read. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] bufsz The size of buffer provided + * @param[in] maxs Maximum number of samples to read + * @param[in] handle Instance handle related to the samples to read + * @param[in] mask Filter the data based on dds_sample_state_t|dds_view_state_t|dds_instance_state_t. + * + * @returns >=0 - Success (number of samples read). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this reader. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_read_instance_mask( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle, + _In_ uint32_t mask); + +/** + * @brief Access and read loaned samples of data reader, readcondition or + * querycondition based on mask, scoped by the provided instance handle. + * + * This operation implements the same functionality as dds_read_mask_wl, except that + * only data scoped to the provided instance handle is read. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] maxs Maximum number of samples to read + * @param[in] handle Instance handle related to the samples to read + * @param[in] mask Filter the data based on dds_sample_state_t|dds_view_state_t|dds_instance_state_t. + * + * @returns >=0 - Success (number of samples read). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this reader. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_read_instance_mask_wl( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle, + _In_ uint32_t mask); + +/** + * Description : Access the collection of data values (of same type) and sample info from the data reader + * based on the criteria specified in the read condition. + * Read condition must be attached to the data reader before associating with data read. + * Return value provides information about number of samples read, which will + * be <= maxs. Based on the count, the buffer will contain data to be read only + * when valid_data bit in sample info structure is set. + * The buffer required for data values, could be allocated explicitly or can + * use the memory from data reader to prevent copy. In the latter case, buffer and + * sample_info should be returned back, once it is no longer using the Data. + * Data values once read will remain in the buffer with the sample_state set to READ + * and view_state set to NOT_NEW. + * + * Arguments : + * -# rd Reader entity + * -# buf an array of pointers to samples into which data is read (pointers can be NULL) + * -# maxs maximum number of samples to read + * -# si pointer to an array of \ref dds_sample_info_t returned for each data value + * -# cond read condition to filter the data samples based on the content + * -# Returns the number of samples read, 0 indicates no data to read. + */ + +/** + * @brief Access the collection of data values (of same type) and sample info from the + * data reader, readcondition or querycondition. + * + * Data value once read is removed from the Data Reader cannot to + * 'read' or 'taken' again. + * Return value provides information about number of samples read, which will + * be <= maxs. Based on the count, the buffer will contain data to be read only + * when valid_data bit in sample info structure is set. + * The buffer required for data values, could be allocated explicitly or can + * use the memory from data reader to prevent copy. In the latter case, buffer and + * sample_info should be returned back, once it is no longer using the Data. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] bufsz The size of buffer provided + * @param[in] maxs Maximum number of samples to read + * + * @returns >=0 - Success (number of samples read). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_take( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, /* _Out_writes_to_ annotation would be nice, however we don't know the size of the elements. Solution for that? Is there a better annotation? */ + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs); + +/** + * @brief Access loaned samples of data reader, readcondition or querycondition. + * + * After dds_take_wl function is being called and the data has been handled, dds_return_loan function must be called to possibly free memory + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] maxs Maximum number of samples to read + * + * @returns >=0 - Success (number of samples read). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_take_wl( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, /* _Out_writes_to_ annotation would be nice, however we don't know the size of the elements. Solution for that? Is there a better annotation? */ + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs); + +/** + * @brief Take the collection of data values (of same type) and sample info from the + * data reader, readcondition or querycondition based on mask + * + * When using a readcondition or querycondition, their masks are or'd with the given mask. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] bufsz The size of buffer provided + * @param[in] maxs Maximum number of samples to read + * @param[in] mask Filter the data based on dds_sample_state_t|dds_view_state_t|dds_instance_state_t. + * + * @returns >=0 - Success (number of samples read). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_take_mask( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, /* _Out_writes_to_ annotation would be nice, however we don't know the size of the elements. Solution for that? Is there a better annotation? */ + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ uint32_t mask); + +/** + * @brief Access loaned samples of data reader, readcondition or querycondition based on mask. + * + * When using a readcondition or querycondition, their masks are or'd with the given mask. + * + * After dds_take_mask_wl function is being called and the data has been handled, dds_return_loan function must be called to possibly free memory + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] maxs Maximum number of samples to read + * @param[in] mask Filter the data based on dds_sample_state_t|dds_view_state_t|dds_instance_state_t. + * + * @returns >=0 - Success (number of samples read). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_take_mask_wl( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, /* _Out_writes_to_ annotation would be nice, however we don't know the size of the elements. Solution for that? Is there a better annotation? */ + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs, + _In_ uint32_t mask); + +/* + * Untyped API, which take serialized blobs now. + * Whether they remain exposed like this with X-types isn't entirely clear yet. + * TODO: make a decide about dds_takecdr + * If we want dds_takecdr(), shouldn't there be a dds_readcdr()? + */ +struct serdata; + +DDS_EXPORT int +dds_takecdr( + dds_entity_t reader_or_condition, + struct serdata **buf, + uint32_t maxs, + dds_sample_info_t *si, + uint32_t mask); + + +/** + * @brief Access the collection of data values (of same type) and sample info from the + * data reader, readcondition or querycondition but scoped by the given + * instance handle. + * + * This operation mplements the same functionality as dds_take, except that only data + * scoped to the provided instance handle is taken. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] bufsz The size of buffer provided + * @param[in] maxs Maximum number of samples to read + * @param[in] handle Instance handle related to the samples to read + * + * @returns >=0 - Success (number of samples read). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this reader. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_take_instance( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle); + +/** + * @brief Access loaned samples of data reader, readcondition or querycondition, + * scoped by the given instance handle. + * + * This operation implements the same functionality as dds_take_wl, except that + * only data scoped to the provided instance handle is read. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] maxs Maximum number of samples to read + * @param[in] handle Instance handle related to the samples to read + * + * @returns >=0 - Success (number of samples read). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this reader. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_take_instance_wl( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle); + +/** + * @brief Take the collection of data values (of same type) and sample info from the + * data reader, readcondition or querycondition based on mask and scoped + * by the given instance handle. + * + * This operation implements the same functionality as dds_take_mask, except that only + * data scoped to the provided instance handle is read. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] bufsz The size of buffer provided + * @param[in] maxs Maximum number of samples to read + * @param[in] handle Instance handle related to the samples to read + * @param[in] mask Filter the data based on dds_sample_state_t|dds_view_state_t|dds_instance_state_t. + * + * @returns >=0 - Success (number of samples read). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this reader. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_take_instance_mask( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle, + _In_ uint32_t mask); + +/** + * @brief Access loaned samples of data reader, readcondition or querycondition based + * on mask and scoped by the given intance handle. + * + * This operation implements the same functionality as dds_take_mask_wl, except that + * only data scoped to the provided instance handle is read. + * + * @param[in] reader_or_condition Reader, readcondition or querycondition entity + * @param[out] buf An array of pointers to samples into which data is read (pointers can be NULL) + * @param[out] si Pointer to an array of \ref dds_sample_info_t returned for each data value + * @param[in] maxs Maximum number of samples to read + * @param[in] handle Instance handle related to the samples to read + * @param[in] mask Filter the data based on dds_sample_state_t|dds_view_state_t|dds_instance_state_t. + * + * @returns >=0 - Success (number of samples read). + * @returns <0 - Failure (use dds_err_nr() to get error value). + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * @retval DDS_RETCODE_BAD_PARAMETER + * One of the given arguments is not valid. + * @retval DDS_RETCODE_ILLEGAL_OPERATION + * The operation is invoked on an inappropriate object. + * @retval DDS_RETCODE_ALREADY_DELETED + * The entity has already been deleted. + * @retval DDS_RETCODE_PRECONDITION_NOT_MET + * The instance handle has not been registered with this reader. + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT dds_return_t +dds_take_instance_mask_wl( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle, + _In_ uint32_t mask); + + +/* + The read/take next functions return a single sample. The returned sample + has a sample state of NOT_READ, a view state of ANY_VIEW_STATE and an + instance state of ANY_INSTANCE_STATE. +*/ + +/** + * Description : This operation copies the next, non-previously accessed data value and corresponding + * sample info and removes from the data reader. + * + * Arguments : + * -# rd Reader entity + * -# buf an array of pointers to samples into which data is read (pointers can be NULL) + * -# si pointer to \ref dds_sample_info_t returned for a data value + * -# Returns 1 on successful operation, else 0 if there is no data to be read. + */ +DDS_EXPORT dds_return_t +dds_take_next( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, + _Out_ dds_sample_info_t *si); + +DDS_EXPORT dds_return_t +dds_take_next_wl( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, + _Out_ dds_sample_info_t *si); + +/** + * Description : This operation copies the next, non-previously accessed data value and corresponding + * sample info. + * + * Arguments : + * -# rd Reader entity + * -# buf an array of pointers to samples into which data is read (pointers can be NULL) + * -# si pointer to \ref dds_sample_info_t returned for a data value + * -# Returns 1 on successful operation, else 0 if there is no data to be read. + */ +DDS_EXPORT dds_return_t +dds_read_next( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, + _Out_ dds_sample_info_t *si); + +DDS_EXPORT dds_return_t +dds_read_next_wl( + _In_ dds_entity_t reader_or_condition, + _Out_ void **buf, + _Out_ dds_sample_info_t *si); + +/** + * @brief Return loaned samples to data-reader or condition associated with a data-reader + * + * Used to release sample buffers returned by a read/take operation. When the application + * provides an empty buffer, memory is allocated and managed by DDS. By calling dds_return_loan, + * the memory is released so that the buffer can be reused during a successive read/take operation. + * When a condition is provided, the reader to which the condition belongs is looked up. + * + * @param[in] rd_or_cnd Reader or condition that belongs to a reader + * @param[in] buf An array of (pointers to) samples + * @param[in] bufsz The number of (pointers to) samples stored in buf + * + * @returns A dds_return_t indicating success or failure + */ +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +DDS_EXPORT _Must_inspect_result_ dds_return_t +dds_return_loan( + _In_ dds_entity_t reader_or_condition, + _Inout_updates_(bufsz) void **buf, + _In_ size_t bufsz); + +/* + Instance handle <=> key value mapping. + Functions exactly as read w.r.t. treatment of data + parameter. On output, only key values set. + + T x = { ... }; + T y; + dds_instance_handle_t ih; + ih = dds_instance_lookup (e, &x); + dds_instance_get_key (e, ih, &y); +*/ + +/** + * Description : This operation takes a sample and returns an instance handle to be used for + * subsequent operations. + * + * Arguments : + * -# e Reader or Writer entity + * -# data sample with a key fields set + * -# Returns instance handle or DDS_HANDLE_NIL if instance could not be found from key + */ +DDS_EXPORT dds_return_t +dds_lookup_instance( + _In_ dds_entity_t entity, + _Out_ dds_instance_handle_t *handle, + _In_ const void *data); + +/** + * Description : This operation takes an instance handle and return a key-value corresponding to it. + * + * Arguments : + * -# e Reader or Writer entity + * -# inst Instance handle + * -# data pointer to an instance, to which the key ID corresponding to the instance handle will be + * returned, the sample in the instance should be ignored. + * -# Returns 0 on successful operation, or a non-zero value to indicate an error if the instance + * passed doesn't have a key-value + */ +DDS_EXPORT dds_return_t +dds_instance_get_key( + _In_ dds_entity_t entity, + _In_ dds_instance_handle_t handle, + _Out_ void *data); + +/** + * @brief Begin coherent publishing or begin accessing a coherent set in a subscriber + * + * Invoking on a Writer or Reader behaves as if dds_begin_coherent was invoked on its parent + * Publisher or Subscriber respectively. + * + * @param[in] e - The entity that is prepared for coherent access + * + * @returns - A dds_return_t indicating success or failure + * + * @retval DDS_RETCODE_ERROR + * An internal error has occurred. + * DDS_RETCODE_BAD_PARAMETER + * The provided entity is invalid or not supported + */ +_Pre_satisfies_(((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER) ) +DDS_EXPORT dds_return_t +dds_begin_coherent( + _In_ dds_entity_t entity); + +/** + * @brief End coherent publishing or end accessing a coherent set in a subscriber + * + * Invoking on a Writer or Reader behaves as if dds_end_coherent was invoked on its parent + * Publisher or Subscriber respectively. + * + * @param[in] e - The entity on which coherent access is finished + * + * @returns - A dds_return_t indicating success or failure + * + * @retval DDS_RETCODE_OK + * The operation was successful + * DDS_RETCODE_BAD_PARAMETER + * The provided entity is invalid or not supported + */ +_Pre_satisfies_(((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER) ) +DDS_EXPORT dds_return_t +dds_end_coherent( + _In_ dds_entity_t entity); + +/** + * @brief Trigger DATA_AVAILABLE event on contained readers + * + * The DATA_AVAILABLE event is broadcast to all readers owned by this subscriber that currently + * have new data available. Any on_data_available listener callbacks attached to respective + * readers are invoked. + * + * @param[in] sub A subscriber + * + * @returns - A dds_return_t indicating success or failure + * + * @reval DDS_RETCODE_OK + * The operation was successful + * DDS_RETCODE_BAD_PARAMETER + * The provided subscriber is invalid + */ +_Pre_satisfies_((subscriber & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER) +DDS_EXPORT dds_return_t +dds_notify_readers( + _In_ dds_entity_t subscriber); + + +/** + * Description : Resolves the domain-entity identified by id if it exists + * + * Arguments : + * -# id + */ +DDS_EXPORT dds_entity_t dds_get_domain(_In_ dds_domainid_t id); + +/** + * Description : Retrieves the matched publications (for a given Reader) or subscriptions (for a given Writer) + * + * Arguments : + * -# wr_or_r Writer or Reader + * -# handles Array of size nofHandles + * -# nofHandles Number of elements that can be written to in handles + * + * -# Returns the number of available matched publications or subscriptions. If return > nofHandles + * the resulting set is truncated. Handles are only initialized up to min(return, nofHandles). + */ +DDS_EXPORT dds_return_t dds_get_matched(_In_ dds_entity_t wr_or_r, _Out_writes_to_(nofHandles, return) dds_instance_handle_t *handles, _In_ size_t nofHandles); + +/** + * Description : Asserts the liveliness of the entity + * + * Arguments : + * -# e Entity + */ +DDS_EXPORT dds_return_t dds_assert_liveliness(_In_ dds_entity_t e); + +/** + * Description : Checks whether entity c is contained in entity e + * + * Containment is defined as follows: TODO + * + * Arguments : + * -# e Entity for which has to be determined whether c is contained within it + * -# c Entity to check for being contained in e + */ +DDS_EXPORT dds_return_t dds_contains(_In_ dds_entity_t e, _In_ dds_entity_t c); + +/** + * Description : Returns the current wall-clock as used for timestamps + */ +DDS_EXPORT dds_time_t dds_time(void); + +/** + * Description : Checks whether the entity has one of its enabled statuses triggered. + * + * Arguments : + * -# e Entity for which to check for triggered status + */ +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT dds_return_t +dds_triggered( + _In_ dds_entity_t entity); + +/* TODO: dds_create_contentfilteredtopic -> dds_create_topic_w_query and use dds_get_query and the like. */ +DDS_EXPORT dds_entity_t dds_create_contentfilteredtopic(_In_ dds_entity_t pp, _In_z_ const char * name, _In_ dds_entity_t related_topic, _In_z_ const char *expression, _In_reads_opt_z_(npars) const char ** parameters, _In_ size_t npars); + +/** + * Description : Tries to find the topic with the supplied name. + * + * Arguments : + * -# pp Participant + * -# name Topic-name to look for + */ +DDS_EXPORT dds_entity_t dds_lookup_topic(_In_ dds_entity_t pp, _In_z_ const char * name); + +/** + * Description : Ignore the entity described by handle. + * + * Arguments : + * -# pp Participant + * -# handle Instance-handle of entity to be ignored. + */ +DDS_EXPORT dds_return_t dds_ignore(_In_ dds_entity_t pp, _In_ dds_instance_handle_t handle); + +/** + * Description : Retrieve the topic on which the content-filtered-topic is based + * + * TODO: Refactor CFT + * + * Arguments : + * -# cft ContentFilteredTopic + */ +DDS_EXPORT dds_entity_t dds_get_related_topic(_In_ dds_entity_t cft); +/* DDS_EXPORT dds_return_t dds_contentfilteredtopic_get_parameters(...) see dds_get_query_patameters(...) */; +/* DDS_EXPORT dds_return_t dds_contentfilteredtopic_set_parameters(...) see dds_set_query_patameters(...) */; + +/** + * Description : Retrieve the query underlying the entity + * + * + * Arguments : + * -# top_mt_qc Topic, MultiTopic, QueryConditon + */ +DDS_EXPORT dds_entity_t dds_get_query(_In_ dds_entity_t top_mt_qc); + +/** + * Description : Retrieve the query-parameters + * + * + * Arguments : + * -# top_mt_qc Topic, MultiTopic, QueryConditon + */ +DDS_EXPORT dds_return_t dds_get_query_parameters(_In_ dds_entity_t e, _Out_writes_to_(npars, return) const char ** params, _In_ size_t npars); + +/** + * Description : Set the query-parameters + * + * Arguments : + * -# top_mt_qc Topic, MultiTopic, QueryConditon + */ +DDS_EXPORT dds_return_t dds_set_query_parameters(_In_ dds_entity_t e, _In_reads_opt_z_(npars) const char ** parameters, _In_ size_t npars); + +DDS_EXPORT dds_entity_t dds_get_topic(_In_ dds_entity_t e); /* Convenience-wrapper for (multiple) get_parent on Writer or Reader or their children*/ + +#if defined (__cplusplus) +} +#endif +#endif /* DDS_H */ diff --git a/src/core/ddsc/src/dds__alloc.h b/src/core/ddsc/src/dds__alloc.h new file mode 100644 index 0000000..a8f09db --- /dev/null +++ b/src/core/ddsc/src/dds__alloc.h @@ -0,0 +1,26 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_ALLOC_H_ +#define _DDS_ALLOC_H_ + +#include "dds__types.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +void dds_sample_free_contents (char * data, const uint32_t * ops); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__builtin.h b/src/core/ddsc/src/dds__builtin.h new file mode 100644 index 0000000..284f968 --- /dev/null +++ b/src/core/ddsc/src/dds__builtin.h @@ -0,0 +1,72 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_BUILTIN_H_ +#define _DDS_BUILTIN_H_ + +#include "ddsi/q_time.h" +#include "dds_builtinTopics.h" + + +#if defined (__cplusplus) +extern "C" +{ +#endif + + + +/* Get actual topic in related participant related to topic 'id'. */ +_Must_inspect_result_ dds_entity_t +dds__get_builtin_topic( + _In_ dds_entity_t e, + _In_ dds_entity_t topic); + +/* Global publisher singleton (publishes only locally). */ +_Must_inspect_result_ dds_entity_t +dds__get_builtin_publisher( + void); + +/* Subscriber singleton within related participant. */ +_Must_inspect_result_ dds_entity_t +dds__get_builtin_subscriber( + _In_ dds_entity_t e); + + + +/* Initialization and finalize functions. */ +void +dds__builtin_init( + void); + +void +dds__builtin_fini( + void); + + + +/* Callback functions that contain received builtin data. */ +void +dds__builtin_participant_cb( + DDS_ParticipantBuiltinTopicData *data, + nn_wctime_t timestamp); + +void +dds__builtin_cmparticipant_cb( + DDS_CMParticipantBuiltinTopicData *data, + nn_wctime_t timestamp); + + +#if defined (__cplusplus) +} +#endif + +#endif /* _DDS_BUILTIN_H_ */ + diff --git a/src/core/ddsc/src/dds__domain.h b/src/core/ddsc/src/dds__domain.h new file mode 100644 index 0000000..feaf014 --- /dev/null +++ b/src/core/ddsc/src/dds__domain.h @@ -0,0 +1,30 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_DOMAIN_H_ +#define _DDS_DOMAIN_H_ + +#include "dds__types.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +extern const ut_avlTreedef_t dds_domaintree_def; + +extern dds_domain * dds_domain_create (dds_domainid_t id); +extern void dds_domain_free (dds_domain * domain); +extern dds_domain * dds_domain_find_locked (dds_domainid_t id); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__entity.h b/src/core/ddsc/src/dds__entity.h new file mode 100644 index 0000000..2b62c14 --- /dev/null +++ b/src/core/ddsc/src/dds__entity.h @@ -0,0 +1,114 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_ENTITY_H_ +#define _DDS_ENTITY_H_ + +#include "dds__types.h" +#include "ddsi/q_thread.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +_Check_return_ dds_entity_t +dds_entity_init( + _In_ dds_entity * e, + _When_(kind != DDS_KIND_PARTICIPANT, _Notnull_) + _When_(kind == DDS_KIND_PARTICIPANT, _Null_) + _In_opt_ dds_entity * parent, + _In_ dds_entity_kind_t kind, + _In_opt_ dds_qos_t * qos, + _In_opt_ const dds_listener_t *listener, + _In_ uint32_t mask); + +void +dds_entity_add_ref( + _In_ dds_entity *e); +void +dds_entity_add_ref_nolock( + _In_ dds_entity *e); + +_Check_return_ dds__retcode_t +dds_entity_listener_propagation( + _Inout_opt_ dds_entity *e, + _In_ dds_entity *src, + _In_ uint32_t status, + _In_opt_ void *metrics, + _In_ bool propagate); + +#define dds_entity_is_enabled(e, k) (((dds_entity*)e)->m_flags & DDS_ENTITY_ENABLED) + +#define dds_entity_status_set(e, t) (((dds_entity*)e)->m_trigger |= (((dds_entity*)e)->m_status_enable & t)) +#define dds_entity_status_reset(e,t) (((dds_entity*)e)->m_trigger &= ~t) +#define dds_entity_status_match(e,t) (((dds_entity*)e)->m_trigger & t) + +/* The mutex needs to be unlocked when calling this because the entity can be called + * within the signal callback from other contexts. That shouldn't deadlock. */ +void +dds_entity_status_signal( + _In_ dds_entity *e); + +_Check_return_ dds__retcode_t +dds_valid_hdl( + _In_ dds_entity_t hdl, + _In_ dds_entity_kind_t kind); + +_Acquires_exclusive_lock_(*e) +_Check_return_ dds__retcode_t +dds_entity_lock( + _In_ dds_entity_t hdl, + _In_ dds_entity_kind_t kind, + _Out_ dds_entity **e); + +_Releases_exclusive_lock_(e) +void +dds_entity_unlock( + _Inout_ dds_entity *e); + +#define dds_entity_kind(hdl) ((hdl > 0) ? (hdl & DDS_ENTITY_KIND_MASK) : 0) + +_Check_return_ dds__retcode_t +dds_entity_observer_register_nl( + _In_ dds_entity* observed, + _In_ dds_entity_t observer, + _In_ dds_entity_callback cb); + +_Check_return_ dds__retcode_t +dds_entity_observer_register( + _In_ dds_entity_t observed, + _In_ dds_entity_t observer, + _In_ dds_entity_callback cb); + +dds__retcode_t +dds_entity_observer_unregister_nl( + _In_ dds_entity* observed, + _In_ dds_entity_t observer); + +dds__retcode_t +dds_entity_observer_unregister( + _In_ dds_entity_t observed, + _In_ dds_entity_t observer); + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +dds_return_t +dds_delete_impl( + _In_ dds_entity_t entity, + _In_ bool keep_if_explicit); + +const char * +dds__entity_kind_str( + _In_ dds_entity_t e); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__err.h b/src/core/ddsc/src/dds__err.h new file mode 100644 index 0000000..0e65a16 --- /dev/null +++ b/src/core/ddsc/src/dds__err.h @@ -0,0 +1,41 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_ERR_H_ +#define _DDS_ERR_H_ + +#include +#include "os/os.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +/* To construct return status + * Use '+' instead of '|'. Otherwise, the SAL checking doesn't + * understand when a return value is negative or positive and + * complains a lot about "A successful path through the function + * does not set the named _Out_ parameter." */ +#if !defined(__FILE_ID__) +#error "__FILE_ID__ not defined" +#endif + +#define DDS__FILE_ID__ (((__FILE_ID__ & 0x1ff)) << 22) +#define DDS__LINE__ ((__LINE__ & 0x3fff) << 8) + +#define DDS_ERR_NO(err) -(DDS__FILE_ID__ + DDS__LINE__ + (err)) + +#define DDS_ERRNO(e,msg,...) (assert(e > DDS_RETCODE_OK), os_report(OS_REPORT_ERROR, OS_FUNCTION, __FILE__, __LINE__, DDS_ERR_NO(e), (msg), ##__VA_ARGS__), DDS_ERR_NO(e)) + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__iid.h b/src/core/ddsc/src/dds__iid.h new file mode 100644 index 0000000..b7134d4 --- /dev/null +++ b/src/core/ddsc/src/dds__iid.h @@ -0,0 +1,28 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_IID_H_ +#define _DDS_IID_H_ + +#include "dds__types.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +void dds_iid_init (void); +void dds_iid_fini (void); +uint64_t dds_iid_gen (void); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__init.h b/src/core/ddsc/src/dds__init.h new file mode 100644 index 0000000..3bcc8af --- /dev/null +++ b/src/core/ddsc/src/dds__init.h @@ -0,0 +1,78 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_INIT_H_ +#define _DDS_INIT_H_ + +#include "dds__types.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +dds_return_t +dds__check_domain( + _In_ dds_domainid_t domain); + +/** + *Description : Initialization function. This operation initializes all the + *required resources that are needed for the DDSC API process lifecycle + *(like the init mutex and os layer). + *A function will be registered that is called at the end of the process + *lifecycle and will destroy the resources initialized in this function. + **/ +void +dds__startup(void); + +/** + *Description : Initialization function, called from main. This operation + *initializes all the required DDS resources, + *handles configuration of domainid based on the input passed, parses and + *configures middleware from a xml file and initializes required resources. + * + *Arguments : + *-# Returns 0 on success or a non-zero error status + **/ +dds_return_t +dds_init(void); + +/* Finalization function, called from main */ + +/** + *Description : Finalization function, called from main. This operation + *releases all the resources used by DDS. + * + *Arguments : + *-# None + **/ +void +dds_fini(void); + + + +/** + * Description : Function that provides the explicit ID of default domain + * It should be called after DDS initialization. + * @return Valid domain id. Undetermined behaviour if DDS is not initialized. + */ +dds_domainid_t dds_domain_default (void); + + +/** + * Description : Mutex used for initialization synchronization. + */ +extern os_mutex dds__init_mutex; + + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__key.h b/src/core/ddsc/src/dds__key.h new file mode 100644 index 0000000..b01f8bf --- /dev/null +++ b/src/core/ddsc/src/dds__key.h @@ -0,0 +1,35 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_KEY_H_ +#define _DDS_KEY_H_ + +#include "dds__types.h" + +struct dds_key_hash; + +#if defined (__cplusplus) +extern "C" { +#endif + +void dds_key_md5 (struct dds_key_hash * kh); + +void dds_key_gen +( + const dds_topic_descriptor_t * const desc, + struct dds_key_hash * kh, + const char * sample +); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__listener.h b/src/core/ddsc/src/dds__listener.h new file mode 100644 index 0000000..b25453b --- /dev/null +++ b/src/core/ddsc/src/dds__listener.h @@ -0,0 +1,31 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_LISTENER_H_ +#define _DDS_LISTENER_H_ + +#include "dds__types.h" +#include "ddsc/dds_public_listener.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +/* + * Listener API (internal & external) are present in + * dds__types.h + * ddsc/dds_public_listener.h + */ + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__participant.h b/src/core/ddsc/src/dds__participant.h new file mode 100644 index 0000000..0299a7a --- /dev/null +++ b/src/core/ddsc/src/dds__participant.h @@ -0,0 +1,23 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_PPANT_H_ +#define _DDS_PPANT_H_ + +#if defined (__cplusplus) +extern "C" { +#endif + + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__publisher.h b/src/core/ddsc/src/dds__publisher.h new file mode 100644 index 0000000..8551d42 --- /dev/null +++ b/src/core/ddsc/src/dds__publisher.h @@ -0,0 +1,27 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_PUBLISHER_H_ +#define _DDS_PUBLISHER_H_ + +#include "ddsc/dds.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +dds_return_t dds_publisher_begin_coherent (dds_entity_t e); +dds_return_t dds_publisher_end_coherent (dds_entity_t e); + +#if defined (__cplusplus) +} +#endif +#endif /* _DDS_PUBLISHER_H_ */ diff --git a/src/core/ddsc/src/dds__qos.h b/src/core/ddsc/src/dds__qos.h new file mode 100644 index 0000000..0f2e1a1 --- /dev/null +++ b/src/core/ddsc/src/dds__qos.h @@ -0,0 +1,37 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_QOS_H_ +#define _DDS_QOS_H_ + +#include "dds__entity.h" +#include "ddsi/q_xqos.h" +#include "ddsi/q_time.h" +#include "ddsi/q_plist.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +bool validate_deadline_and_timebased_filter (const nn_duration_t deadline, const nn_duration_t minimum_separation); +bool validate_entityfactory_qospolicy (const nn_entity_factory_qospolicy_t * entityfactory); +bool validate_octetseq (const nn_octetseq_t* seq); +bool validate_partition_qospolicy (_In_ const nn_partition_qospolicy_t * partition); +bool validate_reliability_qospolicy (const nn_reliability_qospolicy_t * reliability); +bool validate_stringseq (const nn_stringseq_t* seq); + +bool dds_qos_validate_common (const dds_qos_t *qos); +dds_return_t dds_qos_validate_mutable_common (_In_ const dds_qos_t *qos); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__querycond.h b/src/core/ddsc/src/dds__querycond.h new file mode 100644 index 0000000..db62ee6 --- /dev/null +++ b/src/core/ddsc/src/dds__querycond.h @@ -0,0 +1,23 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_QUERYCOND_H_ +#define _DDS_QUERYCOND_H_ + +#if defined (__cplusplus) +extern "C" +{ +#endif + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__readcond.h b/src/core/ddsc/src/dds__readcond.h new file mode 100644 index 0000000..adedd2d --- /dev/null +++ b/src/core/ddsc/src/dds__readcond.h @@ -0,0 +1,23 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_READCOND_H_ +#define _DDS_READCOND_H_ + +#include "dds__entity.h" + +_Must_inspect_result_ dds_readcond* +dds_create_readcond( + _In_ dds_reader *rd, + _In_ dds_entity_kind_t kind, + _In_ uint32_t mask); + +#endif diff --git a/src/core/ddsc/src/dds__reader.h b/src/core/ddsc/src/dds__reader.h new file mode 100644 index 0000000..85ce0a0 --- /dev/null +++ b/src/core/ddsc/src/dds__reader.h @@ -0,0 +1,49 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_READER_H_ +#define _DDS_READER_H_ + +#include "dds__types.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +struct status_cb_data; + +void dds_reader_status_cb (void * entity, const struct status_cb_data * data); + +/* + dds_reader_lock_samples: Returns number of samples in read cache and locks the + reader cache to make sure that the samples content doesn't change. + Because the cache is locked, you should be able to read/take without having to + lock first. This is done by passing the DDS_READ_WITHOUT_LOCK value to the + read/take call as maxs. Then the read/take will not lock but still unlock. + + See also CHAM-287, CHAM-306 and LITE-1183. + + Used to support LENGTH_UNLIMITED in C++. +*/ +#define DDS_READ_WITHOUT_LOCK (0xFFFFFFED) +DDS_EXPORT uint32_t dds_reader_lock_samples (dds_entity_t entity); + +struct nn_rsample_info; +struct nn_rdata; +DDS_EXPORT void dds_reader_ddsi2direct (dds_entity_t entity, void (*cb) (const struct nn_rsample_info *sampleinfo, const struct nn_rdata *fragchain, void *arg), void *cbarg); + +#define dds_reader_lock(hdl, obj) dds_entity_lock(hdl, DDS_KIND_READER, (dds_entity**)obj) +#define dds_reader_unlock(obj) dds_entity_unlock((dds_entity*)obj); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__report.h b/src/core/ddsc/src/dds__report.h new file mode 100644 index 0000000..ced10af --- /dev/null +++ b/src/core/ddsc/src/dds__report.h @@ -0,0 +1,78 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef DDS_REPORT_H +#define DDS_REPORT_H + +#include +#include "os/os_report.h" + +#define DDS_REPORT_STACK() \ + os_report_stack () + +#define DDS_CRITICAL(...) \ + dds_report ( \ + OS_REPORT_CRITICAL, \ + __FILE__, \ + __LINE__, \ + OS_FUNCTION, \ + DDS_RETCODE_ERROR, \ + __VA_ARGS__) + +#define DDS_ERROR(code,...) \ + dds_report ( \ + OS_REPORT_ERROR, \ + __FILE__, \ + __LINE__, \ + OS_FUNCTION, \ + (code), \ + __VA_ARGS__) + +#define DDS_INFO(...) \ + dds_report ( \ + OS_REPORT_INFO, \ + __FILE__, \ + __LINE__, \ + OS_FUNCTION, \ + DDS_RETCODE_OK, \ + __VA_ARGS__) + +#define DDS_WARNING(code,...) \ + dds_report ( \ + OS_REPORT_WARNING, \ + __FILE__, \ + __LINE__, \ + OS_FUNCTION, \ + (code), \ + __VA_ARGS__) + +#define DDS_REPORT(type, code,...) \ + dds_report ( \ + type, \ + __FILE__, \ + __LINE__, \ + OS_FUNCTION, \ + (code), \ + __VA_ARGS__) + +#define DDS_REPORT_FLUSH OS_REPORT_FLUSH + +void +dds_report( + os_reportType reportType, + const char *file, + int32_t line, + const char *function, + dds_return_t code, + const char *format, + ...); + +#endif diff --git a/src/core/ddsc/src/dds__rhc.h b/src/core/ddsc/src/dds__rhc.h new file mode 100644 index 0000000..0e5f9f8 --- /dev/null +++ b/src/core/ddsc/src/dds__rhc.h @@ -0,0 +1,86 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_RHC_H_ +#define _DDS_RHC_H_ + +#include "os/os_defs.h" + +#define NO_STATE_MASK_SET (DDS_ANY_STATE + 1) + + +#if defined (__cplusplus) +extern "C" { +#endif + +struct rhc; +struct nn_xqos; +struct serdata; +struct tkmap_instance; +struct tkmap; +struct nn_rsample_info; +struct proxy_writer_info; + +struct rhc * dds_rhc_new (dds_reader * reader, const struct sertopic * topic); +void dds_rhc_free (struct rhc * rhc); +void dds_rhc_fini (struct rhc * rhc); + +uint32_t dds_rhc_lock_samples (struct rhc * rhc); + +DDS_EXPORT bool dds_rhc_store +( + struct rhc * __restrict rhc, const struct nn_rsample_info * __restrict sampleinfo, + struct serdata * __restrict sample, struct tkmap_instance * __restrict tk +); +void dds_rhc_unregister_wr (struct rhc * __restrict rhc, const struct proxy_writer_info * __restrict pwr_info); +void dds_rhc_relinquish_ownership (struct rhc * __restrict rhc, const uint64_t wr_iid); + +int +dds_rhc_read( + struct rhc *rhc, + bool lock, + void ** values, + dds_sample_info_t *info_seq, + uint32_t max_samples, + uint32_t mask, + dds_instance_handle_t handle, + dds_readcond *cond); +int +dds_rhc_take( + struct rhc *rhc, + bool lock, + void ** values, + dds_sample_info_t *info_seq, + uint32_t max_samples, + uint32_t mask, + dds_instance_handle_t handle, + dds_readcond *cond); + +void dds_rhc_set_qos (struct rhc * rhc, const struct nn_xqos * qos); + +void dds_rhc_add_readcondition (dds_readcond * cond); +void dds_rhc_remove_readcondition (dds_readcond * cond); + +bool dds_rhc_add_waitset (dds_readcond * cond, dds_waitset * waitset, dds_attach_t x); +int dds_rhc_remove_waitset (dds_readcond * cond, dds_waitset * waitset); + +int dds_rhc_takecdr +( + struct rhc *rhc, bool lock, struct serdata **values, dds_sample_info_t *info_seq, + uint32_t max_samples, unsigned sample_states, + unsigned view_states, unsigned instance_states, + dds_instance_handle_t handle +); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__stream.h b/src/core/ddsc/src/dds__stream.h new file mode 100644 index 0000000..52d8737 --- /dev/null +++ b/src/core/ddsc/src/dds__stream.h @@ -0,0 +1,88 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_STREAM_H_ +#define _DDS_STREAM_H_ + +#include "ddsi/ddsi_ser.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +void dds_stream_write_sample +( + dds_stream_t * os, + const void * data, + const struct sertopic * topic +); +void dds_stream_read_sample +( + dds_stream_t * is, + void * data, + const struct sertopic * topic +); + +size_t dds_stream_check_optimize (_In_ const dds_topic_descriptor_t * desc); +void dds_stream_from_serstate (_Out_ dds_stream_t * s, _In_ const serstate_t st); +void dds_stream_add_to_serstate (_Inout_ dds_stream_t * s, _Inout_ serstate_t st); + +void dds_stream_write_key +( + dds_stream_t * os, + const char * sample, + const dds_topic_descriptor_t * desc +); +void dds_stream_read_key +( + dds_stream_t * is, + char * sample, + const dds_topic_descriptor_t * desc +); +void dds_stream_read_keyhash +( + dds_stream_t * is, + dds_key_hash_t * kh, + const dds_topic_descriptor_t * desc, + const bool just_key +); +char * dds_stream_reuse_string +( + dds_stream_t * is, + char * str, + const uint32_t bound +); +DDS_EXPORT void dds_stream_swap (void * buff, uint32_t size, uint32_t num); + +extern const uint32_t dds_op_size[5]; + +/* For marshalling op code handling */ + +#define DDS_OP_MASK 0xff000000 +#define DDS_OP_TYPE_MASK 0x00ff0000 +#define DDS_OP_SUBTYPE_MASK 0x0000ff00 +#define DDS_OP_JMP_MASK 0x0000ffff +#define DDS_OP_FLAGS_MASK 0x000000ff +#define DDS_JEQ_TYPE_MASK 0x00ff0000 + +#define DDS_OP(o) ((o) & DDS_OP_MASK) +#define DDS_OP_TYPE(o) (((o) & DDS_OP_TYPE_MASK) >> 16) +#define DDS_OP_SUBTYPE(o) (((o) & DDS_OP_SUBTYPE_MASK) >> 8) +#define DDS_OP_FLAGS(o) ((o) & DDS_OP_FLAGS_MASK) +#define DDS_OP_ADR_JSR(o) ((o) & DDS_OP_JMP_MASK) +#define DDS_OP_JUMP(o) ((int16_t) ((o) & DDS_OP_JMP_MASK)) +#define DDS_OP_ADR_JMP(o) ((o) >> 16) +#define DDS_JEQ_TYPE(o) (((o) & DDS_JEQ_TYPE_MASK) >> 16) + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__subscriber.h b/src/core/ddsc/src/dds__subscriber.h new file mode 100644 index 0000000..3345cbf --- /dev/null +++ b/src/core/ddsc/src/dds__subscriber.h @@ -0,0 +1,41 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_SUBSCRIBER_H_ +#define _DDS_SUBSCRIBER_H_ + +#include "ddsc/dds.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +struct dds_entity; + +_Requires_exclusive_lock_held_(participant) +_Check_return_ dds_entity_t +dds__create_subscriber_l( + _Inout_ struct dds_entity *participant, /* entity-lock must be held */ + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener); + +dds_return_t +dds_subscriber_begin_coherent( + _In_ dds_entity_t e); + +dds_return_t +dds_subscriber_end_coherent ( + _In_ dds_entity_t e); + +#if defined (__cplusplus) +} +#endif +#endif /* _DDS_SUBSCRIBER_H_ */ diff --git a/src/core/ddsc/src/dds__tkmap.h b/src/core/ddsc/src/dds__tkmap.h new file mode 100644 index 0000000..1c8ffdb --- /dev/null +++ b/src/core/ddsc/src/dds__tkmap.h @@ -0,0 +1,53 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_TKMAP_H_ +#define _DDS_TKMAP_H_ + +#include "dds__types.h" +#include "os/os_atomics.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +struct tkmap; +struct serdata; +struct dds_topic; + +struct tkmap_instance +{ + struct serdata * m_sample; + struct tkmap * m_map; + uint64_t m_iid; + os_atomic_uint32_t m_refc; +}; + + +struct tkmap * dds_tkmap_new (void); +void dds_tkmap_free (_Inout_ _Post_invalid_ struct tkmap *tkmap); +void dds_tkmap_instance_ref (_In_ struct tkmap_instance *tk); +uint64_t dds_tkmap_lookup (_In_ struct tkmap *tkmap, _In_ const struct serdata *serdata); +_Check_return_ bool dds_tkmap_get_key (_In_ struct tkmap * map, _In_ uint64_t iid, _Out_ void * sample); +_Check_return_ struct tkmap_instance * dds_tkmap_find( + _In_opt_ const struct dds_topic * topic, + _In_ struct serdata * sd, + _In_ const bool rd, + _In_ const bool create); +_Check_return_ struct tkmap_instance * dds_tkmap_find_by_id (_In_ struct tkmap * map, _In_ uint64_t iid); + +DDS_EXPORT _Check_return_ struct tkmap_instance * dds_tkmap_lookup_instance_ref (_In_ struct serdata * sd); +DDS_EXPORT void dds_tkmap_instance_unref (_In_ struct tkmap_instance * tk); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__topic.h b/src/core/ddsc/src/dds__topic.h new file mode 100644 index 0000000..43e238c --- /dev/null +++ b/src/core/ddsc/src/dds__topic.h @@ -0,0 +1,41 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_TOPIC_H_ +#define _DDS_TOPIC_H_ + +#include "dds__types.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +#define dds_topic_lock(hdl, obj) dds_entity_lock(hdl, DDS_KIND_TOPIC, (dds_entity**)obj) +#define dds_topic_unlock(obj) dds_entity_unlock((dds_entity*)obj); + +extern struct sertopic * dds_topic_lookup (dds_domain * domain, const char * name); +extern void dds_topic_free (dds_domainid_t domainid, struct sertopic * st); + +#ifndef DDS_TOPIC_INTERN_FILTER_FN_DEFINED +#define DDS_TOPIC_INTERN_FILTER_FN_DEFINED +typedef bool (*dds_topic_intern_filter_fn) (const void * sample, void *ctx); +#endif + +DDS_EXPORT void dds_topic_set_filter_with_ctx + (dds_entity_t topic, dds_topic_intern_filter_fn filter, void *ctx); + +DDS_EXPORT dds_topic_intern_filter_fn dds_topic_get_filter_with_ctx + (dds_entity_t topic); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__types.h b/src/core/ddsc/src/dds__types.h new file mode 100644 index 0000000..8b289f6 --- /dev/null +++ b/src/core/ddsc/src/dds__types.h @@ -0,0 +1,273 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_TYPES_H_ +#define _DDS_TYPES_H_ + +/* DDS internal type definitions */ + +#include "os/os.h" +#include "ddsc/dds.h" +#include "ddsi/q_rtps.h" +#include "util/ut_avl.h" +#include "util/ut_handleserver.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +typedef _Return_type_success_(return == DDS_RETCODE_OK) int32_t dds__retcode_t; + +struct dds_domain; +struct dds_entity; +struct dds_participant; +struct dds_reader; +struct dds_writer; +struct dds_publisher; +struct dds_subscriber; +struct dds_topic; +struct dds_readcond; +struct dds_guardcond; + +struct sertopic; +struct rhc; + +/* Internal entity status flags */ + +#define DDS_INTERNAL_STATUS_MASK (0xFF000000) + +#define DDS_WAITSET_TRIGGER_STATUS (0x01000000) +#define DDS_DELETING_STATUS (0x02000000) + +/* This can be used when polling for various states. + * Obviously, it is encouraged to use condition variables and such. But + * sometimes it wouldn't make that much of a difference and taking the + * easy route is somewhat pragmatic. */ +#define DDS_HEADBANG_TIMEOUT_MS (10) + +typedef bool (*dds_querycondition_filter_with_ctx_fn) (const void * sample, const void *ctx); + + +/* The listener struct. */ + +typedef struct c_listener { + dds_on_inconsistent_topic_fn on_inconsistent_topic; + dds_on_liveliness_lost_fn on_liveliness_lost; + dds_on_offered_deadline_missed_fn on_offered_deadline_missed; + dds_on_offered_incompatible_qos_fn on_offered_incompatible_qos; + dds_on_data_on_readers_fn on_data_on_readers; + dds_on_sample_lost_fn on_sample_lost; + dds_on_data_available_fn on_data_available; + dds_on_sample_rejected_fn on_sample_rejected; + dds_on_liveliness_changed_fn on_liveliness_changed; + dds_on_requested_deadline_missed_fn on_requested_deadline_missed; + dds_on_requested_incompatible_qos_fn on_requested_incompatible_qos; + dds_on_publication_matched_fn on_publication_matched; + dds_on_subscription_matched_fn on_subscription_matched; + void *arg; +} c_listener_t; + +/* Entity flag values */ + +#define DDS_ENTITY_ENABLED 0x0001 +#define DDS_ENTITY_IMPLICIT 0x0002 + +typedef struct dds_domain +{ + ut_avlNode_t m_node; + dds_domainid_t m_id; + ut_avlTree_t m_topics; + uint32_t m_refc; +} +dds_domain; + +struct dds_entity; +typedef struct dds_entity_deriver { + /* Close can be used to terminate (blocking) actions on a entity before actually deleting it. */ + dds_return_t (*close)(struct dds_entity *e); + /* Delete is used to actually free the entity. */ + dds_return_t (*delete)(struct dds_entity *e); + dds_return_t (*set_qos)(struct dds_entity *e, const dds_qos_t *qos, bool enabled); + dds_return_t (*validate_status)(uint32_t mask); + dds_return_t (*propagate_status)(struct dds_entity *e, uint32_t mask, bool set); + dds_return_t (*get_instance_hdl)(struct dds_entity *e, dds_instance_handle_t *i); +} +dds_entity_deriver; + +typedef void (*dds_entity_callback)(dds_entity_t observer, dds_entity_t observed, uint32_t status); + +typedef struct dds_entity_observer +{ + dds_entity_callback m_cb; + dds_entity_t m_observer; + struct dds_entity_observer *m_next; +} +dds_entity_observer; + +typedef struct dds_entity +{ + ut_handle_t m_hdl; + dds_entity_deriver m_deriver; + uint32_t m_refc; + struct dds_entity * m_next; + struct dds_entity * m_parent; + struct dds_entity * m_children; + struct dds_entity * m_participant; + struct dds_domain * m_domain; + dds_qos_t * m_qos; + dds_domainid_t m_domainid; + nn_guid_t m_guid; + uint32_t m_status_enable; + uint32_t m_flags; + uint32_t m_cb_count; + os_mutex m_mutex; + os_cond m_cond; + c_listener_t m_listener; + uint32_t m_trigger; + dds_entity_observer *m_observers; + struct ut_handlelink *m_hdllink; +} +dds_entity; + +extern const ut_avlTreedef_t dds_topictree_def; + +typedef struct dds_subscriber +{ + struct dds_entity m_entity; +} +dds_subscriber; + +typedef struct dds_publisher +{ + struct dds_entity m_entity; +} +dds_publisher; + +typedef struct dds_participant +{ + struct dds_entity m_entity; + struct dds_entity * m_dur_reader; + struct dds_entity * m_dur_writer; + dds_entity_t m_builtin_subscriber; +} +dds_participant; + +typedef struct dds_reader +{ + struct dds_entity m_entity; + const struct dds_topic * m_topic; + struct reader * m_rd; + bool m_data_on_readers; + bool m_loan_out; + char * m_loan; + uint32_t m_loan_size; + + /* Status metrics */ + + dds_sample_rejected_status_t m_sample_rejected_status; + dds_liveliness_changed_status_t m_liveliness_changed_status; + dds_requested_deadline_missed_status_t m_requested_deadline_missed_status; + dds_requested_incompatible_qos_status_t m_requested_incompatible_qos_status; + dds_sample_lost_status_t m_sample_lost_status; + dds_subscription_matched_status_t m_subscription_matched_status; +} +dds_reader; + +typedef struct dds_writer +{ + struct dds_entity m_entity; + const struct dds_topic * m_topic; + struct nn_xpack * m_xp; + struct writer * m_wr; + os_mutex m_call_lock; + + /* Status metrics */ + + dds_liveliness_lost_status_t m_liveliness_lost_status; + dds_offered_deadline_missed_status_t m_offered_deadline_missed_status; + dds_offered_incompatible_qos_status_t m_offered_incompatible_qos_status; + dds_publication_matched_status_t m_publication_matched_status; +} +dds_writer; + +typedef struct dds_topic +{ + struct dds_entity m_entity; + struct sertopic * m_stopic; + const dds_topic_descriptor_t * m_descriptor; + + /* Status metrics */ + + dds_inconsistent_topic_status_t m_inconsistent_topic_status; +} +dds_topic; + +typedef struct dds_readcond +{ + dds_entity m_entity; + struct rhc * m_rhc; + uint32_t m_qminv; + uint32_t m_sample_states; + uint32_t m_view_states; + uint32_t m_instance_states; + nn_guid_t m_rd_guid; + struct dds_readcond * m_rhc_next; + struct + { + dds_querycondition_filter_fn m_filter; + } m_query; +} +dds_readcond; + +typedef struct dds_attachment +{ + dds_entity *entity; + dds_attach_t arg; + struct dds_attachment* next; +} +dds_attachment; + +typedef struct dds_waitset +{ + dds_entity m_entity; + dds_attachment *observed; + dds_attachment *triggered; +} +dds_waitset; + +typedef struct dds_iid +{ + uint64_t counter; + uint32_t key[4]; +} +dds_iid; + +/* Globals */ + +typedef struct dds_globals +{ + dds_domainid_t m_default_domain; + int32_t m_init_count; + void (*m_dur_reader) (struct dds_reader * reader, struct rhc * rhc); + int (*m_dur_wait) (struct dds_reader * reader, dds_duration_t timeout); + void (*m_dur_init) (void); + void (*m_dur_fini) (void); + ut_avlTree_t m_domains; + os_mutex m_mutex; +} +dds_globals; + +DDS_EXPORT extern dds_globals dds_global; + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__write.h b/src/core/ddsc/src/dds__write.h new file mode 100644 index 0000000..149f950 --- /dev/null +++ b/src/core/ddsc/src/dds__write.h @@ -0,0 +1,51 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_WRITE_H_ +#define _DDS_WRITE_H_ + +#if defined (__cplusplus) +extern "C" { +#endif + +#define DDS_WR_KEY_BIT 0x01 +#define DDS_WR_DISPOSE_BIT 0x02 +#define DDS_WR_UNREGISTER_BIT 0x04 + +typedef enum +{ + DDS_WR_ACTION_WRITE = 0, + DDS_WR_ACTION_WRITE_DISPOSE = DDS_WR_DISPOSE_BIT, + DDS_WR_ACTION_DISPOSE = DDS_WR_KEY_BIT | DDS_WR_DISPOSE_BIT, + DDS_WR_ACTION_UNREGISTER = DDS_WR_KEY_BIT | DDS_WR_UNREGISTER_BIT +} +dds_write_action; + +int +dds_write_impl( + _In_ dds_writer *wr, + _In_ const void *data, + _In_ dds_time_t tstamp, + _In_ dds_write_action action); + +int +dds_writecdr_impl( + _In_ dds_writer *wr, + _In_ const void *cdr, + _In_ size_t sz, + _In_ dds_time_t tstamp, + _In_ dds_write_action action); + + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds__writer.h b/src/core/ddsc/src/dds__writer.h new file mode 100644 index 0000000..25b21e8 --- /dev/null +++ b/src/core/ddsc/src/dds__writer.h @@ -0,0 +1,27 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDS_WRITER_H_ +#define _DDS_WRITER_H_ + +#include "dds__entity.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +#define dds_writer_lock(hdl, obj) dds_entity_lock(hdl, DDS_KIND_WRITER, (dds_entity**)obj) +#define dds_writer_unlock(obj) dds_entity_unlock((dds_entity*)obj); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/dds_alloc.c b/src/core/ddsc/src/dds_alloc.c new file mode 100644 index 0000000..d3c566b --- /dev/null +++ b/src/core/ddsc/src/dds_alloc.c @@ -0,0 +1,371 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include "dds__alloc.h" +#include "dds__stream.h" +#include "os/os_heap.h" +#include "ddsi/q_config.h" + +/* +#define OP_DEBUG_FREE 1 +*/ + +#if defined OP_DEBUG_FREE +static const char * stream_op_type[11] = +{ + NULL, "1Byte", "2Byte", "4Byte", "8Byte", "String", + "BString", "Sequence", "Array", "Union", "Struct" +}; +#endif + +static dds_allocator_t dds_allocator_fns = { os_malloc, os_realloc, os_free }; + +void * dds_alloc (size_t size) +{ + void * ret = (dds_allocator_fns.malloc) (size); + if (ret) + { + memset (ret, 0, size); + } + else + { + DDS_FAIL ("dds_alloc"); + } + return ret; +} + +void * dds_realloc (void * ptr, size_t size) +{ + void * ret = (dds_allocator_fns.realloc) (ptr, size); + if (ret == NULL) DDS_FAIL ("dds_realloc"); + return ret; +} + +void * dds_realloc_zero (void * ptr, size_t size) +{ + void * ret = dds_realloc (ptr, size); + if (ret) + { + memset (ret, 0, size); + } + return ret; +} + +void dds_free (void * ptr) +{ + if (ptr) (dds_allocator_fns.free) (ptr); +} + +char * dds_string_alloc (size_t size) +{ + return (char*) dds_alloc (size + 1); +} + +char * dds_string_dup (const char * str) +{ + char * ret = NULL; + if (str) + { + ret = dds_alloc (strlen (str) + 1); + strcpy (ret, str); + } + return ret; +} + +void dds_string_free (char * str) +{ + dds_free (str); +} + +void dds_sample_free_contents (char * data, const uint32_t * ops) +{ + uint32_t op; + uint32_t type; + uint32_t num; + uint32_t subtype; + char * addr; + + while ((op = *ops) != DDS_OP_RTS) + { + switch (DDS_OP_MASK & op) + { + case DDS_OP_ADR: + { + type = DDS_OP_TYPE (op); +#ifdef OP_DEBUG_FREE + TRACE (("F-ADR: %s offset %d\n", stream_op_type[type], ops[1])); +#endif + addr = data + ops[1]; + ops += 2; + switch (type) + { + case DDS_OP_VAL_1BY: + case DDS_OP_VAL_2BY: + case DDS_OP_VAL_4BY: + case DDS_OP_VAL_8BY: + { + break; + } + case DDS_OP_VAL_STR: + { +#ifdef OP_DEBUG_FREE + TRACE (("F-STR: @ %p %s\n", addr, *((char**) addr))); +#endif + dds_free (*((char**) addr)); + *((char**) addr) = NULL; + break; + } + case DDS_OP_VAL_SEQ: + { + dds_sequence_t * seq = (dds_sequence_t*) addr; + subtype = DDS_OP_SUBTYPE (op); + num = (seq->_maximum > seq->_length) ? seq->_maximum : seq->_length; + +#ifdef OP_DEBUG_FREE + TRACE (("F-SEQ: of %s\n", stream_op_type[subtype])); +#endif + if ((seq->_release && num) || (subtype > DDS_OP_VAL_STR)) + { + switch (subtype) + { + case DDS_OP_VAL_1BY: + case DDS_OP_VAL_2BY: + case DDS_OP_VAL_4BY: + case DDS_OP_VAL_8BY: + { + break; + } + case DDS_OP_VAL_BST: + { + ops++; + break; + } + case DDS_OP_VAL_STR: + { + char ** ptr = (char**) seq->_buffer; + while (num--) + { + dds_free (*ptr++); + } + break; + } + default: + { + const uint32_t elem_size = *ops++; + const uint32_t * jsr_ops = ops + DDS_OP_ADR_JSR (*ops) - 3; + const uint32_t jmp = DDS_OP_ADR_JMP (*ops); + char * ptr = (char*) seq->_buffer; + + while (num--) + { + dds_sample_free_contents (ptr, jsr_ops); + ptr += elem_size; + } + ops += jmp ? (jmp - 3) : 1; + break; + } + } + } + if (seq->_release) + { + dds_free (seq->_buffer); + seq->_buffer = NULL; + } + break; + } + case DDS_OP_VAL_ARR: + { + subtype = DDS_OP_SUBTYPE (op); + num = *ops++; + +#ifdef OP_DEBUG_FREE + TRACE (("F-ARR: of %s size %d\n", stream_op_type[subtype], num)); +#endif + switch (subtype) + { + case DDS_OP_VAL_1BY: + case DDS_OP_VAL_2BY: + case DDS_OP_VAL_4BY: + case DDS_OP_VAL_8BY: + { + break; + } + case DDS_OP_VAL_STR: + { + char ** ptr = (char**) addr; + while (num--) + { + dds_free (*ptr++); + } + break; + } + case DDS_OP_VAL_BST: + { + ops += 2; + break; + } + default: + { + const uint32_t * jsr_ops = ops + DDS_OP_ADR_JSR (*ops) - 3; + const uint32_t jmp = DDS_OP_ADR_JMP (*ops); + const uint32_t elem_size = ops[1]; + + while (num--) + { + dds_sample_free_contents (addr, jsr_ops); + addr += elem_size; + } + ops += jmp ? (jmp - 3) : 2; + break; + } + } + break; + } + case DDS_OP_VAL_UNI: + { + const bool has_default = op & DDS_OP_FLAG_DEF; + subtype = DDS_OP_SUBTYPE (op); + num = ops[0]; + const uint32_t * jeq_op = ops + DDS_OP_ADR_JSR (ops[1]) - 2; + uint32_t disc = 0; + + assert (subtype <= DDS_OP_VAL_4BY); + +#ifdef OP_DEBUG_FREE + TRACE (("F-UNI: switch %s cases %d\n", stream_op_type[subtype], num)); +#endif + /* Get discriminant */ + + switch (subtype) + { + case DDS_OP_VAL_1BY: + { + disc = *((uint8_t*) addr); + break; + } + case DDS_OP_VAL_2BY: + { + disc = *((uint16_t*) addr); + break; + } + case DDS_OP_VAL_4BY: + { + disc = *((uint32_t*) addr); + break; + } + default: assert (0); + } + + /* Free case matching discriminant */ + + while (num--) + { + assert ((DDS_OP_MASK & jeq_op[0]) == DDS_OP_JEQ); + if ((jeq_op[1] == disc) || (has_default && (num == 0))) + { + subtype = DDS_JEQ_TYPE (jeq_op[0]); + addr = data + jeq_op[2]; + + switch (subtype) + { + case DDS_OP_VAL_1BY: + case DDS_OP_VAL_2BY: + case DDS_OP_VAL_4BY: + case DDS_OP_VAL_8BY: + case DDS_OP_VAL_BST: + { + break; + } + case DDS_OP_VAL_STR: + { + dds_free (*((char**) addr)); + *((char**) addr) = NULL; + break; + } + default: + { + dds_sample_free_contents (addr, jeq_op + DDS_OP_ADR_JSR (jeq_op[0])); + break; + } + } + break; + } + jeq_op += 3; + } + + /* Jump to next instruction */ + + ops += DDS_OP_ADR_JMP (ops[1]) - 2; + break; + } + case DDS_OP_VAL_BST: + { + ops++; + break; + } + default: assert (0); + } + break; + } + case DDS_OP_JSR: /* Implies nested type */ + { +#ifdef OP_DEBUG_FREE + TRACE (("F-JSR: %d\n", DDS_OP_JUMP (op))); +#endif + dds_sample_free_contents (data, ops + DDS_OP_JUMP (op)); + ops++; + break; + } + default: assert (0); + } + } +#ifdef OP_DEBUG_FREE + TRACE (("F-RTS:\n")); +#endif +} + +static void dds_sample_free_key (char * sample, const struct dds_topic_descriptor * desc) +{ + uint32_t i; + const uint32_t * op; + + for (i = 0; i < desc->m_nkeys; i++) + { + op = desc->m_ops + desc->m_keys[i].m_index; + if (DDS_OP_TYPE (*op) == DDS_OP_VAL_STR) + { + dds_free (*(char**)(sample + op[1])); + } + } +} + +void dds_sample_free (void * sample, const struct dds_topic_descriptor * desc, dds_free_op_t op) +{ + assert (desc); + + if (sample) + { + if (op & DDS_FREE_CONTENTS_BIT) + { + dds_sample_free_contents ((char*) sample, desc->m_ops); + } + else if (op & DDS_FREE_KEY_BIT) + { + dds_sample_free_key ((char*) sample, desc); + } + if (op & DDS_FREE_ALL_BIT) + { + dds_free (sample); + } + } +} diff --git a/src/core/ddsc/src/dds_builtin.c b/src/core/ddsc/src/dds_builtin.c new file mode 100644 index 0000000..c8c9cc8 --- /dev/null +++ b/src/core/ddsc/src/dds_builtin.c @@ -0,0 +1,440 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include "ddsi/q_entity.h" +#include "ddsi/q_thread.h" +#include "ddsi/q_config.h" +#include "ddsi/q_builtin_topic.h" +#include "q__osplser.h" +#include "dds__init.h" +#include "dds__qos.h" +#include "dds__domain.h" +#include "dds__participant.h" +#include "dds__err.h" +#include "dds__types.h" +#include "dds__report.h" +#include "dds__builtin.h" +#include "dds__subscriber.h" + + +static dds_return_t +dds__delete_builtin_participant( + dds_entity *e); + +static _Must_inspect_result_ dds_entity_t +dds__create_builtin_participant( + void); + +static _Must_inspect_result_ dds_entity_t +dds__create_builtin_publisher( + _In_ dds_entity_t participant); + +static _Must_inspect_result_ dds_entity_t +dds__create_builtin_writer( + _In_ dds_entity_t topic); + +static _Must_inspect_result_ dds_entity_t +dds__get_builtin_participant( + void); + + + + +static os_mutex g_builtin_mutex; +static os_atomic_uint32_t m_call_count = OS_ATOMIC_UINT32_INIT(0); + +/* Singletons are used to publish builtin data locally. */ +static dds_entity_t g_builtin_local_participant = 0; +static dds_entity_t g_builtin_local_publisher = 0; +static dds_entity_t g_builtin_local_writers[] = { + 0, /* index DDS_BUILTIN_TOPIC_DCPSPARTICIPANT - DDS_KIND_INTERNAL - 1 */ + 0, /* index DDS_BUILTIN_TOPIC_CMPARTICIPANT - DDS_KIND_INTERNAL - 1 */ + 0, /* index DDS_BUILTIN_TOPIC_DCPSTYPE - DDS_KIND_INTERNAL - 1 */ + 0, /* index DDS_BUILTIN_TOPIC_DCPSTOPIC - DDS_KIND_INTERNAL - 1 */ + 0, /* index DDS_BUILTIN_TOPIC_DCPSPUBLICATION - DDS_KIND_INTERNAL - 1 */ + 0, /* index DDS_BUILTIN_TOPIC_CMPUBLISHER - DDS_KIND_INTERNAL - 1 */ + 0, /* index DDS_BUILTIN_TOPIC_DCPSSUBSCRIPTION - DDS_KIND_INTERNAL - 1 */ + 0, /* index DDS_BUILTIN_TOPIC_CMSUBSCRIBER - DDS_KIND_INTERNAL - 1 */ + 0, /* index DDS_BUILTIN_TOPIC_CMDATAWRITER - DDS_KIND_INTERNAL - 1 */ + 0, /* index DDS_BUILTIN_TOPIC_CMDATAREADER - DDS_KIND_INTERNAL - 1 */ +}; + + +static dds_return_t +dds__delete_builtin_participant( + dds_entity *e) +{ + struct thread_state1 * const thr = lookup_thread_state (); + const bool asleep = !vtime_awake_p (thr->vtime); + + assert(e); + assert(thr); + assert(dds_entity_kind(e->m_hdl) == DDS_KIND_PARTICIPANT); + + if (asleep) { + thread_state_awake(thr); + } + dds_domain_free(e->m_domain); + if (asleep) { + thread_state_asleep(thr); + } + + return DDS_RETCODE_OK; +} + +/* + * We don't use the 'normal' create participant. + * + * This way, the application is not able to access the local builtin writers. + * Also, we can indicate that it should be a 'local only' participant, which + * means that none of the entities under the hierarchy of this participant will + * be exposed to the outside world. This is what we want, because these builtin + * writers are only applicable to local user readers. + */ +static _Must_inspect_result_ dds_entity_t +dds__create_builtin_participant( + void) +{ + int q_rc; + nn_plist_t plist; + struct thread_state1 * thr; + bool asleep; + nn_guid_t guid; + dds_entity_t participant; + dds_participant *pp; + + nn_plist_init_empty (&plist); + + thr = lookup_thread_state (); + asleep = !vtime_awake_p (thr->vtime); + if (asleep) { + thread_state_awake (thr); + } + q_rc = new_participant (&guid, RTPS_PF_NO_BUILTIN_WRITERS | RTPS_PF_NO_BUILTIN_READERS | RTPS_PF_ONLY_LOCAL, &plist); + if (asleep) { + thread_state_asleep (thr); + } + + if (q_rc != 0) { + participant = DDS_ERRNO(DDS_RETCODE_ERROR, "Internal builtin error"); + goto fail; + } + + pp = dds_alloc (sizeof (*pp)); + participant = dds_entity_init (&pp->m_entity, NULL, DDS_KIND_PARTICIPANT, NULL, NULL, 0); + if (participant < 0) { + goto fail; + } + + pp->m_entity.m_guid = guid; + pp->m_entity.m_domain = dds_domain_create (config.domainId); + pp->m_entity.m_domainid = config.domainId; + pp->m_entity.m_deriver.delete = dds__delete_builtin_participant; + +fail: + return participant; +} + +static _Must_inspect_result_ dds_entity_t +dds__create_builtin_publisher( + _In_ dds_entity_t participant) +{ + dds_entity_t pub; + dds_qos_t *qos; + const char *partition = "__BUILT-IN PARTITION__"; + + qos = dds_qos_create(); + dds_qset_partition(qos, 1, &partition); + + pub = dds_create_publisher(participant, qos, NULL); + + dds_qos_delete(qos); + + return pub; +} + +static _Must_inspect_result_ dds_entity_t +dds__create_builtin_subscriber( + _In_ dds_entity *participant) +{ + dds_entity_t sub; + dds_qos_t *qos; + const char *partition = "__BUILT-IN PARTITION__"; + + qos = dds_qos_create(); + dds_qset_partition(qos, 1, &partition); + + /* Create builtin-subscriber */ + sub = dds__create_subscriber_l(participant, qos, NULL); + dds_qos_delete(qos); + + return sub; +} + +static _Must_inspect_result_ dds_entity_t +dds__create_builtin_writer( + _In_ dds_entity_t topic) +{ + dds_entity_t wr; + dds_entity_t pub = dds__get_builtin_publisher(); + if (pub > 0) { + dds_entity_t top = dds__get_builtin_topic(pub, topic); + if (top > 0) { + dds_qos_t *qos; + // TODO: set builtin qos + qos = dds_qos_create(); + dds_qset_durability(qos, DDS_DURABILITY_TRANSIENT_LOCAL); + dds_qset_reliability(qos, DDS_RELIABILITY_RELIABLE, DDS_MSECS(100)); + wr = dds_create_writer(pub, top, qos, NULL); + dds_qos_delete(qos); + (void)dds_delete(top); + } else { + wr = top; + } + } else { + wr = pub; + } + return wr; +} + + +static _Must_inspect_result_ dds_entity_t +dds__get_builtin_participant( + void) +{ + if (g_builtin_local_participant == 0) { + g_builtin_local_participant = dds__create_builtin_participant(); + } + return g_builtin_local_participant; +} + + +_Must_inspect_result_ dds_entity_t +dds__get_builtin_publisher( + void) +{ + if (g_builtin_local_publisher == 0) { + dds_entity_t par = dds__get_builtin_participant(); + if (par > 0) { + g_builtin_local_publisher = dds__create_builtin_publisher(par); + } + } + return g_builtin_local_publisher; +} + +_Must_inspect_result_ dds_entity_t +dds__get_builtin_subscriber( + _In_ dds_entity_t e) +{ + dds_entity_t sub; + dds_return_t ret; + dds_entity_t participant; + dds_participant *p; + dds_entity *part_entity; + + participant = dds_get_participant(e); + if (participant <= 0) { + /* error already in participant error; no need to repeat error */ + ret = participant; + goto error; + } + ret = dds_entity_lock(participant, DDS_KIND_PARTICIPANT, (dds_entity **)&part_entity); + if (ret != DDS_RETCODE_OK) { + goto error; + } + p = (dds_participant *)part_entity; + if(p->m_builtin_subscriber <= 0) { + p->m_builtin_subscriber = dds__create_builtin_subscriber(part_entity); + } + sub = p->m_builtin_subscriber; + dds_entity_unlock(part_entity); + + return sub; + + /* Error handling */ +error: + assert(ret < 0); + return ret; +} + + +_Must_inspect_result_ dds_entity_t +dds__get_builtin_topic( + _In_ dds_entity_t e, + _In_ dds_entity_t topic) +{ + dds_entity_t participant; + dds_entity_t ret; + + participant = dds_get_participant(e); + if (participant > 0) { + const dds_topic_descriptor_t *desc; + const char *name; + + if (topic == DDS_BUILTIN_TOPIC_DCPSPARTICIPANT) { + desc = &DDS_ParticipantBuiltinTopicData_desc; + name = "DCPSParticipant"; + } else if (topic == DDS_BUILTIN_TOPIC_CMPARTICIPANT) { + desc = &DDS_CMParticipantBuiltinTopicData_desc; + name = "CMParticipant"; + } else if (topic == DDS_BUILTIN_TOPIC_DCPSTYPE) { + desc = &DDS_TypeBuiltinTopicData_desc; + name = "DCPSType"; + } else if (topic == DDS_BUILTIN_TOPIC_DCPSTOPIC) { + desc = &DDS_TopicBuiltinTopicData_desc; + name = "DCPSTopic"; + } else if (topic == DDS_BUILTIN_TOPIC_DCPSPUBLICATION) { + desc = &DDS_PublicationBuiltinTopicData_desc; + name = "DCPSPublication"; + } else if (topic == DDS_BUILTIN_TOPIC_CMPUBLISHER) { + desc = &DDS_CMPublisherBuiltinTopicData_desc; + name = "CMPublisher"; + } else if (topic == DDS_BUILTIN_TOPIC_DCPSSUBSCRIPTION) { + desc = &DDS_SubscriptionBuiltinTopicData_desc; + name = "DCPSSubscription"; + } else if (topic == DDS_BUILTIN_TOPIC_CMSUBSCRIBER) { + desc = &DDS_CMSubscriberBuiltinTopicData_desc; + name = "CMSubscriber"; + } else if (topic == DDS_BUILTIN_TOPIC_CMDATAWRITER) { + desc = &DDS_CMDataWriterBuiltinTopicData_desc; + name = "CMDataWriter"; + } else if (topic == DDS_BUILTIN_TOPIC_CMDATAREADER) { + desc = &DDS_CMDataReaderBuiltinTopicData_desc; + name = "CMDataReader"; + } else { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Invalid builtin-topic handle(%d)", topic); + goto err_invalid_topic; + } + + ret = dds_find_topic(participant, name); + if (ret < 0 && dds_err_nr(ret) == DDS_RETCODE_PRECONDITION_NOT_MET) { + dds_qos_t *tqos; + + /* drop the precondition-no-met error */ + DDS_REPORT_FLUSH(0); + DDS_REPORT_STACK(); + + tqos = dds_qos_create(); + dds_qset_durability(tqos, DDS_DURABILITY_TRANSIENT_LOCAL); + dds_qset_presentation(tqos, DDS_PRESENTATION_TOPIC, false, false); + dds_qset_reliability(tqos, DDS_RELIABILITY_RELIABLE, DDS_MSECS(100)); + ret = dds_create_topic(participant, desc, name, tqos, NULL); + dds_qos_delete(tqos); + } + + } else { + /* Failed to get participant of provided entity */ + ret = participant; + } + +err_invalid_topic: + return ret; +} + + +static _Must_inspect_result_ dds_entity_t +dds__get_builtin_writer( + _In_ dds_entity_t topic) +{ + dds_entity_t wr; + if ((topic >= DDS_BUILTIN_TOPIC_DCPSPARTICIPANT) && (topic <= DDS_BUILTIN_TOPIC_CMDATAREADER)) { + int index = (int)(topic - DDS_KIND_INTERNAL - 1); + os_mutexLock(&g_builtin_mutex); + wr = g_builtin_local_writers[index]; + if (wr == 0) { + wr = dds__create_builtin_writer(topic); + if (wr > 0) { + g_builtin_local_writers[index] = wr; + } + } + os_mutexUnlock(&g_builtin_mutex); + } else { + wr = DDS_ERRNO(DDS_RETCODE_ERROR, "Given topic is not a builtin topic."); + } + return wr; +} + +static dds_return_t +dds__builtin_write( + _In_ dds_entity_t topic, + _In_ const void *data, + _In_ dds_time_t timestamp, + _In_ int alive) +{ + dds_return_t ret = DDS_RETCODE_OK; + if (os_atomic_inc32_nv(&m_call_count) > 1) { + dds_entity_t wr; + wr = dds__get_builtin_writer(topic); + if (wr > 0) { + if (alive) { + ret = dds_write_ts(wr, data, timestamp); + } else { + ret = dds_dispose_ts(wr, data, timestamp); + } + } else { + ret = wr; + } + } + os_atomic_dec32(&m_call_count); + return ret; +} + +void +dds__builtin_init( + void) +{ + assert(os_atomic_ld32(&m_call_count) == 0); + os_mutexInit(&g_builtin_mutex); + os_atomic_inc32(&m_call_count); +} + +void +dds__builtin_fini( + void) +{ + assert(os_atomic_ld32(&m_call_count) > 0); + while (os_atomic_dec32_nv(&m_call_count) > 0) { + os_atomic_inc32_nv(&m_call_count); + dds_sleepfor(DDS_MSECS(10)); + } + (void)dds_delete(g_builtin_local_participant); + g_builtin_local_participant = 0; + g_builtin_local_publisher = 0; + memset(g_builtin_local_writers, 0, sizeof(g_builtin_local_writers)); + os_mutexDestroy(&g_builtin_mutex); +} + + +void +forward_builtin_participant( + _In_ DDS_ParticipantBuiltinTopicData *data, + _In_ nn_wctime_t timestamp, + _In_ int alive) +{ + dds_return_t ret; + DDS_REPORT_STACK(); + ret = dds__builtin_write(DDS_BUILTIN_TOPIC_DCPSPARTICIPANT, data, timestamp.v, alive); + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); +} + +void +forward_builtin_cmparticipant( + _In_ DDS_CMParticipantBuiltinTopicData *data, + _In_ nn_wctime_t timestamp, + _In_ int alive) +{ + dds_return_t ret; + DDS_REPORT_STACK(); + ret = dds__builtin_write(DDS_BUILTIN_TOPIC_CMPARTICIPANT, data, timestamp.v, alive); + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); +} diff --git a/src/core/ddsc/src/dds_builtinTopics.idl b/src/core/ddsc/src/dds_builtinTopics.idl new file mode 100644 index 0000000..d3400d9 --- /dev/null +++ b/src/core/ddsc/src/dds_builtinTopics.idl @@ -0,0 +1,371 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef OSPL_DDS_BUILTINTOPICS_IDL +#define OSPL_DDS_BUILTINTOPICS_IDL + +/* +This file was the one orginally in ./src/api/dcps/saj/code/. +(and therefore by implication ./src/api/dcps/cj/java/code). +*/ + +#define BUILTIN_TOPIC_KEY_TYPE_NATIVE long + +module DDS { + + + // Added octet sequence definition. + // Prevents IDL compiler warnings from deprecated anonymous types + // on composite type members. + typedef sequence octSeq; + + typedef BUILTIN_TOPIC_KEY_TYPE_NATIVE BuiltinTopicKey_t[3]; + typedef sequence StringSeq; + typedef short DataRepresentationId_t; + + const DataRepresentationId_t XCDR_REPRESENTATION = 0; + const DataRepresentationId_t XML_REPRESENTATION = 0x001; + const DataRepresentationId_t OSPL_REPRESENTATION = 0x400; + const DataRepresentationId_t GPB_REPRESENTATION = 0x401; + const DataRepresentationId_t INVALID_REPRESENTATION = 0x7FFF; + + struct Duration_t { + long sec; + unsigned long nanosec; + }; + + struct UserDataQosPolicy { + octSeq value; + // replaced deprecated anonymous sequence value; + }; + + struct TopicDataQosPolicy { + octSeq value; + // replaced deprecated anonymous sequence value; + }; + + struct GroupDataQosPolicy { + octSeq value; + // replaced deprected anonymous sequence value; + }; + + struct TransportPriorityQosPolicy { + long value; + }; + + struct LifespanQosPolicy { + Duration_t duration; + }; + + enum DurabilityQosPolicyKind { + VOLATILE_DURABILITY_QOS, + TRANSIENT_LOCAL_DURABILITY_QOS, + TRANSIENT_DURABILITY_QOS, + PERSISTENT_DURABILITY_QOS + }; + + struct DurabilityQosPolicy { + DurabilityQosPolicyKind kind; + }; + + enum PresentationQosPolicyAccessScopeKind { + INSTANCE_PRESENTATION_QOS, + TOPIC_PRESENTATION_QOS, + GROUP_PRESENTATION_QOS + }; + + struct PresentationQosPolicy { + PresentationQosPolicyAccessScopeKind access_scope; + boolean coherent_access; + boolean ordered_access; + }; + + struct DeadlineQosPolicy { + Duration_t period; + }; + + struct LatencyBudgetQosPolicy { + Duration_t duration; + }; + + enum OwnershipQosPolicyKind { + SHARED_OWNERSHIP_QOS, + EXCLUSIVE_OWNERSHIP_QOS + }; + + struct OwnershipQosPolicy { + OwnershipQosPolicyKind kind; + }; + + struct OwnershipStrengthQosPolicy { + long value; + }; + + enum LivelinessQosPolicyKind { + AUTOMATIC_LIVELINESS_QOS, + MANUAL_BY_PARTICIPANT_LIVELINESS_QOS, + MANUAL_BY_TOPIC_LIVELINESS_QOS + }; + + struct LivelinessQosPolicy { + LivelinessQosPolicyKind kind; + Duration_t lease_duration; + }; + + struct TimeBasedFilterQosPolicy { + Duration_t minimum_separation; + }; + + struct PartitionQosPolicy { + StringSeq name; + }; + + enum ReliabilityQosPolicyKind { + BEST_EFFORT_RELIABILITY_QOS, + RELIABLE_RELIABILITY_QOS + }; + + struct ReliabilityQosPolicy { + ReliabilityQosPolicyKind kind; + Duration_t max_blocking_time; + boolean synchronous; + }; + + enum DestinationOrderQosPolicyKind { + BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS, + BY_SOURCE_TIMESTAMP_DESTINATIONORDER_QOS + }; + + struct DestinationOrderQosPolicy { + DestinationOrderQosPolicyKind kind; + }; + + enum HistoryQosPolicyKind { + KEEP_LAST_HISTORY_QOS, + KEEP_ALL_HISTORY_QOS + }; + + struct HistoryQosPolicy { + HistoryQosPolicyKind kind; + long depth; + }; + + struct ResourceLimitsQosPolicy { + long max_samples; + long max_instances; + long max_samples_per_instance; + }; + + struct DurabilityServiceQosPolicy { + Duration_t service_cleanup_delay; + HistoryQosPolicyKind history_kind; + long history_depth; + long max_samples; + long max_instances; + long max_samples_per_instance; + }; + + struct ProductDataQosPolicy { + string value; + }; + + struct EntityFactoryQosPolicy { + boolean autoenable_created_entities; + }; + + struct ShareQosPolicy { + string name; + boolean enable; + }; + + struct WriterDataLifecycleQosPolicy { + boolean autodispose_unregistered_instances; + Duration_t autopurge_suspended_samples_delay; + Duration_t autounregister_instance_delay; + }; + + enum InvalidSampleVisibilityQosPolicyKind { + NO_INVALID_SAMPLES, + MINIMUM_INVALID_SAMPLES, + ALL_INVALID_SAMPLES + }; + + struct InvalidSampleVisibilityQosPolicy { + InvalidSampleVisibilityQosPolicyKind kind; + }; + +// @discrepancy The below QoS did not exist in ./etc/idlpp/dds_dcps.idl Retain ? + + struct SubscriptionKeyQosPolicy { + boolean use_key_list; + StringSeq key_list; + }; + +// @discrepancy End of above discrepancy + + struct ReaderDataLifecycleQosPolicy { + Duration_t autopurge_nowriter_samples_delay; + Duration_t autopurge_disposed_samples_delay; + boolean autopurge_dispose_all; + // @discrepancy The below member existed in this file but did not + // in ./etc/idlpp/dds_dcps.idl. Retain ? + boolean enable_invalid_samples; // @deprecated Will be replaced by invalid_sample_visibility in due time + InvalidSampleVisibilityQosPolicy invalid_sample_visibility; + }; + + struct UserKeyQosPolicy { + boolean enable; + string expression; + }; + + struct ReaderLifespanQosPolicy { + boolean use_lifespan; + Duration_t duration; + }; + + struct TypeHash { + unsigned long long msb; + unsigned long long lsb; + }; + + struct ParticipantBuiltinTopicData { + BuiltinTopicKey_t key; + UserDataQosPolicy user_data; + }; +#pragma keylist ParticipantBuiltinTopicData key + + struct TopicBuiltinTopicData { + BuiltinTopicKey_t key; + string name; + string type_name; + DurabilityQosPolicy durability; + DurabilityServiceQosPolicy durability_service; + DeadlineQosPolicy deadline; + LatencyBudgetQosPolicy latency_budget; + LivelinessQosPolicy liveliness; + ReliabilityQosPolicy reliability; + TransportPriorityQosPolicy transport_priority; + LifespanQosPolicy lifespan; + DestinationOrderQosPolicy destination_order; + HistoryQosPolicy history; + ResourceLimitsQosPolicy resource_limits; + OwnershipQosPolicy ownership; + TopicDataQosPolicy topic_data; + }; +#pragma keylist TopicBuiltinTopicData key + + struct TypeBuiltinTopicData { + string name; + DataRepresentationId_t data_representation_id; + TypeHash type_hash; + octSeq meta_data; + octSeq extentions; + }; +#pragma keylist TypeBuiltinTopicData name data_representation_id type_hash.msb type_hash.lsb + + struct PublicationBuiltinTopicData { + BuiltinTopicKey_t key; + BuiltinTopicKey_t participant_key; + string topic_name; + string type_name; + DurabilityQosPolicy durability; + DeadlineQosPolicy deadline; + LatencyBudgetQosPolicy latency_budget; + LivelinessQosPolicy liveliness; + ReliabilityQosPolicy reliability; + LifespanQosPolicy lifespan; + DestinationOrderQosPolicy destination_order; + UserDataQosPolicy user_data; + OwnershipQosPolicy ownership; + OwnershipStrengthQosPolicy ownership_strength; + PresentationQosPolicy presentation; + PartitionQosPolicy partition; + TopicDataQosPolicy topic_data; + GroupDataQosPolicy group_data; + }; +#pragma keylist PublicationBuiltinTopicData key + + struct SubscriptionBuiltinTopicData { + BuiltinTopicKey_t key; + BuiltinTopicKey_t participant_key; + string topic_name; + string type_name; + DurabilityQosPolicy durability; + DeadlineQosPolicy deadline; + LatencyBudgetQosPolicy latency_budget; + LivelinessQosPolicy liveliness; + ReliabilityQosPolicy reliability; + OwnershipQosPolicy ownership; + DestinationOrderQosPolicy destination_order; + UserDataQosPolicy user_data; + TimeBasedFilterQosPolicy time_based_filter; + PresentationQosPolicy presentation; + PartitionQosPolicy partition; + TopicDataQosPolicy topic_data; + GroupDataQosPolicy group_data; + }; +#pragma keylist SubscriptionBuiltinTopicData key + + struct CMParticipantBuiltinTopicData { + BuiltinTopicKey_t key; + ProductDataQosPolicy product; + }; +#pragma keylist CMParticipantBuiltinTopicData key + + struct CMPublisherBuiltinTopicData { + BuiltinTopicKey_t key; + ProductDataQosPolicy product; + BuiltinTopicKey_t participant_key; + string name; + EntityFactoryQosPolicy entity_factory; + PartitionQosPolicy partition; + }; +#pragma keylist CMPublisherBuiltinTopicData key + + struct CMSubscriberBuiltinTopicData { + BuiltinTopicKey_t key; + ProductDataQosPolicy product; + BuiltinTopicKey_t participant_key; + string name; + EntityFactoryQosPolicy entity_factory; + ShareQosPolicy share; + PartitionQosPolicy partition; + }; +#pragma keylist CMSubscriberBuiltinTopicData key + + struct CMDataWriterBuiltinTopicData { + BuiltinTopicKey_t key; + ProductDataQosPolicy product; + BuiltinTopicKey_t publisher_key; + string name; + HistoryQosPolicy history; + ResourceLimitsQosPolicy resource_limits; + WriterDataLifecycleQosPolicy writer_data_lifecycle; + }; +#pragma keylist CMDataWriterBuiltinTopicData key + + struct CMDataReaderBuiltinTopicData { + BuiltinTopicKey_t key; + ProductDataQosPolicy product; + BuiltinTopicKey_t subscriber_key; + string name; + HistoryQosPolicy history; + ResourceLimitsQosPolicy resource_limits; + ReaderDataLifecycleQosPolicy reader_data_lifecycle; + UserKeyQosPolicy subscription_keys; + ReaderLifespanQosPolicy reader_lifespan; + ShareQosPolicy share; + }; +#pragma keylist CMDataReaderBuiltinTopicData key + +}; + +#endif /* OSPL_DDS_BUILTINTOPICS_IDL */ diff --git a/src/core/ddsc/src/dds_coherent.c b/src/core/ddsc/src/dds_coherent.c new file mode 100644 index 0000000..9fbd81f --- /dev/null +++ b/src/core/ddsc/src/dds_coherent.c @@ -0,0 +1,79 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include + +#include "ddsc/dds.h" +#include "dds__entity.h" +#include "dds__subscriber.h" +#include "dds__publisher.h" +#include "dds__err.h" +#include "dds__report.h" + +_Pre_satisfies_(((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER) ) +dds_return_t +dds_begin_coherent( + _In_ dds_entity_t entity) +{ + dds_return_t ret; + + switch(dds_entity_kind(entity)) { + case DDS_KIND_READER: + case DDS_KIND_WRITER: + /* Invoking on a writer/reader behaves as if invoked on + * its parent publisher/subscriber. */ + ret = dds_begin_coherent(dds_get_parent(entity)); + break; + case DDS_KIND_PUBLISHER: + ret = dds_publisher_begin_coherent(entity); + break; + case DDS_KIND_SUBSCRIBER: + ret = dds_subscriber_begin_coherent(entity); + break; + default: + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Given entity can not control coherency"); + break; + } + return ret; +} + +_Pre_satisfies_(((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER) ) +dds_return_t +dds_end_coherent( + _In_ dds_entity_t entity) +{ + dds_return_t ret; + + switch(dds_entity_kind(entity)) { + case DDS_KIND_READER: + case DDS_KIND_WRITER: + /* Invoking on a writer/reader behaves as if invoked on + * its parent publisher/subscriber. */ + ret = dds_end_coherent(dds_get_parent(entity)); + break; + case DDS_KIND_PUBLISHER: + ret = dds_publisher_end_coherent(entity); + break; + case DDS_KIND_SUBSCRIBER: + ret = dds_subscriber_end_coherent(entity); + break; + default: + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Given entity can not control coherency"); + break; + } + return ret; +} diff --git a/src/core/ddsc/src/dds_dcps_builtintopics.idl b/src/core/ddsc/src/dds_dcps_builtintopics.idl new file mode 100644 index 0000000..cfa394a --- /dev/null +++ b/src/core/ddsc/src/dds_dcps_builtintopics.idl @@ -0,0 +1,130 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef OSPL_DDS_DCPS_BUILTINTOPICS_IDL +#define OSPL_DDS_DCPS_BUILTINTOPICS_IDL + +/** +*This file (name) was orginally in ./src/api/dcps/ccpp/idl/. +*It's been modified to include from another file the base definitions +*required for this 'built in topics' superset... : +*/ + +#include "dds_builtinTopics.idl" + +module DDS { + + struct Time_t { + long sec; + unsigned long nanosec; + }; + + enum SchedulingClassQosPolicyKind { + SCHEDULE_DEFAULT, + SCHEDULE_TIMESHARING, + SCHEDULE_REALTIME + }; + + struct SchedulingClassQosPolicy { + SchedulingClassQosPolicyKind kind; + }; + + enum SchedulingPriorityQosPolicyKind { + PRIORITY_RELATIVE, + PRIORITY_ABSOLUTE + }; + + struct SchedulingPriorityQosPolicy { + SchedulingPriorityQosPolicyKind kind; + }; + + struct SchedulingQosPolicy { + SchedulingClassQosPolicy scheduling_class; + SchedulingPriorityQosPolicy scheduling_priority_kind; + long scheduling_priority; + }; + + struct DomainParticipantQos { + UserDataQosPolicy user_data; + EntityFactoryQosPolicy entity_factory; + SchedulingQosPolicy watchdog_scheduling; + SchedulingQosPolicy listener_scheduling; + }; + + struct TopicQos { + TopicDataQosPolicy topic_data; + DurabilityQosPolicy durability; + DurabilityServiceQosPolicy durability_service; + DeadlineQosPolicy deadline; + LatencyBudgetQosPolicy latency_budget; + LivelinessQosPolicy liveliness; + ReliabilityQosPolicy reliability; + DestinationOrderQosPolicy destination_order; + HistoryQosPolicy history; + ResourceLimitsQosPolicy resource_limits; + TransportPriorityQosPolicy transport_priority; + LifespanQosPolicy lifespan; + OwnershipQosPolicy ownership; + }; + + struct DataWriterQos { + DurabilityQosPolicy durability; + DeadlineQosPolicy deadline; + LatencyBudgetQosPolicy latency_budget; + LivelinessQosPolicy liveliness; + ReliabilityQosPolicy reliability; + DestinationOrderQosPolicy destination_order; + HistoryQosPolicy history; + ResourceLimitsQosPolicy resource_limits; + TransportPriorityQosPolicy transport_priority; + LifespanQosPolicy lifespan; + UserDataQosPolicy user_data; + OwnershipQosPolicy ownership; + OwnershipStrengthQosPolicy ownership_strength; + WriterDataLifecycleQosPolicy writer_data_lifecycle; + }; + + struct PublisherQos { + PresentationQosPolicy presentation; + PartitionQosPolicy partition; + GroupDataQosPolicy group_data; + EntityFactoryQosPolicy entity_factory; + }; + + struct DataReaderQos { + DurabilityQosPolicy durability; + DeadlineQosPolicy deadline; + LatencyBudgetQosPolicy latency_budget; + LivelinessQosPolicy liveliness; + ReliabilityQosPolicy reliability; + DestinationOrderQosPolicy destination_order; + HistoryQosPolicy history; + ResourceLimitsQosPolicy resource_limits; + UserDataQosPolicy user_data; + OwnershipQosPolicy ownership; + TimeBasedFilterQosPolicy time_based_filter; + ReaderDataLifecycleQosPolicy reader_data_lifecycle; + SubscriptionKeyQosPolicy subscription_keys; + ReaderLifespanQosPolicy reader_lifespan; + ShareQosPolicy share; + }; + + struct SubscriberQos { + PresentationQosPolicy presentation; + PartitionQosPolicy partition; + GroupDataQosPolicy group_data; + EntityFactoryQosPolicy entity_factory; + ShareQosPolicy share; + }; + +}; + +#endif /* DDS_DCPS_BUILTINTOPICS_IDL */ diff --git a/src/core/ddsc/src/dds_domain.c b/src/core/ddsc/src/dds_domain.c new file mode 100644 index 0000000..4b3e23e --- /dev/null +++ b/src/core/ddsc/src/dds_domain.c @@ -0,0 +1,59 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "dds__domain.h" +#include "dds__tkmap.h" + +static int dds_domain_compare (const int32_t * a, const int32_t * b) +{ + return (*a == *b) ? 0 : (*a < *b) ? -1 : 1; +} + +const ut_avlTreedef_t dds_domaintree_def = UT_AVL_TREEDEF_INITIALIZER +( + offsetof (dds_domain, m_node), + offsetof (dds_domain, m_id), + (int (*) (const void *, const void *)) dds_domain_compare, + 0 +); + +dds_domain * dds_domain_find_locked (dds_domainid_t id) +{ + return (dds_domain*) ut_avlLookup (&dds_domaintree_def, &dds_global.m_domains, &id); +} + +dds_domain * dds_domain_create (dds_domainid_t id) +{ + dds_domain * domain; + os_mutexLock (&dds_global.m_mutex); + domain = dds_domain_find_locked (id); + if (domain == NULL) + { + domain = dds_alloc (sizeof (*domain)); + domain->m_id = id; + ut_avlInit (&dds_topictree_def, &domain->m_topics); + ut_avlInsert (&dds_domaintree_def, &dds_global.m_domains, domain); + } + domain->m_refc++; + os_mutexUnlock (&dds_global.m_mutex); + return domain; +} + +void dds_domain_free (dds_domain * domain) +{ + os_mutexLock (&dds_global.m_mutex); + if (--domain->m_refc == 0) + { + ut_avlDelete (&dds_domaintree_def, &dds_global.m_domains, domain); + dds_free (domain); + } + os_mutexUnlock (&dds_global.m_mutex); +} diff --git a/src/core/ddsc/src/dds_entity.c b/src/core/ddsc/src/dds_entity.c new file mode 100644 index 0000000..cb9e78b --- /dev/null +++ b/src/core/ddsc/src/dds_entity.c @@ -0,0 +1,1291 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include "dds__entity.h" +#include "dds__write.h" +#include "dds__writer.h" +#include "dds__reader.h" +#include "dds__listener.h" +#include "dds__err.h" +#include "os/os_report.h" +#include "dds__report.h" +#include "ddsc/ddsc_project.h" + +/* Sanity check. */ +#if DDS_ENTITY_KIND_MASK != UT_HANDLE_KIND_MASK +#error "DDS_ENTITY_KIND_MASK != UT_HANDLE_KIND_MASK" +#endif + + + +static void +dds_entity_observers_delete( + _In_ dds_entity *observed); + +static void +dds_entity_observers_signal( + _In_ dds_entity *observed, + _In_ uint32_t status); + +void +dds_entity_add_ref(_In_ dds_entity * e) +{ + assert (e); + os_mutexLock (&e->m_mutex); + e->m_refc++; + os_mutexUnlock (&e->m_mutex); +} + +static void +dds_set_explicit( + _In_ dds_entity_t entity); + +/*This function returns the parent entity of e. If e is a participant it returns NULL*/ +_Ret_maybenull_ +static dds_entity * +dds__nonself_parent( + _In_ dds_entity *e){ + return e->m_parent == e ? NULL : e->m_parent; +} + +void +dds_entity_add_ref_nolock(_In_ dds_entity *e) +{ + assert(e); + e->m_refc++; +} + +_Check_return_ dds__retcode_t +dds_entity_listener_propagation( + _Inout_opt_ dds_entity *e, + _In_ dds_entity *src, + _In_ uint32_t status, + _In_opt_ void *metrics, + _In_ bool propagate) +{ + dds__retcode_t rc = DDS_RETCODE_NO_DATA; /* Mis-use NO_DATA as NO_CALL. */ + dds_entity *dummy; + /* e will be NULL when reaching the top of the entity hierarchy. */ + if (e) { + rc = dds_entity_lock(e->m_hdl, DDS_KIND_DONTCARE, &dummy); + if (rc == DDS_RETCODE_OK) { + dds_listener_t *l = (dds_listener_t *)(&e->m_listener); + + assert(e == dummy); + + /* Indicate that a callback will be in progress, so that a parallel + * delete/set_listener will wait. */ + e->m_cb_count++; + + /* Calling the actual listener should be done unlocked. */ + dds_entity_unlock(e); + + /* Now, perform the callback when available. */ + rc = DDS_RETCODE_NO_DATA; + switch (status) { + case DDS_INCONSISTENT_TOPIC_STATUS: + if (l->on_inconsistent_topic != DDS_LUNSET) { + assert(metrics); + l->on_inconsistent_topic(src->m_hdl, *((dds_inconsistent_topic_status_t*)metrics), l->arg); + rc = DDS_RETCODE_OK; + } + break; + case DDS_OFFERED_DEADLINE_MISSED_STATUS: + if (l->on_offered_deadline_missed != DDS_LUNSET) { + assert(metrics); + l->on_offered_deadline_missed(src->m_hdl, *((dds_offered_deadline_missed_status_t*)metrics), l->arg); + rc = DDS_RETCODE_OK; + } + break; + case DDS_REQUESTED_DEADLINE_MISSED_STATUS: + if (l->on_requested_deadline_missed != DDS_LUNSET) { + assert(metrics); + l->on_requested_deadline_missed(src->m_hdl, *((dds_requested_deadline_missed_status_t*)metrics), l->arg); + rc = DDS_RETCODE_OK; + } + break; + case DDS_OFFERED_INCOMPATIBLE_QOS_STATUS: + if (l->on_offered_incompatible_qos != DDS_LUNSET) { + assert(metrics); + l->on_offered_incompatible_qos(src->m_hdl, *((dds_offered_incompatible_qos_status_t*)metrics), l->arg); + rc = DDS_RETCODE_OK; + } + break; + case DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS: + if (l->on_requested_incompatible_qos != DDS_LUNSET) { + assert(metrics); + l->on_requested_incompatible_qos(src->m_hdl, *((dds_requested_incompatible_qos_status_t*)metrics), l->arg); + rc = DDS_RETCODE_OK; + } + break; + case DDS_SAMPLE_LOST_STATUS: + if (l->on_sample_lost != DDS_LUNSET) { + assert(metrics); + l->on_sample_lost(src->m_hdl, *((dds_sample_lost_status_t*)metrics), l->arg); + rc = DDS_RETCODE_OK; + } + break; + case DDS_SAMPLE_REJECTED_STATUS: + if (l->on_sample_rejected != DDS_LUNSET) { + assert(metrics); + l->on_sample_rejected(src->m_hdl, *((dds_sample_rejected_status_t*)metrics), l->arg); + rc = DDS_RETCODE_OK; + } + break; + case DDS_DATA_ON_READERS_STATUS: + if (l->on_data_on_readers != DDS_LUNSET) { + l->on_data_on_readers(src->m_hdl, l->arg); + rc = DDS_RETCODE_OK; + } + break; + case DDS_DATA_AVAILABLE_STATUS: + if (l->on_data_available != DDS_LUNSET) { + l->on_data_available(src->m_hdl, l->arg); + rc = DDS_RETCODE_OK; + } + break; + case DDS_LIVELINESS_LOST_STATUS: + if (l->on_liveliness_lost != DDS_LUNSET) { + assert(metrics); + l->on_liveliness_lost(src->m_hdl, *((dds_liveliness_lost_status_t*)metrics), l->arg); + rc = DDS_RETCODE_OK; + } + break; + case DDS_LIVELINESS_CHANGED_STATUS: + if (l->on_liveliness_changed != DDS_LUNSET) { + assert(metrics); + l->on_liveliness_changed(src->m_hdl, *((dds_liveliness_changed_status_t*)metrics), l->arg); + rc = DDS_RETCODE_OK; + } + break; + case DDS_PUBLICATION_MATCHED_STATUS: + if (l->on_publication_matched != DDS_LUNSET) { + assert(metrics); + l->on_publication_matched(src->m_hdl, *((dds_publication_matched_status_t*)metrics), l->arg); + rc = DDS_RETCODE_OK; + } + break; + case DDS_SUBSCRIPTION_MATCHED_STATUS: + if (l->on_subscription_matched != DDS_LUNSET) { + assert(metrics); + l->on_subscription_matched(src->m_hdl, *((dds_subscription_matched_status_t*)metrics), l->arg); + rc = DDS_RETCODE_OK; + } + break; + default: assert (0); + } + if ((rc == DDS_RETCODE_NO_DATA) && propagate) { + /* See if the parent is interested. */ + rc = dds_entity_listener_propagation(dds__nonself_parent(e), src, status, metrics, propagate); + } + + os_mutexLock(&(e->m_mutex)); + /* We are done with our callback. */ + e->m_cb_count--; + /* Wake up possible waiting threads. */ + os_condBroadcast(&e->m_cond); + os_mutexUnlock(&(e->m_mutex)); + } + } + return rc; +} + + +static void +dds_entity_cb_wait (_In_ dds_entity *e) +{ + while (e->m_cb_count > 0) { + os_condWait (&e->m_cond, &e->m_mutex); + } +} + + + +_Check_return_ dds_entity_t +dds_entity_init( + _In_ dds_entity * e, + _When_(kind != DDS_KIND_PARTICIPANT, _Notnull_) + _When_(kind == DDS_KIND_PARTICIPANT, _Null_) + _In_opt_ dds_entity * parent, + _In_ dds_entity_kind_t kind, + _In_opt_ dds_qos_t * qos, + _In_opt_ const dds_listener_t *listener, + _In_ uint32_t mask) +{ + assert (e); + + e->m_refc = 1; + e->m_qos = qos; + e->m_cb_count = 0; + e->m_observers = NULL; + e->m_trigger = 0; + + /* TODO: CHAM-96: Implement dynamic enabling of entity. */ + e->m_flags |= DDS_ENTITY_ENABLED; + + /* set the status enable based on kind */ + e->m_status_enable = mask | DDS_INTERNAL_STATUS_MASK; + + os_mutexInit (&e->m_mutex); + os_condInit (&e->m_cond, &e->m_mutex); + + if (parent) { + e->m_parent = parent; + e->m_domain = parent->m_domain; + e->m_domainid = parent->m_domainid; + e->m_participant = parent->m_participant; + e->m_next = parent->m_children; + parent->m_children = e; + } else { + assert (kind == DDS_KIND_PARTICIPANT); + e->m_participant = e; + e->m_parent = e; + } + + if (listener) { + dds_listener_copy(&e->m_listener, listener); + } else { + dds_listener_reset(&e->m_listener); + } + + e->m_hdllink = NULL; + e->m_hdl = ut_handle_create((int32_t)kind, e); + if (e->m_hdl > 0) { + e->m_hdllink = ut_handle_get_link(e->m_hdl); + assert(e->m_hdllink); + } else{ + if (e->m_hdl == UT_HANDLE_OUT_OF_RESOURCES) { + e->m_hdl = DDS_ERRNO(DDS_RETCODE_OUT_OF_RESOURCES, "Can not create new entity; too many where created previously."); + } else if (e->m_hdl == UT_HANDLE_NOT_INITALIZED) { + e->m_hdl = DDS_ERRNO(DDS_RETCODE_PRECONDITION_NOT_MET, DDSC_PROJECT_NAME" is not yet initialized. Please create a participant before executing an other method."); + } else { + e->m_hdl = DDS_ERRNO(DDS_RETCODE_ERROR, "An internal error has occurred."); + } + } + + /* An ut_handle_t is directly used as dds_entity_t. */ + return (dds_entity_t)(e->m_hdl); +} + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +dds_return_t +dds_delete( + _In_ dds_entity_t entity) +{ + dds_return_t ret; + DDS_REPORT_STACK(); + ret = dds_delete_impl(entity, false); + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +dds_return_t +dds_delete_impl( + _In_ dds_entity_t entity, + _In_ bool keep_if_explicit) +{ + os_time timeout = { 10, 0 }; + dds_entity *e; + dds_entity *child; + dds_entity *parent; + dds_entity *prev = NULL; + dds_entity *next = NULL; + dds_return_t ret = DDS_RETCODE_OK; + dds__retcode_t rc; + + rc = dds_entity_lock(entity, UT_HANDLE_DONTCARE_KIND, &e); + if (rc != DDS_RETCODE_OK) { + return DDS_ERRNO(rc, "Error on locking entity"); + } + + if(keep_if_explicit == true && ((e->m_flags & DDS_ENTITY_IMPLICIT) == 0)){ + dds_entity_unlock(e); + return DDS_RETCODE_OK; + } + + if (--e->m_refc != 0) { + dds_entity_unlock(e); + return DDS_RETCODE_OK; + } + + dds_entity_cb_wait(e); + + ut_handle_close(e->m_hdl, e->m_hdllink); + e->m_status_enable = 0; + dds_listener_reset(&e->m_listener); + e->m_trigger |= DDS_DELETING_STATUS; + + dds_entity_unlock(e); + + /* Signal observers that this entity will be deleted. */ + dds_entity_status_signal(e); + + /* + * Recursively delete children. + * + * It is possible that a writer/reader has the last reference + * to a topic. This will mean that when deleting a writer could + * cause a topic to be deleted. + * This can cause issues when deleting the children of a participant: + * when a topic is the next child in line to be deleted, while at the + * same time it is already being deleted due to the recursive deletion + * of a publisher->writer. + * + * Another problem is that when the topic was already deleted, and + * we'd delete it here for the second time before the writer/reader + * is deleted, they will have dangling pointers. + * + * To circumvent the problem. We ignore topics in the first loop. + */ + child = e->m_children; + while ((child != NULL) && (dds_entity_kind(child->m_hdl) == DDS_KIND_TOPIC)) { + child = child->m_next; + } + while ((child != NULL) && (ret == DDS_RETCODE_OK)) { + next = child->m_next; + while ((next != NULL) && (dds_entity_kind(next->m_hdl) == DDS_KIND_TOPIC)) { + next = next->m_next; + } + /* This will probably delete the child entry from + * the current childrens list */ + ret = dds_delete(child->m_hdl); + /* Next child. */ + child = next; + } + child = e->m_children; + while ((child != NULL) && (ret == DDS_RETCODE_OK)) { + next = child->m_next; + assert(dds_entity_kind(child->m_hdl) == DDS_KIND_TOPIC); + /* This will probably delete the child entry from + * the current childrens list */ + ret = dds_delete(child->m_hdl); + /* Next child. */ + child = next; + } + + + if (ret == DDS_RETCODE_OK) { + /* Close the entity. This can terminate threads or kick of + * other destroy stuff that takes a while. */ + if (e->m_deriver.close) { + ret = e->m_deriver.close(e); + } + } + + if (ret == DDS_RETCODE_OK) { + /* The ut_handle_delete will wait until the last active claim on that handle + * is released. It is possible that this last release will be done by a thread + * that was kicked during the close(). */ + if (ut_handle_delete(e->m_hdl, e->m_hdllink, timeout) != UT_HANDLE_OK) { + return DDS_ERRNO(DDS_RETCODE_TIMEOUT, "Entity deletion did not release resources."); + } + } + + if (ret == DDS_RETCODE_OK) { + /* Remove all possible observers. */ + dds_entity_observers_delete(e); + + /* Remove from parent */ + if ((parent = dds__nonself_parent(e)) != NULL) { + os_mutexLock (&parent->m_mutex); + child = parent->m_children; + while (child) { + if (child == e) { + if (prev) { + prev->m_next = e->m_next; + } else { + parent->m_children = e->m_next; + } + break; + } + prev = child; + child = child->m_next; + } + os_mutexUnlock (&parent->m_mutex); + } + + /* Do some specific deletion when needed. */ + if (e->m_deriver.delete) { + ret = e->m_deriver.delete(e); + } + } + + if (ret == DDS_RETCODE_OK) { + /* Destroy last few things. */ + dds_qos_delete (e->m_qos); + os_condDestroy (&e->m_cond); + os_mutexDestroy (&e->m_mutex); + dds_free (e); + } + + return ret; +} + + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +_Check_return_ dds_entity_t +dds_get_parent( + _In_ dds_entity_t entity) +{ + dds_entity *e; + dds__retcode_t rc; + dds_entity_t hdl; + dds_entity *parent; + + DDS_REPORT_STACK(); + + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if (rc == DDS_RETCODE_OK) { + if ((parent = dds__nonself_parent(e)) != NULL) { + hdl = parent->m_hdl; + dds_set_explicit(hdl); + } else { + hdl = DDS_ENTITY_NIL; + } + dds_entity_unlock(e); + } else { + hdl = DDS_ERRNO(rc, "Error on locking handle entity"); + } + DDS_REPORT_FLUSH(hdl <= 0); + return hdl; +} + + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +_Check_return_ dds_entity_t +dds_get_participant ( + _In_ dds_entity_t entity) +{ + dds_entity *e; + dds__retcode_t rc; + dds_entity_t hdl; + + DDS_REPORT_STACK(); + + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if (rc == DDS_RETCODE_OK) { + assert(e->m_participant); + hdl = e->m_participant->m_hdl; + dds_entity_unlock(e); + } else { + hdl = DDS_ERRNO(rc, "Error on locking handle entity"); + } + DDS_REPORT_FLUSH( hdl <= 0); + return hdl; +} + + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +_Check_return_ dds_return_t +dds_get_children( + _In_ dds_entity_t entity, + _Out_opt_ dds_entity_t *children, + _In_ size_t size) +{ + dds_entity *e; + dds__retcode_t rc; + dds_return_t ret; + dds_entity* iter; + + DDS_REPORT_STACK(); + + if ((children != NULL) && ((size <= 0) || (size >= INT32_MAX))) { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Array is given, but with invalid size"); + goto err; + } + + if ((children == NULL) && (size != 0)) { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Size is given, but no array"); + goto err; + } + + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + goto err; + } + /* Initialize first child to satisfy SAL. */ + if (children) { + children[0] = 0; + } + ret = 0; + iter = e->m_children; + while (iter) { + if ((size_t)ret < size) { /*To fix the warning of signed/unsigned mismatch, type casting is done for the variable 'ret'*/ + children[ret] = iter->m_hdl; + dds_set_explicit(iter->m_hdl); + } + ret++; + iter = iter->m_next; + } + dds_entity_unlock(e); + +err: + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +_Check_return_ dds_return_t +dds_get_qos( + _In_ dds_entity_t entity, + _Out_ dds_qos_t *qos) +{ + dds_entity *e; + dds__retcode_t rc; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + if (qos == NULL) { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER,"Argument qos is NULL"); + goto fail; + } + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + goto fail; + } + if (e->m_deriver.set_qos) { + rc = dds_qos_copy(qos, e->m_qos); + } else { + rc = DDS_RETCODE_ILLEGAL_OPERATION; + ret = DDS_ERRNO(rc, "QoS cannot be set on this entity"); + } + dds_entity_unlock(e); +fail: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK ); + return ret; +} + + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +_Check_return_ dds_return_t +dds_set_qos( + _In_ dds_entity_t entity, + _In_ const dds_qos_t *qos) +{ + dds_entity *e; + dds__retcode_t rc; + dds_return_t ret; + + DDS_REPORT_STACK(); + + if (qos != NULL) { + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if (rc == DDS_RETCODE_OK) { + if (e->m_deriver.set_qos) { + ret = e->m_deriver.set_qos(e, qos, e->m_flags & DDS_ENTITY_ENABLED); + } else { + ret = DDS_ERRNO(DDS_RETCODE_ILLEGAL_OPERATION, "QoS cannot be set on this entity"); + } + if (ret == DDS_RETCODE_OK) { + /* Remember this QoS. */ + if (e->m_qos == NULL) { + e->m_qos = dds_qos_create(); + } + rc = dds_qos_copy(e->m_qos, qos); + ret = DDS_ERRNO(rc, "QoS cannot be set on this entity"); + } + dds_entity_unlock(e); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + } else { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } + DDS_REPORT_FLUSH( ret != DDS_RETCODE_OK); + return ret; +} + + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +_Check_return_ dds_return_t +dds_get_listener( + _In_ dds_entity_t entity, + _Out_ dds_listener_t *listener) +{ + dds_entity *e; + dds_return_t ret = DDS_RETCODE_OK; + dds__retcode_t rc; + + DDS_REPORT_STACK(); + + if (listener != NULL) { + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if (rc == DDS_RETCODE_OK) { + dds_entity_cb_wait(e); + dds_listener_copy (listener, &e->m_listener); + dds_entity_unlock(e); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + } else { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK ); + return ret; +} + + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +_Check_return_ dds_return_t +dds_set_listener( + _In_ dds_entity_t entity, + _In_opt_ const dds_listener_t * listener) +{ + dds_entity *e; + dds__retcode_t rc; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if(rc != DDS_RETCODE_OK){ + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + goto fail; + } + dds_entity_cb_wait(e); + if (listener) { + dds_listener_copy(&e->m_listener, listener); + } else { + dds_listener_reset(&e->m_listener); + } + dds_entity_unlock(e); +fail: + DDS_REPORT_FLUSH( ret != DDS_RETCODE_OK); + return ret; +} + + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +_Check_return_ dds_return_t +dds_enable( + _In_ dds_entity_t entity) +{ + dds_entity *e; + dds__retcode_t rc; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if (rc == DDS_RETCODE_OK) { + if ((e->m_flags & DDS_ENTITY_ENABLED) == 0) { + /* TODO: CHAM-96: Really enable. */ + e->m_flags |= DDS_ENTITY_ENABLED; + DDS_ERROR(DDS_RETCODE_UNSUPPORTED, "Delayed entity enabling is not supported"); + } + dds_entity_unlock(e); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +_Must_inspect_result_ dds_return_t +dds_get_status_changes( + _In_ dds_entity_t entity, + _Out_ uint32_t *status) +{ + dds_entity *e; + dds__retcode_t rc; + dds_return_t ret; + + DDS_REPORT_STACK(); + + if (status != NULL) { + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if (rc == DDS_RETCODE_OK) { + if (e->m_deriver.validate_status) { + *status = e->m_trigger; + ret = DDS_RETCODE_OK; + } else { + ret = DDS_ERRNO(DDS_RETCODE_ILLEGAL_OPERATION, "This entity does not maintain a status."); + } + dds_entity_unlock(e); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + } else { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument status is NULL"); + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +_Check_return_ dds_return_t +dds_get_enabled_status( + _In_ dds_entity_t entity, + _Out_ uint32_t *status) +{ + dds_entity *e; + dds__retcode_t rc; + dds_return_t ret; + + DDS_REPORT_STACK(); + + if (status != NULL) { + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if (rc == DDS_RETCODE_OK) { + if (e->m_deriver.validate_status) { + *status = (e->m_status_enable & ~DDS_INTERNAL_STATUS_MASK); + ret = DDS_RETCODE_OK; + } else { + ret = DDS_ERRNO(DDS_RETCODE_ILLEGAL_OPERATION, "This entity does not maintain a status."); + } + dds_entity_unlock(e); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + } else{ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument status is NULL"); + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +DDS_EXPORT dds_return_t +dds_set_enabled_status( + _In_ dds_entity_t entity, + _In_ uint32_t mask) +{ + dds_entity *e; + dds__retcode_t rc; + dds_return_t ret; + + DDS_REPORT_STACK(); + + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if (rc == DDS_RETCODE_OK) { + if (e->m_deriver.validate_status) { + ret = e->m_deriver.validate_status(mask); + if (ret == DDS_RETCODE_OK) { + /* Don't block internal status triggers. */ + mask |= DDS_INTERNAL_STATUS_MASK; + e->m_status_enable = mask; + e->m_trigger &= mask; + if (e->m_deriver.propagate_status) { + ret = e->m_deriver.propagate_status(e, mask, true); + } + } + } else { + ret = DDS_ERRNO (DDS_RETCODE_ILLEGAL_OPERATION, "This entity does not maintain a status."); + } + dds_entity_unlock(e); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + + + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +_Check_return_ dds_return_t +dds_read_status( + _In_ dds_entity_t entity, + _Out_ uint32_t *status, + _In_ uint32_t mask) +{ + dds_entity *e; + dds__retcode_t rc; + dds_return_t ret; + + DDS_REPORT_STACK(); + + if (status != NULL) { + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if (rc == DDS_RETCODE_OK) { + if (e->m_deriver.validate_status) { + ret = e->m_deriver.validate_status(mask); + assert(ret <= DDS_RETCODE_OK); + if (ret == DDS_RETCODE_OK) { + *status = e->m_trigger & mask; + } + } else { + ret = DDS_ERRNO(DDS_RETCODE_ILLEGAL_OPERATION, "This entity does not maintain a status."); + } + dds_entity_unlock(e); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + } else { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument status is NULL"); + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + + + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +_Check_return_ dds_return_t +dds_take_status( + _In_ dds_entity_t entity, + _Out_ uint32_t *status, + _In_ uint32_t mask) +{ + dds_entity *e; + dds__retcode_t rc; + dds_return_t ret; + + DDS_REPORT_STACK(); + if (status != NULL) { + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if (rc == DDS_RETCODE_OK) { + if (e->m_deriver.validate_status) { + ret = e->m_deriver.validate_status(mask); + assert(ret <= DDS_RETCODE_OK); + if (ret == DDS_RETCODE_OK) { + *status = e->m_trigger & mask; + if (e->m_deriver.propagate_status) { + ret = e->m_deriver.propagate_status(e, *status, false); + } + e->m_trigger &= ~mask; + } + } else { + ret = DDS_ERRNO(DDS_RETCODE_ILLEGAL_OPERATION, "This entity does not maintain a status."); + } + dds_entity_unlock(e); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + } else { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument status is NUL"); + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + + + +void +dds_entity_status_signal( + _In_ dds_entity *e) +{ + assert(e); + /* Signal the observers in an unlocked state. + * This is safe because we use handles and the current entity + * will not be deleted while we're in this signaling state + * due to a claimed handle. */ + dds_entity_observers_signal(e, e->m_trigger); +} + + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +_Check_return_ dds_return_t +dds_get_domainid( + _In_ dds_entity_t entity, + _Out_ dds_domainid_t *id) +{ + dds_entity *e; + dds__retcode_t rc; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + if (id != NULL) { + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if (rc == DDS_RETCODE_OK) { + *id = e->m_domainid; + dds_entity_unlock(e); + } else{ + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + } else{ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument domain id is NULL"); + } + + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +_Check_return_ dds_return_t +dds_get_instance_handle( + _In_ dds_entity_t entity, + _Out_ dds_instance_handle_t *ihdl) +{ + dds_entity *e; + dds__retcode_t rc; + dds_return_t ret; + + DDS_REPORT_STACK(); + + if (ihdl != NULL) { + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if (rc == DDS_RETCODE_OK) { + if (e->m_deriver.get_instance_hdl) { + ret = e->m_deriver.get_instance_hdl(e, ihdl); + } else { + ret = DDS_ERRNO(DDS_RETCODE_ILLEGAL_OPERATION, "Instance handle is not valid"); + } + dds_entity_unlock(e); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + } else { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument instance handle is NULL"); + } + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + + +_Check_return_ dds__retcode_t +dds_valid_hdl( + _In_ dds_entity_t hdl, + _In_ dds_entity_kind_t kind) +{ + dds__retcode_t rc = hdl; + ut_handle_t utr; + + /* When the given handle already contains an error, then return that + * same error to retain the original information. */ + if (hdl >= 0) { + utr = ut_handle_status(hdl, NULL, kind); + if(utr == UT_HANDLE_OK){ + rc = DDS_RETCODE_OK; + } else if(utr == UT_HANDLE_UNEQUAL_KIND){ + rc = DDS_RETCODE_ILLEGAL_OPERATION; + DDS_WARNING(rc, "Given (%s) [0x%08lx] entity type can not perform this operation.", dds__entity_kind_str(hdl), hdl); + } else if(utr == UT_HANDLE_INVALID){ + rc = DDS_RETCODE_BAD_PARAMETER; + DDS_WARNING(rc, "Given (%s) [0x%08lx] entity is invalid", dds__entity_kind_str(hdl), hdl); + } else if(utr == UT_HANDLE_DELETED){ + rc = DDS_RETCODE_ALREADY_DELETED; + DDS_WARNING(rc, "Given (%s) [0x%08lx] entity is already deleted.", dds__entity_kind_str(hdl), hdl); + } else if(utr == UT_HANDLE_CLOSED){ + rc = DDS_RETCODE_ALREADY_DELETED; + DDS_WARNING(rc, "Given (%s) [0x%08lx] entity is already deleted.", dds__entity_kind_str(hdl), hdl); + } else { + rc = DDS_RETCODE_ERROR; + DDS_WARNING(rc, "An internal error occurred."); + } + } else { + rc = DDS_RETCODE_BAD_PARAMETER; + DDS_WARNING(rc, "Given entity (0x%08lx) was not properly created.", hdl); + } + return rc; +} + +_Acquires_exclusive_lock_(*e) +_Check_return_ dds__retcode_t +dds_entity_lock( + _In_ dds_entity_t hdl, + _In_ dds_entity_kind_t kind, + _Out_ dds_entity **e) +{ + dds__retcode_t rc = hdl; + ut_handle_t utr; + assert(e); + /* When the given handle already contains an error, then return that + * same error to retain the original information. */ + if (hdl >= 0) { + utr = ut_handle_claim(hdl, NULL, kind, (void**)e); + if (utr == UT_HANDLE_OK) { + os_mutexLock(&((*e)->m_mutex)); + /* The handle could have been closed while we were waiting for the mutex. */ + if (ut_handle_is_closed(hdl, (*e)->m_hdllink)) { + dds_entity_unlock(*e); + utr = UT_HANDLE_CLOSED; + } + } + if(utr == UT_HANDLE_OK){ + rc = DDS_RETCODE_OK; + } else if(utr == UT_HANDLE_UNEQUAL_KIND){ + rc = DDS_RETCODE_ILLEGAL_OPERATION; + DDS_WARNING(rc, "Given (%s) [0x%08lx] entity type can not perform this operation.", dds__entity_kind_str(hdl), hdl); + } else if(utr == UT_HANDLE_INVALID){ + rc = DDS_RETCODE_BAD_PARAMETER; + DDS_WARNING(rc, "Given (%s) [0x%08lx] entity is invalid", dds__entity_kind_str(hdl), hdl); + } else if(utr == UT_HANDLE_DELETED){ + rc = DDS_RETCODE_ALREADY_DELETED; + DDS_WARNING(rc , "Given (%s) [0x%08lx] entity is already deleted", dds__entity_kind_str(hdl), hdl); + } else if(utr == UT_HANDLE_CLOSED){ + rc = DDS_RETCODE_ALREADY_DELETED; + DDS_WARNING(rc, "Given (%s) [0x%08lx] entity is already deleted", dds__entity_kind_str(hdl), hdl); + } else { + rc = DDS_RETCODE_ERROR; + DDS_WARNING(rc, "An internal error occurred"); + } + } else { + rc = DDS_RETCODE_BAD_PARAMETER; + DDS_WARNING(rc, "Given entity (0x%08lx) was not properly created.", hdl); + } + return rc; +} + + +_Releases_exclusive_lock_(e) +void +dds_entity_unlock( + _Inout_ dds_entity *e) +{ + assert(e); + os_mutexUnlock(&e->m_mutex); + ut_handle_release(e->m_hdl, e->m_hdllink); +} + + + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +dds_return_t +dds_triggered( + _In_ dds_entity_t entity) +{ + dds_entity *e; + dds_return_t ret; + dds__retcode_t rc; + + DDS_REPORT_STACK(); + + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if (rc == DDS_RETCODE_OK) { + ret = (e->m_trigger != 0); + dds_entity_unlock(e); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + + + +_Check_return_ dds__retcode_t +dds_entity_observer_register_nl( + _In_ dds_entity* observed, + _In_ dds_entity_t observer, + _In_ dds_entity_callback cb) +{ + dds__retcode_t rc = DDS_RETCODE_OK; + dds_entity_observer *o = os_malloc(sizeof(dds_entity_observer)); + assert(observed); + o->m_cb = cb; + o->m_observer = observer; + o->m_next = NULL; + if (observed->m_observers == NULL) { + observed->m_observers = o; + } else { + dds_entity_observer *last; + dds_entity_observer *idx = observed->m_observers; + while ((idx != NULL) && (o != NULL)) { + if (idx->m_observer == observer) { + os_free(o); + o = NULL; + rc = DDS_RETCODE_PRECONDITION_NOT_MET; + } + last = idx; + idx = idx->m_next; + } + if (o != NULL) { + last->m_next = o; + } + } + return rc; +} + + + +_Check_return_ dds__retcode_t +dds_entity_observer_register( + _In_ dds_entity_t observed, + _In_ dds_entity_t observer, + _In_ dds_entity_callback cb) +{ + dds__retcode_t rc; + dds_entity *e; + assert(cb); + rc = dds_entity_lock(observed, DDS_KIND_DONTCARE, &e); + if (rc == DDS_RETCODE_OK) { + rc = dds_entity_observer_register_nl(e, observer, cb); + dds_entity_unlock(e); + } else{ + DDS_ERROR(rc, "Error occurred on locking observer"); + } + return rc; +} + + + +dds__retcode_t +dds_entity_observer_unregister_nl( + _In_ dds_entity* observed, + _In_ dds_entity_t observer) +{ + dds__retcode_t rc = DDS_RETCODE_PRECONDITION_NOT_MET; + dds_entity_observer *prev = NULL; + dds_entity_observer *idx = observed->m_observers; + while (idx != NULL) { + if (idx->m_observer == observer) { + if (prev == NULL) { + observed->m_observers = idx->m_next; + } else { + prev->m_next = idx->m_next; + } + os_free(idx); + idx = NULL; + rc = DDS_RETCODE_OK; + } else { + prev = idx; + idx = idx->m_next; + } + } + return rc; +} + + + +dds__retcode_t +dds_entity_observer_unregister( + _In_ dds_entity_t observed, + _In_ dds_entity_t observer) +{ + dds__retcode_t rc; + dds_return_t ret; + dds_entity *e; + rc = dds_entity_lock(observed, DDS_KIND_DONTCARE, &e); + if (rc == DDS_RETCODE_OK) { + rc = dds_entity_observer_unregister_nl(e, observer); + dds_entity_unlock(e); + } else{ + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + return rc; +} + + + +static void +dds_entity_observers_delete( + _In_ dds_entity *observed) +{ + dds_entity_observer *next; + dds_entity_observer *idx = observed->m_observers; + while (idx != NULL) { + next = idx->m_next; + os_free(idx); + idx = next; + } + observed->m_observers = NULL; +} + + + +static void +dds_entity_observers_signal( + _In_ dds_entity *observed, + _In_ uint32_t status) +{ + dds_entity_observer *idx = observed->m_observers; + while (idx != NULL) { + idx->m_cb(idx->m_observer, observed->m_hdl, status); + idx = idx->m_next; + } +} + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +dds_entity_t +dds_get_topic( + _In_ dds_entity_t entity) +{ + dds__retcode_t rc; + dds_entity_t hdl = entity; + dds_reader *rd; + dds_writer *wr; + + DDS_REPORT_STACK(); + + rc = dds_reader_lock(entity, &rd); + if(rc == DDS_RETCODE_OK) { + hdl = rd->m_topic->m_entity.m_hdl; + dds_reader_unlock(rd); + } else if (rc == DDS_RETCODE_ILLEGAL_OPERATION) { + rc = dds_writer_lock(entity, &wr); + if (rc == DDS_RETCODE_OK) { + hdl = wr->m_topic->m_entity.m_hdl; + dds_writer_unlock(wr); + } else if (dds_entity_kind(entity) == DDS_KIND_COND_READ || dds_entity_kind(entity) == DDS_KIND_COND_QUERY) { + hdl = dds_get_topic(dds_get_parent(entity)); + rc = DDS_RETCODE_OK; + } + } + if (rc != DDS_RETCODE_OK) { + hdl = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + DDS_REPORT_FLUSH(hdl <= 0); + return hdl; +} + +static void +dds_set_explicit( + _In_ dds_entity_t entity) +{ + dds_entity *e; + dds__retcode_t rc; + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if( rc == DDS_RETCODE_OK){ + e->m_flags &= ~DDS_ENTITY_IMPLICIT; + dds_entity_unlock(e); + } +} + +const char * +dds__entity_kind_str(_In_ dds_entity_t e) +{ + if(e <= 0) { + return "(ERROR)"; + } + switch(e & DDS_ENTITY_KIND_MASK) { + case DDS_KIND_TOPIC: return "Topic"; + case DDS_KIND_PARTICIPANT: return "Participant"; + case DDS_KIND_READER: return "Reader"; + case DDS_KIND_WRITER: return "Writer"; + case DDS_KIND_SUBSCRIBER: return "Subscriber"; + case DDS_KIND_PUBLISHER: return "Publisher"; + case DDS_KIND_COND_READ: return "ReadCondition"; + case DDS_KIND_COND_QUERY: return "QueryCondition"; + case DDS_KIND_WAITSET: return "WaitSet"; + default: return "(INVALID_ENTITY)"; + } +} diff --git a/src/core/ddsc/src/dds_err.c b/src/core/ddsc/src/dds_err.c new file mode 100644 index 0000000..89ad9aa --- /dev/null +++ b/src/core/ddsc/src/dds_err.c @@ -0,0 +1,102 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include "os/os.h" +#include "dds__types.h" +#include "dds__err.h" + +#define DDS_ERR_CODE_NUM 12 +#define DDS_ERR_MSG_MAX 128 + +#define DDS_ERR_NR_INDEX(e) (((-e) & DDS_ERR_NR_MASK) -1) + +static const char * dds_err_code_array[DDS_ERR_CODE_NUM] = +{ + "Error", + "Unsupported", + "Bad Parameter", + "Precondition Not Met", + "Out Of Resources", + "Not Enabled", + "Immutable Policy", + "Inconsistent Policy", + "Already Deleted", + "Timeout", + "No Data", + "Illegal Operation" +}; + +const char * dds_err_str (dds_return_t err) +{ + unsigned index = DDS_ERR_NR_INDEX (err); + if (err >= 0) + { + return "Success"; + } + if (index >= DDS_ERR_CODE_NUM) + { + return "Unknown"; + } + return dds_err_code_array[index]; +} + +bool dds_err_check (dds_return_t err, unsigned flags, const char * where) +{ + if (err < 0) + { + if (flags & (DDS_CHECK_REPORT | DDS_CHECK_FAIL)) + { + char msg[DDS_ERR_MSG_MAX]; + (void) snprintf (msg, DDS_ERR_MSG_MAX, "Error %d:M%d:%s", dds_err_file_id(err), dds_err_line(err), dds_err_str(err)); + if (flags & DDS_CHECK_REPORT) + { + printf ("%s: %s\n", where, msg); + } + if (flags & DDS_CHECK_FAIL) + { + dds_fail (msg, where); + } + } + if (flags & DDS_CHECK_EXIT) + { + exit (-1); + } + } + return (err >= 0); +} + + +static void dds_fail_default (const char * msg, const char * where) +{ + fprintf (stderr, "Aborting Failure: %s %s\n", where, msg); + abort (); +} + +static dds_fail_fn dds_fail_func = dds_fail_default; + +void dds_fail_set (dds_fail_fn fn) +{ + dds_fail_func = fn; +} + +dds_fail_fn dds_fail_get (void) +{ + return dds_fail_func; +} + +void dds_fail (const char * msg, const char * where) +{ + if (dds_fail_func) + { + (dds_fail_func) (msg, where); + } +} diff --git a/src/core/ddsc/src/dds_iid.c b/src/core/ddsc/src/dds_iid.c new file mode 100644 index 0000000..1809dd8 --- /dev/null +++ b/src/core/ddsc/src/dds_iid.c @@ -0,0 +1,79 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "dds__iid.h" +#include "ddsi/q_time.h" +#include "ddsi/q_globals.h" + +static os_mutex dds_iid_lock_g; +static dds_iid dds_iid_g; + +static void dds_tea_encrypt (uint32_t v[2], const uint32_t k[4]) +{ + /* TEA encryption straight from Wikipedia */ + uint32_t v0=v[0], v1=v[1], sum=0, i; /* set up */ + uint32_t delta=0x9e3779b9; /* a key schedule constant */ + uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */ + for (i=0; i < 32; i++) { /* basic cycle start */ + sum += delta; + v0 += ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1); + v1 += ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3); + } /* end cycle */ + v[0]=v0; v[1]=v1; +} + +static void dds_tea_decrypt (uint32_t v[2], const uint32_t k[4]) +{ + uint32_t v0=v[0], v1=v[1], sum=0xC6EF3720, i; /* set up */ + uint32_t delta=0x9e3779b9; /* a key schedule constant */ + uint32_t k0=k[0], k1=k[1], k2=k[2], k3=k[3]; /* cache key */ + for (i=0; i<32; i++) { /* basic cycle start */ + v1 -= ((v0<<4) + k2) ^ (v0 + sum) ^ ((v0>>5) + k3); + v0 -= ((v1<<4) + k0) ^ (v1 + sum) ^ ((v1>>5) + k1); + sum -= delta; + } /* end cycle */ + v[0]=v0; v[1]=v1; +} + +uint64_t dds_iid_gen (void) +{ + uint64_t iid; + union { uint64_t u64; uint32_t u32[2]; } tmp; + + os_mutexLock (&dds_iid_lock_g); + tmp.u64 = ++dds_iid_g.counter; + dds_tea_encrypt (tmp.u32, dds_iid_g.key); + iid = tmp.u64; + os_mutexUnlock (&dds_iid_lock_g); + return iid; +} + +void dds_iid_init (void) +{ + union { uint64_t u64; uint32_t u32[2]; } tmp; + nn_wctime_t tnow = now (); + + os_mutexInit (&dds_iid_lock_g); + + dds_iid_g.key[0] = (uint32_t) ((uintptr_t) &dds_iid_g); + dds_iid_g.key[1] = (uint32_t) tnow.v; + dds_iid_g.key[2] = (uint32_t) (tnow.v >> 32); + dds_iid_g.key[3] = 0xdeadbeef; + + tmp.u64 = 0; + dds_tea_decrypt (tmp.u32, dds_iid_g.key); + dds_iid_g.counter = tmp.u64; +} + +void dds_iid_fini (void) +{ + os_mutexDestroy (&dds_iid_lock_g); +} diff --git a/src/core/ddsc/src/dds_init.c b/src/core/ddsc/src/dds_init.c new file mode 100644 index 0000000..0ad2486 --- /dev/null +++ b/src/core/ddsc/src/dds_init.c @@ -0,0 +1,305 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include +#include +#include "dds__init.h" +#include "dds__rhc.h" +#include "dds__tkmap.h" +#include "dds__iid.h" +#include "dds__domain.h" +#include "dds__err.h" +#include "dds__builtin.h" +#include "dds__report.h" +#include "ddsi/ddsi_ser.h" +#include "ddsi/q_servicelease.h" +#include "ddsi/q_entity.h" +#include +#include "ddsc/ddsc_project.h" + +#ifdef _WRS_KERNEL +char *os_environ[] = { NULL }; +#endif + +#define DOMAIN_ID_MIN 0 +#define DOMAIN_ID_MAX 230 + +struct q_globals gv; + +dds_globals dds_global = +{ + DDS_DOMAIN_DEFAULT, 0, + NULL, NULL, NULL, NULL +}; + +static struct cfgst * dds_cfgst = NULL; + + + + +os_mutex dds__init_mutex; + +static void +dds__fini_once(void) +{ + os_mutexDestroy(&dds__init_mutex); + os_osExit(); +} + +static void +dds__init_once(void) +{ + os_osInit(); + os_mutexInit(&dds__init_mutex); + os_procAtExit(dds__fini_once); +} + + + +void +dds__startup(void) +{ + static os_once_t dds__init_control = OS_ONCE_T_STATIC_INIT; + os_once(&dds__init_control, dds__init_once); +} + + +dds_return_t +dds_init(void) +{ + dds_return_t ret = DDS_RETCODE_OK; + const char * uri; + char progname[50]; + char hostname[64]; + uint32_t len; + + /* Be sure the DDS lifecycle resources are initialized. */ + dds__startup(); + + DDS_REPORT_STACK(); + + os_mutexLock(&dds__init_mutex); + + dds_global.m_init_count++; + if (dds_global.m_init_count > 1) + { + goto skip; + } + + if (ut_handleserver_init() != UT_HANDLE_OK) + { + ret = DDS_ERRNO(DDS_RETCODE_ERROR, "Failed to initialize internal handle server"); + goto fail_handleserver; + } + + gv.tstart = now (); + gv.exception = false; + gv.static_logbuf_lock_inited = 0; + logbuf_init (&gv.static_logbuf); + os_mutexInit (&gv.static_logbuf_lock); + gv.static_logbuf_lock_inited = 1; + os_mutexInit (&dds_global.m_mutex); + + uri = os_getenv (DDSC_PROJECT_NAME_NOSPACE_CAPS"_URI"); + dds_cfgst = config_init (uri); + if (dds_cfgst == NULL) + { + ret = DDS_ERRNO(DDS_RETCODE_ERROR, "Failed to parse configuration XML file %s", uri); + goto fail_config; + } + /* The config.domainId can change internally in DDSI. So, remember what the + * main configured domain id is. */ + dds_global.m_default_domain = config.domainId; + + dds__builtin_init(); + + if (rtps_config_prep(dds_cfgst) != 0) + { + ret = DDS_ERRNO(DDS_RETCODE_ERROR, "Failed to configure RTPS."); + goto fail_rtps_config; + } + + ut_avlInit(&dds_domaintree_def, &dds_global.m_domains); + + /* Start monitoring the liveliness of all threads and renewing the + service lease if everything seems well. */ + + gv.servicelease = nn_servicelease_new(0, 0); + if (gv.servicelease == NULL) + { + ret = DDS_ERRNO(DDS_RETCODE_OUT_OF_RESOURCES, "Failed to create a servicelease."); + goto fail_servicelease_new; + } + if (nn_servicelease_start_renewing(gv.servicelease) < 0) + { + ret = DDS_ERRNO(DDS_RETCODE_ERROR, "Failed to start the servicelease."); + goto fail_servicelease_start; + } + + if (rtps_init() < 0) + { + ret = DDS_ERRNO(DDS_RETCODE_ERROR, "Failed to initialize RTPS."); + goto fail_rtps_init; + } + upgrade_main_thread(); + + /* Set additional default participant properties */ + + gv.default_plist_pp.process_id = (unsigned)os_procIdSelf(); + gv.default_plist_pp.present |= PP_PRISMTECH_PROCESS_ID; + if (os_procName(progname, sizeof(progname)) > 0) + { + gv.default_plist_pp.exec_name = dds_string_dup(progname); + } + else + { + gv.default_plist_pp.exec_name = dds_string_alloc(32); + (void) snprintf(gv.default_plist_pp.exec_name, 32, "%s: %u", DDSC_PROJECT_NAME, gv.default_plist_pp.process_id); + } + len = (uint32_t) (13 + strlen(gv.default_plist_pp.exec_name)); + gv.default_plist_pp.present |= PP_PRISMTECH_EXEC_NAME; + if (os_gethostname(hostname, sizeof(hostname)) == os_resultSuccess) + { + gv.default_plist_pp.node_name = dds_string_dup(hostname); + gv.default_plist_pp.present |= PP_PRISMTECH_NODE_NAME; + } + gv.default_plist_pp.entity_name = dds_alloc(len); + (void) snprintf(gv.default_plist_pp.entity_name, len, "%s<%u>", progname, + gv.default_plist_pp.process_id); + gv.default_plist_pp.present |= PP_ENTITY_NAME; + +skip: + os_mutexUnlock(&dds__init_mutex); + DDS_REPORT_FLUSH(false); + return DDS_RETCODE_OK; + +fail_rtps_init: +fail_servicelease_start: + nn_servicelease_free (gv.servicelease); + gv.servicelease = NULL; +fail_servicelease_new: + thread_states_fini(); +fail_rtps_config: + dds__builtin_fini(); + dds_global.m_default_domain = DDS_DOMAIN_DEFAULT; + config_fini (dds_cfgst); + dds_cfgst = NULL; +fail_config: + gv.static_logbuf_lock_inited = 0; + os_mutexDestroy (&gv.static_logbuf_lock); + os_mutexDestroy (&dds_global.m_mutex); + ut_handleserver_fini(); +fail_handleserver: + dds_global.m_init_count--; + os_mutexUnlock(&dds__init_mutex); + DDS_REPORT_FLUSH(true); + return ret; +} + + + +extern void dds_fini (void) +{ + os_mutexLock(&dds__init_mutex); + assert(dds_global.m_init_count > 0); + dds_global.m_init_count--; + if (dds_global.m_init_count == 0) + { + dds__builtin_fini(); + + ut_handleserver_fini(); + rtps_term (); + nn_servicelease_free (gv.servicelease); + gv.servicelease = NULL; + downgrade_main_thread (); + thread_states_fini (); + + config_fini (dds_cfgst); + dds_cfgst = NULL; + os_mutexDestroy (&gv.static_logbuf_lock); + os_mutexDestroy (&dds_global.m_mutex); + dds_global.m_default_domain = DDS_DOMAIN_DEFAULT; + } + os_mutexUnlock(&dds__init_mutex); +} + + + + + +static int dds__init_plugin (void) +{ + os_mutexInit (&gv.attach_lock); + dds_iid_init (); + if (dds_global.m_dur_init) (dds_global.m_dur_init) (); + return 0; +} + +static void dds__fini_plugin (void) +{ + os_mutexDestroy (&gv.attach_lock); + if (dds_global.m_dur_fini) (dds_global.m_dur_fini) (); + dds_iid_fini (); +} + +void ddsi_plugin_init (void) +{ + /* Register initialization/clean functions */ + + ddsi_plugin.init_fn = dds__init_plugin; + ddsi_plugin.fini_fn = dds__fini_plugin; + + /* Register read cache functions */ + + ddsi_plugin.rhc_free_fn = dds_rhc_free; + ddsi_plugin.rhc_fini_fn = dds_rhc_fini; + ddsi_plugin.rhc_store_fn = dds_rhc_store; + ddsi_plugin.rhc_unregister_wr_fn = dds_rhc_unregister_wr; + ddsi_plugin.rhc_relinquish_ownership_fn = dds_rhc_relinquish_ownership; + ddsi_plugin.rhc_set_qos_fn = dds_rhc_set_qos; + ddsi_plugin.rhc_lookup_fn = dds_tkmap_lookup_instance_ref; + ddsi_plugin.rhc_unref_fn = dds_tkmap_instance_unref; + + /* Register iid generator */ + + ddsi_plugin.iidgen_fn = dds_iid_gen; +} + + + +//provides explicit default domain id. +dds_domainid_t dds_domain_default (void) +{ + return dds_global.m_default_domain; +} + + +dds_return_t +dds__check_domain( + _In_ dds_domainid_t domain) +{ + dds_return_t ret = DDS_RETCODE_OK; + /* If domain is default: use configured id. */ + if (domain != DDS_DOMAIN_DEFAULT) + { + /* Specific domain has to be the same as the configured domain. */ + if (domain != dds_global.m_default_domain) + { + ret = DDS_ERRNO(DDS_RETCODE_ERROR, + "Inconsistent domain configuration detected: domain on configuration: %d, domain %d", + dds_global.m_default_domain, domain); + } + } + return ret; +} diff --git a/src/core/ddsc/src/dds_instance.c b/src/core/ddsc/src/dds_instance.c new file mode 100644 index 0000000..a32b595 --- /dev/null +++ b/src/core/ddsc/src/dds_instance.c @@ -0,0 +1,468 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include "ddsc/dds.h" +#include "dds__entity.h" +#include "dds__write.h" +#include "dds__writer.h" +#include "dds__rhc.h" +#include "dds__tkmap.h" +#include "dds__err.h" +#include "ddsi/ddsi_ser.h" +#include "ddsi/q_entity.h" +#include "ddsi/q_thread.h" +#include "q__osplser.h" +#include "dds__report.h" + + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +dds_return_t +dds_writedispose( + _In_ dds_entity_t writer, + _In_ const void *data) +{ + dds_return_t ret; + DDS_REPORT_STACK(); + ret = dds_writedispose_ts(writer, data, dds_time()); + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK ); + return ret; +} + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +dds_return_t +dds_dispose( + _In_ dds_entity_t writer, + _In_ const void *data) +{ + dds_return_t ret; + DDS_REPORT_STACK(); + ret = dds_dispose_ts(writer, data, dds_time()); + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK ); + return ret; +} + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +dds_return_t +dds_dispose_ih( + _In_ dds_entity_t writer, + _In_ dds_instance_handle_t handle) +{ + dds_return_t ret; + DDS_REPORT_STACK(); + ret = dds_dispose_ih_ts(writer, handle, dds_time()); + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK ); + return ret; +} + +static struct tkmap_instance* +dds_instance_find( + _In_ const dds_topic *topic, + _In_ const void *data, + _In_ const bool create) +{ + serdata_t sd = serialize_key (gv.serpool, topic->m_stopic, data); + struct tkmap_instance * inst = dds_tkmap_find (topic, sd, false, create); + ddsi_serdata_unref (sd); + return inst; +} + +static void +dds_instance_remove( + _In_ const dds_topic *topic, + _In_opt_ const void *data, + _In_ dds_instance_handle_t handle) +{ + struct tkmap_instance * inst; + + if (handle != DDS_HANDLE_NIL) { + inst = dds_tkmap_find_by_id (gv.m_tkmap, handle); + } else { + assert (data); + inst = dds_instance_find (topic, data, false); + } + + if (inst) { + struct thread_state1 * const thr = lookup_thread_state(); + const bool asleep = thr ? !vtime_awake_p(thr->vtime) : false; + if (asleep) { + thread_state_awake(thr); + } + dds_tkmap_instance_unref (inst); + if (asleep) { + thread_state_asleep(thr); + } + } +} + +static const dds_topic* +dds_instance_info( + _In_ dds_entity *e) +{ + const dds_topic *topic = NULL; + + assert (e); + assert ((dds_entity_kind(e->m_hdl) == DDS_KIND_READER) || (dds_entity_kind(e->m_hdl) == DDS_KIND_WRITER)); + + if (dds_entity_kind(e->m_hdl) == DDS_KIND_READER) { + topic = ((dds_reader*)e)->m_topic; + } else { + topic = ((dds_writer*)e)->m_topic; + } + return topic; +} + +static const dds_topic * dds_instance_info_by_hdl (dds_entity_t e) +{ + const dds_topic * topic = NULL; + dds__retcode_t rc; + dds_entity *w_or_r; + + rc = dds_entity_lock(e, DDS_KIND_WRITER, &w_or_r); + if (rc == DDS_RETCODE_ILLEGAL_OPERATION) { + rc = dds_entity_lock(e, DDS_KIND_READER, &w_or_r); + } + if (rc == DDS_RETCODE_OK) { + topic = dds_instance_info(w_or_r); + dds_entity_unlock(w_or_r); + } + else { + DDS_ERROR(rc, "Error occurred on locking entity"); + } + return topic; +} + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +dds_return_t +dds_register_instance( + _In_ dds_entity_t writer, + _Out_ dds_instance_handle_t *handle, + _In_ const void *data) +{ + struct tkmap_instance * inst; + dds_entity *wr; + dds_return_t ret; + dds__retcode_t rc; + + DDS_REPORT_STACK(); + + if(data == NULL){ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument data is NULL"); + goto err; + } + if(handle == NULL){ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument handle is NULL"); + goto err; + } + rc = dds_entity_lock(writer, DDS_KIND_WRITER, &wr); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking writer"); + goto err; + } + inst = dds_instance_find (((dds_writer*) wr)->m_topic, data, true); + if(inst != NULL){ + *handle = inst->m_iid; + ret = DDS_RETCODE_OK; + } else{ + ret = DDS_ERRNO(DDS_RETCODE_ERROR, "Unable to create instance"); + } + dds_entity_unlock(wr); +err: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +dds_return_t +dds_unregister_instance( + _In_ dds_entity_t writer, + _In_opt_ const void *data) +{ + dds_return_t ret; + DDS_REPORT_STACK(); + ret = dds_unregister_instance_ts (writer, data, dds_time()); + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK ); + return ret; +} + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +dds_return_t +dds_unregister_instance_ih( + _In_ dds_entity_t writer, + _In_opt_ dds_instance_handle_t handle) +{ + dds_return_t ret; + DDS_REPORT_STACK(); + ret = dds_unregister_instance_ih_ts(writer, handle, dds_time()); + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK ); + return ret; +} + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +dds_return_t +dds_unregister_instance_ts( + _In_ dds_entity_t writer, + _In_opt_ const void *data, + _In_ dds_time_t timestamp) +{ + dds_return_t ret = DDS_RETCODE_OK; + dds__retcode_t rc; + bool autodispose = true; + dds_write_action action = DDS_WR_ACTION_UNREGISTER; + void * sample = (void*) data; + dds_entity *wr; + + DDS_REPORT_STACK(); + + if (data == NULL){ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument data is NULL"); + goto err; + } + if(timestamp < 0){ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument timestamp has negative value"); + goto err; + } + rc = dds_entity_lock(writer, DDS_KIND_WRITER, &wr); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking writer"); + goto err; + } + + if (wr->m_qos) { + dds_qget_writer_data_lifecycle (wr->m_qos, &autodispose); + } + if (autodispose) { + dds_instance_remove (((dds_writer*) wr)->m_topic, data, DDS_HANDLE_NIL); + action |= DDS_WR_DISPOSE_BIT; + } + ret = dds_write_impl ((dds_writer*)wr, sample, timestamp, action); + dds_entity_unlock(wr); +err: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +dds_return_t +dds_unregister_instance_ih_ts( + _In_ dds_entity_t writer, + _In_opt_ dds_instance_handle_t handle, + _In_ dds_time_t timestamp) +{ + dds_return_t ret = DDS_RETCODE_OK; + dds__retcode_t rc; + bool autodispose = true; + dds_write_action action = DDS_WR_ACTION_UNREGISTER; + dds_entity *wr; + struct tkmap *map; + const dds_topic *topic; + void *sample; + + DDS_REPORT_STACK(); + + rc = dds_entity_lock(writer, DDS_KIND_WRITER, &wr); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking writer"); + goto err; + } + + if (wr->m_qos) { + dds_qget_writer_data_lifecycle (wr->m_qos, &autodispose); + } + if (autodispose) { + dds_instance_remove (((dds_writer*) wr)->m_topic, NULL, handle); + action |= DDS_WR_DISPOSE_BIT; + } + + map = gv.m_tkmap; + topic = dds_instance_info((dds_entity*)wr); + sample = dds_alloc (topic->m_descriptor->m_size); + if (dds_tkmap_get_key (map, handle, sample)) { + ret = dds_write_impl ((dds_writer*)wr, sample, timestamp, action); + } else{ + ret = DDS_ERRNO(DDS_RETCODE_PRECONDITION_NOT_MET, "No instance related with the provided handle is found"); + } + dds_sample_free (sample, topic->m_descriptor, DDS_FREE_ALL); + + dds_entity_unlock(wr); +err: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +dds_return_t +dds_writedispose_ts( + _In_ dds_entity_t writer, + _In_ const void *data, + _In_ dds_time_t timestamp) +{ + dds_return_t ret; + dds__retcode_t rc; + dds_writer *wr; + + DDS_REPORT_STACK(); + + rc = dds_writer_lock(writer, &wr); + if (rc == DDS_RETCODE_OK) { + ret = dds_write_impl (wr, data, timestamp, DDS_WR_ACTION_WRITE_DISPOSE); + if (ret == DDS_RETCODE_OK) { + dds_instance_remove (wr->m_topic, data, DDS_HANDLE_NIL); + } + dds_writer_unlock(wr); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking writer"); + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +static dds_return_t +dds_dispose_impl( + _In_ dds_writer *wr, + _In_ const void *data, + _In_ dds_instance_handle_t handle, + _In_ dds_time_t timestamp) +{ + dds_return_t ret; + assert(wr); + ret = dds_write_impl(wr, data, timestamp, DDS_WR_ACTION_DISPOSE); + if (ret == DDS_RETCODE_OK) { + dds_instance_remove (wr->m_topic, data, handle); + } + return ret; +} + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +dds_return_t +dds_dispose_ts( + _In_ dds_entity_t writer, + _In_ const void *data, + _In_ dds_time_t timestamp) +{ + dds_return_t ret; + dds__retcode_t rc; + dds_writer *wr; + + DDS_REPORT_STACK(); + + rc = dds_writer_lock(writer, &wr); + if (rc == DDS_RETCODE_OK) { + ret = dds_dispose_impl(wr, data, DDS_HANDLE_NIL, timestamp); + dds_writer_unlock(wr); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking writer"); + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +dds_return_t +dds_dispose_ih_ts( + _In_ dds_entity_t writer, + _In_ dds_instance_handle_t handle, + _In_ dds_time_t timestamp) +{ + dds_return_t ret; + dds__retcode_t rc; + dds_writer *wr; + + DDS_REPORT_STACK(); + + rc = dds_writer_lock(writer, &wr); + if (rc == DDS_RETCODE_OK) { + struct tkmap *map = gv.m_tkmap; + const dds_topic *topic = dds_instance_info((dds_entity*)wr); + void *sample = dds_alloc (topic->m_descriptor->m_size); + if (dds_tkmap_get_key (map, handle, sample)) { + ret = dds_dispose_impl(wr, sample, handle, timestamp); + } else { + ret = DDS_ERRNO(DDS_RETCODE_PRECONDITION_NOT_MET, "No instance related with the provided handle is found"); + } + dds_free(sample); + dds_writer_unlock(wr); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking writer"); + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +dds_instance_handle_t +dds_instance_lookup( + dds_entity_t entity, + const void *data) +{ + dds_instance_handle_t ih = DDS_HANDLE_NIL; + const dds_topic * topic; + struct tkmap * map = gv.m_tkmap; + serdata_t sd; + dds_return_t ret; + + DDS_REPORT_STACK(); + + if(data == NULL){ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument data is NULL"); + goto err; + } + + topic = dds_instance_info_by_hdl (entity); + if (topic) { + sd = serialize_key (gv.serpool, topic->m_stopic, data); + ih = dds_tkmap_lookup (map, sd); + ddsi_serdata_unref (sd); + ret = DDS_RETCODE_OK; + } else { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Acquired topic is NULL"); + } +err: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ih; +} + +_Pre_satisfies_(entity & DDS_ENTITY_KIND_MASK) +int +dds_instance_get_key( + dds_entity_t entity, + dds_instance_handle_t inst, + void *data) +{ + dds_return_t ret; + const dds_topic * topic; + struct tkmap * map = gv.m_tkmap; + + DDS_REPORT_STACK(); + + if(data == NULL){ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument data is NULL"); + goto err; + } + + topic = dds_instance_info_by_hdl (entity); + if(topic == NULL){ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Could not find topic related to the given entity"); + goto err; + } + memset (data, 0, topic->m_descriptor->m_size); + + if (dds_tkmap_get_key (map, inst, data)) { + ret = DDS_RETCODE_OK; + } else{ + ret = DDS_ERRNO(DDS_RETCODE_PRECONDITION_NOT_MET, "No instance related with the provided entity is found"); + } + +err: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} diff --git a/src/core/ddsc/src/dds_key.c b/src/core/ddsc/src/dds_key.c new file mode 100644 index 0000000..092821b --- /dev/null +++ b/src/core/ddsc/src/dds_key.c @@ -0,0 +1,166 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include "dds__key.h" +#include "dds__stream.h" +#include "ddsi/ddsi_ser.h" +#include "ddsi/q_bswap.h" +#include "ddsi/q_md5.h" + +void dds_key_md5 (dds_key_hash_t * kh) +{ + md5_state_t md5st; + md5_init (&md5st); + md5_append (&md5st, (md5_byte_t*) kh->m_key_buff, kh->m_key_len); + md5_finish (&md5st, (unsigned char *) kh->m_hash); +} + +/* + dds_key_gen: Generates key and keyhash for a sample. + See section 9.6.3.3 of DDSI spec. +*/ + +void dds_key_gen +( + const dds_topic_descriptor_t * const desc, + dds_key_hash_t * kh, + const char * sample +) +{ + const char * src; + const uint32_t * op; + uint32_t i; + uint32_t len = 0; + char * dst; + + assert (desc->m_nkeys); + assert (kh->m_hash[0] == 0 && kh->m_hash[15] == 0); + + kh->m_flags = DDS_KEY_SET | DDS_KEY_HASH_SET; + + /* Select key buffer to use */ + + if (desc->m_flagset & DDS_TOPIC_FIXED_KEY) + { + kh->m_flags |= DDS_KEY_IS_HASH; + kh->m_key_len = sizeof (kh->m_hash); + dst = kh->m_hash; + } + else + { + /* Calculate key length */ + + for (i = 0; i < desc->m_nkeys; i++) + { + op = desc->m_ops + desc->m_keys[i].m_index; + src = sample + op[1]; + + switch (DDS_OP_TYPE (*op)) + { + case DDS_OP_VAL_1BY: len += 1; break; + case DDS_OP_VAL_2BY: len += 2; break; + case DDS_OP_VAL_4BY: len += 4; break; + case DDS_OP_VAL_8BY: len += 8; break; + case DDS_OP_VAL_STR: src = *((char**) src); /* Fall-through intentional */ + case DDS_OP_VAL_BST: len += (uint32_t) (5 + strlen (src)); break; + case DDS_OP_VAL_ARR: + len += op[2] * dds_op_size[DDS_OP_SUBTYPE (*op)]; + break; + default: assert (0); + } + } + + kh->m_key_len = len; + if (len > kh->m_key_buff_size) + { + kh->m_key_buff = dds_realloc_zero (kh->m_key_buff, len); + kh->m_key_buff_size = len; + } + dst = kh->m_key_buff; + } + + /* Write keys to buffer (Big Endian CDR encoded with no padding) */ + + for (i = 0; i < desc->m_nkeys; i++) + { + op = desc->m_ops + desc->m_keys[i].m_index; + src = sample + op[1]; + assert ((*op & DDS_OP_FLAG_KEY) && ((DDS_OP_MASK & *op) == DDS_OP_ADR)); + + switch (DDS_OP_TYPE (*op)) + { + case DDS_OP_VAL_1BY: + { + *dst = *src; + dst++; + break; + } + case DDS_OP_VAL_2BY: + { + uint16_t u16 = toBE2u (*((const uint16_t*) src)); + memcpy (dst, &u16, sizeof (u16)); + dst += sizeof (u16); + break; + } + case DDS_OP_VAL_4BY: + { + uint32_t u32 = toBE4u (*((const uint32_t*) src)); + memcpy (dst, &u32, sizeof (u32)); + dst += sizeof (u32); + break; + } + case DDS_OP_VAL_8BY: + { + uint64_t u64 = toBE8u (*((const uint64_t*) src)); + memcpy (dst, &u64, sizeof (u64)); + dst += sizeof (u64); + break; + } + case DDS_OP_VAL_STR: + { + src = *((char**) src); + } /* Fall-through intentional */ + case DDS_OP_VAL_BST: + { + uint32_t u32; + len = (uint32_t) (strlen (src) + 1); + u32 = toBE4u (len); + memcpy (dst, &u32, sizeof (u32)); + dst += sizeof (u32); + memcpy (dst, src, len); + dst += len; + break; + } + case DDS_OP_VAL_ARR: + { + uint32_t size = dds_op_size[DDS_OP_SUBTYPE (*op)]; + len = size * op[2]; + memcpy (dst, src, len); + if (dds_stream_endian () && (size != 1u)) + { + dds_stream_swap (dst, size, op[2]); + } + dst += len; + break; + } + default: assert (0); + } + } + + /* Hash is md5 of key */ + + if ((kh->m_flags & DDS_KEY_IS_HASH) == 0) + { + dds_key_md5 (kh); + } +} diff --git a/src/core/ddsc/src/dds_listener.c b/src/core/ddsc/src/dds_listener.c new file mode 100644 index 0000000..d0f40ac --- /dev/null +++ b/src/core/ddsc/src/dds_listener.c @@ -0,0 +1,462 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include "ddsc/dds.h" +#include "dds__listener.h" +#include "dds__report.h" + + + +_Ret_notnull_ +dds_listener_t* +dds_listener_create(_In_opt_ void* arg) +{ + c_listener_t *l = dds_alloc(sizeof(*l)); + dds_listener_reset(l); + l->arg = arg; + return l; +} + +void +dds_listener_delete(_In_ _Post_invalid_ dds_listener_t * __restrict listener) +{ + if (listener) { + dds_free(listener); + } +} + + +void +dds_listener_reset(_Out_ dds_listener_t * __restrict listener) +{ + if (listener) { + c_listener_t *l = listener; + l->on_data_available = DDS_LUNSET; + l->on_data_on_readers = DDS_LUNSET; + l->on_inconsistent_topic = DDS_LUNSET; + l->on_liveliness_changed = DDS_LUNSET; + l->on_liveliness_lost = DDS_LUNSET; + l->on_offered_deadline_missed = DDS_LUNSET; + l->on_offered_incompatible_qos = DDS_LUNSET; + l->on_publication_matched = DDS_LUNSET; + l->on_requested_deadline_missed = DDS_LUNSET; + l->on_requested_incompatible_qos = DDS_LUNSET; + l->on_sample_lost = DDS_LUNSET; + l->on_sample_rejected = DDS_LUNSET; + l->on_subscription_matched = DDS_LUNSET; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + } +} + +void +dds_listener_copy(_Out_ dds_listener_t * __restrict dst, _In_ const dds_listener_t * __restrict src) +{ + const c_listener_t *srcl = src; + c_listener_t *dstl = dst; + + if(!src){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument source(src) is NULL"); + return ; + } + if(!dst){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument destination(dst) is NULL"); + return ; + } + dstl->on_data_available = srcl->on_data_available; + dstl->on_data_on_readers = srcl->on_data_on_readers; + dstl->on_inconsistent_topic = srcl->on_inconsistent_topic; + dstl->on_liveliness_changed = srcl->on_liveliness_changed; + dstl->on_liveliness_lost = srcl->on_liveliness_lost; + dstl->on_offered_deadline_missed = srcl->on_offered_deadline_missed; + dstl->on_offered_incompatible_qos = srcl->on_offered_incompatible_qos; + dstl->on_publication_matched = srcl->on_publication_matched; + dstl->on_requested_deadline_missed = srcl->on_requested_deadline_missed; + dstl->on_requested_incompatible_qos = srcl->on_requested_incompatible_qos; + dstl->on_sample_lost = srcl->on_sample_lost; + dstl->on_sample_rejected = srcl->on_sample_rejected; + dstl->on_subscription_matched = srcl->on_subscription_matched; +} + +void +dds_listener_merge (_Inout_ dds_listener_t * __restrict dst, _In_ const dds_listener_t * __restrict src) +{ + const c_listener_t *srcl = src; + c_listener_t *dstl = dst; + + if(!src){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument source(src) is NULL"); + return ; + } + if(!dst){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument destination(dst) is NULL"); + return ; + } + if (dstl->on_data_available == DDS_LUNSET) { + dstl->on_data_available = srcl->on_data_available; + } + if (dstl->on_data_on_readers == DDS_LUNSET) { + dstl->on_data_on_readers = srcl->on_data_on_readers; + } + if (dstl->on_inconsistent_topic == DDS_LUNSET) { + dstl->on_inconsistent_topic = srcl->on_inconsistent_topic; + } + if (dstl->on_liveliness_changed == DDS_LUNSET) { + dstl->on_liveliness_changed = srcl->on_liveliness_changed; + } + if (dstl->on_liveliness_lost == DDS_LUNSET) { + dstl->on_liveliness_lost = srcl->on_liveliness_lost; + } + if (dstl->on_offered_deadline_missed == DDS_LUNSET) { + dstl->on_offered_deadline_missed = srcl->on_offered_deadline_missed; + } + if (dstl->on_offered_incompatible_qos == DDS_LUNSET) { + dstl->on_offered_incompatible_qos = srcl->on_offered_incompatible_qos; + } + if (dstl->on_publication_matched == DDS_LUNSET) { + dstl->on_publication_matched = srcl->on_publication_matched; + } + if (dstl->on_requested_deadline_missed == DDS_LUNSET) { + dstl->on_requested_deadline_missed = srcl->on_requested_deadline_missed; + } + if (dstl->on_requested_incompatible_qos == DDS_LUNSET) { + dstl->on_requested_incompatible_qos = srcl->on_requested_incompatible_qos; + } + if (dstl->on_sample_lost == DDS_LUNSET) { + dstl->on_sample_lost = srcl->on_sample_lost; + } + if (dstl->on_sample_rejected == DDS_LUNSET) { + dstl->on_sample_rejected = srcl->on_sample_rejected; + } + if (dstl->on_subscription_matched == DDS_LUNSET) { + dstl->on_subscription_matched = srcl->on_subscription_matched; + } +} + +/************************************************************************************************ + * Setters + ************************************************************************************************/ + +void +dds_lset_data_available (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_data_available_fn callback) +{ + if (listener) { + ((c_listener_t*)listener)->on_data_available = callback; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + } +} + +void +dds_lset_data_on_readers (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_data_on_readers_fn callback) +{ + if (listener) { + ((c_listener_t*)listener)->on_data_on_readers = callback; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + } +} + +void +dds_lset_inconsistent_topic (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_inconsistent_topic_fn callback) +{ + if (listener) { + ((c_listener_t*)listener)->on_inconsistent_topic = callback; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + } +} + +void +dds_lset_liveliness_changed (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_liveliness_changed_fn callback) +{ + if (listener) { + ((c_listener_t*)listener)->on_liveliness_changed = callback; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + } +} + +void +dds_lset_liveliness_lost (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_liveliness_lost_fn callback) +{ + if (listener) { + ((c_listener_t*)listener)->on_liveliness_lost = callback; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + } +} + +void +dds_lset_offered_deadline_missed (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_offered_deadline_missed_fn callback) +{ + if (listener) { + ((c_listener_t*)listener)->on_offered_deadline_missed = callback; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + } +} + +void +dds_lset_offered_incompatible_qos (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_offered_incompatible_qos_fn callback) +{ + if (listener) { + ((c_listener_t*)listener)->on_offered_incompatible_qos = callback; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + } +} + +void +dds_lset_publication_matched (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_publication_matched_fn callback) +{ + if (listener) { + ((c_listener_t*)listener)->on_publication_matched = callback; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + } +} + +void +dds_lset_requested_deadline_missed (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_requested_deadline_missed_fn callback) +{ + if (listener) { + ((c_listener_t*)listener)->on_requested_deadline_missed = callback; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + } +} + +void +dds_lset_requested_incompatible_qos (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_requested_incompatible_qos_fn callback) +{ + if (listener) { + ((c_listener_t*)listener)->on_requested_incompatible_qos = callback; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + } +} + +void +dds_lset_sample_lost (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_sample_lost_fn callback) +{ + if (listener) { + ((c_listener_t*)listener)->on_sample_lost = callback; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + } +} + +void +dds_lset_sample_rejected (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_sample_rejected_fn callback) +{ + if (listener) { + ((c_listener_t*)listener)->on_sample_rejected = callback; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + } +} + +void +dds_lset_subscription_matched (_Inout_ dds_listener_t * __restrict listener, _In_opt_ dds_on_subscription_matched_fn callback) +{ + if (listener) { + ((c_listener_t*)listener)->on_subscription_matched = callback; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + } +} + +/************************************************************************************************ + * Getters + ************************************************************************************************/ + +void +dds_lget_data_available (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_data_available_fn *callback) +{ + if(!callback){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument callback is NULL"); + return ; + } + if (!listener) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + return ; + } + *callback = ((c_listener_t*)listener)->on_data_available; +} + +void +dds_lget_data_on_readers (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_data_on_readers_fn *callback) +{ + if(!callback){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument callback is NULL"); + return ; + } + if (!listener) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + return ; + } + *callback = ((c_listener_t*)listener)->on_data_on_readers; +} + +void dds_lget_inconsistent_topic (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_inconsistent_topic_fn *callback) +{ + if(!callback){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument callback is NULL"); + return ; + } + if (!listener) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + return ; + } + *callback = ((c_listener_t*)listener)->on_inconsistent_topic; +} + +void +dds_lget_liveliness_changed (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_liveliness_changed_fn *callback) +{ + if(!callback){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument callback is NULL"); + return ; + } + if (!listener) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + return ; + } + *callback = ((c_listener_t*)listener)->on_liveliness_changed; +} + +void +dds_lget_liveliness_lost (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_liveliness_lost_fn *callback) +{ + if(!callback){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument callback is NULL"); + return ; + } + if (!listener) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + return ; + } + *callback = ((c_listener_t*)listener)->on_liveliness_lost; +} + +void +dds_lget_offered_deadline_missed (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_offered_deadline_missed_fn *callback) +{ + if(!callback){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument callback is NULL"); + return ; + } + if (!listener) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + return ; + } + *callback = ((c_listener_t*)listener)->on_offered_deadline_missed; +} + +void +dds_lget_offered_incompatible_qos (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_offered_incompatible_qos_fn *callback) +{ + if(!callback){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument callback is NULL"); + return ; + } + if (!listener) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + return ; + } + *callback = ((c_listener_t*)listener)->on_offered_incompatible_qos; +} + +void +dds_lget_publication_matched (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_publication_matched_fn *callback) +{ + if(!callback){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument callback is NULL"); + return ; + } + if (!listener) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + return ; + } + *callback = ((c_listener_t*)listener)->on_publication_matched; +} + +void +dds_lget_requested_deadline_missed (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_requested_deadline_missed_fn *callback) +{ + if(!callback) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument callback is NULL"); + return ; + } + if (!listener) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + return ; + } + *callback = ((c_listener_t*)listener)->on_requested_deadline_missed; +} + +void +dds_lget_requested_incompatible_qos (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_requested_incompatible_qos_fn *callback) +{ + if(!callback) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument callback is NULL"); + return ; + } + if (!listener) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + return ; + } + *callback = ((c_listener_t*)listener)->on_requested_incompatible_qos; +} + +void +dds_lget_sample_lost (_In_ const dds_listener_t *__restrict listener, _Outptr_result_maybenull_ dds_on_sample_lost_fn *callback) +{ + if(!callback) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument callback is NULL"); + return ; + } + if (!listener) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + return ; + } + *callback = ((c_listener_t*)listener)->on_sample_lost; +} + +void +dds_lget_sample_rejected (_In_ const dds_listener_t *__restrict listener, _Outptr_result_maybenull_ dds_on_sample_rejected_fn *callback) +{ + if(!callback) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument callback is NULL"); + return ; + } + if (!listener) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + return ; + } + *callback = ((c_listener_t*)listener)->on_sample_rejected; +} + +void +dds_lget_subscription_matched (_In_ const dds_listener_t * __restrict listener, _Outptr_result_maybenull_ dds_on_subscription_matched_fn *callback) +{ + if(!callback) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument callback is NULL"); + return ; + } + if (!listener) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument listener is NULL"); + return ; + } + *callback = ((c_listener_t*)listener)->on_subscription_matched; +} diff --git a/src/core/ddsc/src/dds_log.c b/src/core/ddsc/src/dds_log.c new file mode 100644 index 0000000..eb94586 --- /dev/null +++ b/src/core/ddsc/src/dds_log.c @@ -0,0 +1,73 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include "ddsc/dds.h" +#include "ddsi/q_log.h" + +#define DDS_FMT_MAX 128 + +void dds_log_info (const char * fmt, ...) +{ + va_list args; + + va_start (args, fmt); + nn_vlog (LC_INFO, fmt, args); + va_end (args); +} + +void dds_log_warn (const char * fmt, ...) +{ + va_list args; + char fmt2 [DDS_FMT_MAX]; + + strcpy (fmt2, " "); + strncat (fmt2, fmt, DDS_FMT_MAX - 11); + fmt2[DDS_FMT_MAX-1] = 0; + fmt = fmt2; + + va_start (args, fmt); + nn_vlog (LC_WARNING, fmt, args); + va_end (args); +} + +void dds_log_error (const char * fmt, ...) +{ + va_list args; + char fmt2 [DDS_FMT_MAX]; + + strcpy (fmt2, " "); + strncat (fmt2, fmt, DDS_FMT_MAX - 9); + fmt2[DDS_FMT_MAX-1] = 0; + fmt = fmt2; + + va_start (args, fmt); + nn_vlog (LC_ERROR, fmt, args); + va_end (args); +} + +void dds_log_fatal (const char * fmt, ...) +{ + va_list args; + char fmt2 [DDS_FMT_MAX]; + + strcpy (fmt2, " "); + strncat (fmt2, fmt, DDS_FMT_MAX - 9); + fmt2[DDS_FMT_MAX-1] = 0; + fmt = fmt2; + + va_start (args, fmt); + nn_vlog (LC_FATAL, fmt, args); + va_end (args); + DDS_FAIL (fmt); +} diff --git a/src/core/ddsc/src/dds_participant.c b/src/core/ddsc/src/dds_participant.c new file mode 100644 index 0000000..a730438 --- /dev/null +++ b/src/core/ddsc/src/dds_participant.c @@ -0,0 +1,289 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include "ddsi/q_entity.h" +#include "ddsi/q_thread.h" +#include "ddsi/q_config.h" +#include "q__osplser.h" +#include "dds__init.h" +#include "dds__qos.h" +#include "dds__domain.h" +#include "dds__participant.h" +#include "dds__err.h" +#include "dds__report.h" + +#define DDS_PARTICIPANT_STATUS_MASK 0 + +/* List of created participants */ + +static dds_entity * dds_pp_head = NULL; + +static dds_return_t +dds_participant_status_validate( + uint32_t mask) +{ + return (mask & ~(DDS_PARTICIPANT_STATUS_MASK)) ? + DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument mask is invalid") : + DDS_RETCODE_OK; +} + +static dds_return_t +dds_participant_delete( + dds_entity *e) +{ + struct thread_state1 * const thr = lookup_thread_state (); + const bool asleep = !vtime_awake_p (thr->vtime); + dds_entity *prev = NULL; + dds_entity *iter; + + assert(e); + assert(thr); + assert(dds_entity_kind(e->m_hdl) == DDS_KIND_PARTICIPANT); + + if (asleep) { + thread_state_awake(thr); + } + + dds_domain_free (e->m_domain); + + os_mutexLock (&dds_global.m_mutex); + iter = dds_pp_head; + while (iter) { + if (iter == e) { + if (prev) { + prev->m_next = iter->m_next; + } else { + dds_pp_head = iter->m_next; + } + break; + } + prev = iter; + iter = iter->m_next; + } + os_mutexUnlock (&dds_global.m_mutex); + + assert (iter); + + if (asleep) { + thread_state_asleep(thr); + } + + /* Every dds_init needs a dds_fini. */ + dds_fini(); + + return DDS_RETCODE_OK; +} + +static dds_return_t +dds_participant_instance_hdl( + dds_entity *e, + dds_instance_handle_t *i) +{ + assert(e); + assert(i); + *i = (dds_instance_handle_t)participant_instance_id(&e->m_guid); + return DDS_RETCODE_OK; +} + +static dds_return_t +dds_participant_qos_validate( + const dds_qos_t *qos, + bool enabled) +{ + dds_return_t ret = DDS_RETCODE_OK; + assert(qos); + + /* Check consistency. */ + if ((qos->present & QP_USER_DATA) && !validate_octetseq(&qos->user_data)) { + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "User data QoS policy is inconsistent and caused an error"); + } + if ((qos->present & QP_PRISMTECH_ENTITY_FACTORY) && !validate_entityfactory_qospolicy(&qos->entity_factory)) { + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Prismtech entity factory QoS policy is inconsistent and caused an error"); + } + return ret; +} + + +static dds_return_t +dds_participant_qos_set( + dds_entity *e, + const dds_qos_t *qos, + bool enabled) +{ + dds_return_t ret = dds_participant_qos_validate(qos, enabled); + if (ret == DDS_RETCODE_OK) { + if (enabled) { + /* TODO: CHAM-95: DDSI does not support changing QoS policies. */ + ret = DDS_ERRNO(DDS_RETCODE_UNSUPPORTED, "Changing the participant QoS is not supported."); + } + } + return ret; +} + +_Must_inspect_result_ dds_entity_t +dds_create_participant( + _In_ const dds_domainid_t domain, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener) +{ + int q_rc; + dds_return_t ret; + dds_entity_t e; + nn_guid_t guid; + dds_participant * pp; + nn_plist_t plist; + dds_qos_t * new_qos = NULL; + struct thread_state1 * thr; + bool asleep; + + /* Be sure the DDS lifecycle resources are initialized. */ + dds__startup(); + + /* Make sure DDS instance is initialized. */ + ret = dds_init(); + if (ret != DDS_RETCODE_OK) { + e = (dds_entity_t)ret; + goto fail_dds_init; + } + + /* Report stack is only useful after dds (and thus os) init. */ + DDS_REPORT_STACK(); + + /* Check domain id */ + ret = dds__check_domain (domain); + if (ret != DDS_RETCODE_OK) { + e = (dds_entity_t)ret; + goto fail_domain_check; + } + + /* Validate qos */ + if (qos) { + ret = dds_participant_qos_validate (qos, false); + if (ret != DDS_RETCODE_OK) { + e = (dds_entity_t)ret; + goto fail_qos_validation; + } + new_qos = dds_qos_create (); + /* Only returns failure when one of the qos args is NULL, which + * is not the case here. */ + (void)dds_qos_copy(new_qos, qos); + } else { + /* Use default qos. */ + new_qos = dds_qos_create (); + } + + /* Translate qos */ + nn_plist_init_empty(&plist); + dds_qos_merge (&plist.qos, new_qos); + + thr = lookup_thread_state (); + asleep = !vtime_awake_p (thr->vtime); + if (asleep) { + thread_state_awake (thr); + } + q_rc = new_participant (&guid, 0, &plist); + if (asleep) { + thread_state_asleep (thr); + } + nn_plist_fini (&plist); + if (q_rc != 0) { + e = DDS_ERRNO(DDS_RETCODE_ERROR, "Internal error"); + goto fail_new_participant; + } + + pp = dds_alloc (sizeof (*pp)); + e = dds_entity_init (&pp->m_entity, NULL, DDS_KIND_PARTICIPANT, new_qos, listener, DDS_PARTICIPANT_STATUS_MASK); + if (e < 0) { + goto fail_entity_init; + } + + pp->m_entity.m_guid = guid; + pp->m_entity.m_domain = dds_domain_create (dds_domain_default()); + pp->m_entity.m_domainid = dds_domain_default(); + pp->m_entity.m_deriver.delete = dds_participant_delete; + pp->m_entity.m_deriver.set_qos = dds_participant_qos_set; + pp->m_entity.m_deriver.get_instance_hdl = dds_participant_instance_hdl; + pp->m_entity.m_deriver.validate_status = dds_participant_status_validate; + pp->m_builtin_subscriber = 0; + + /* Add participant to extent */ + os_mutexLock (&dds_global.m_mutex); + pp->m_entity.m_next = dds_pp_head; + dds_pp_head = &pp->m_entity; + os_mutexUnlock (&dds_global.m_mutex); + + DDS_REPORT_FLUSH(false); + return e; + +fail_entity_init: + dds_free(pp); +fail_new_participant: + dds_qos_delete(new_qos); +fail_qos_validation: +fail_domain_check: + DDS_REPORT_FLUSH(true); + dds_fini(); +fail_dds_init: + return e; +} + +_Check_return_ dds_return_t +dds_lookup_participant( + _In_ dds_domainid_t domain_id, + _Out_opt_ dds_entity_t *participants, + _In_ size_t size) +{ + dds_return_t ret = 0; + + /* Be sure the DDS lifecycle resources are initialized. */ + dds__startup(); + + DDS_REPORT_STACK(); + + if ((participants != NULL) && ((size <= 0) || (size >= INT32_MAX))) { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Array is given, but with invalid size"); + goto err; + } + if ((participants == NULL) && (size != 0)) { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Size is given, but no array"); + goto err; + } + + if(participants){ + participants[0] = 0; + } + + os_mutexLock (&dds__init_mutex); + + /* Check if dds is intialized. */ + if (dds_global.m_init_count > 0) { + dds_entity* iter; + os_mutexLock (&dds_global.m_mutex); + iter = dds_pp_head; + while (iter) { + if(iter->m_domainid == domain_id) { + if((size_t)ret < size) { + participants[ret] = iter->m_hdl; + } + ret++; + } + iter = iter->m_next; + } + os_mutexUnlock (&dds_global.m_mutex); + } + + os_mutexUnlock (&dds__init_mutex); + +err: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} diff --git a/src/core/ddsc/src/dds_publisher.c b/src/core/ddsc/src/dds_publisher.c new file mode 100644 index 0000000..d735862 --- /dev/null +++ b/src/core/ddsc/src/dds_publisher.c @@ -0,0 +1,220 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include "dds__listener.h" +#include "dds__qos.h" +#include "dds__err.h" +#include "ddsi/q_entity.h" +#include "dds__report.h" +#include "ddsc/ddsc_project.h" + +#define DDS_PUBLISHER_STATUS_MASK 0 + +static dds_return_t +dds_publisher_instance_hdl( + dds_entity *e, + dds_instance_handle_t *i) +{ + assert(e); + assert(i); + /* TODO: Get/generate proper handle. */ + return DDS_ERRNO(DDS_RETCODE_UNSUPPORTED, "Getting publisher instance handle is not supported"); +} + +static dds_return_t +dds_publisher_qos_validate( + _In_ const dds_qos_t *qos, + _In_ bool enabled) +{ + dds_return_t ret = DDS_RETCODE_OK; + assert(qos); + + /* Check consistency. */ + if((qos->present & QP_GROUP_DATA) && !validate_octetseq(&qos->group_data)){ + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Group data policy is inconsistent and caused an error"); + } + if((qos->present & QP_PRESENTATION) && (validate_presentation_qospolicy(&qos->presentation) != 0)){ + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Presentation policy is inconsistent and caused an error"); + } + if((qos->present & QP_PARTITION) && !validate_stringseq(&qos->partition)){ + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Partition policy is inconsistent and caused an error"); + } + if((qos->present & QP_PRISMTECH_ENTITY_FACTORY) && !validate_entityfactory_qospolicy(&qos->entity_factory)){ + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Prismtech entity factory policy is inconsistent and caused an error"); + } + if(ret == DDS_RETCODE_OK && enabled && (qos->present & QP_PRESENTATION)){ + /* TODO: Improve/check immutable check. */ + ret = DDS_ERRNO(DDS_RETCODE_IMMUTABLE_POLICY, "Presentation policy is immutable"); + } + return ret; +} + +static dds_return_t +dds_publisher_qos_set( + dds_entity *e, + const dds_qos_t *qos, + bool enabled) +{ + dds_return_t ret = dds_publisher_qos_validate(qos, enabled); + if (ret == DDS_RETCODE_OK) { + if (enabled) { + /* TODO: CHAM-95: DDSI does not support changing QoS policies. */ + ret = DDS_ERRNO(DDS_RETCODE_UNSUPPORTED, DDSC_PROJECT_NAME" does not support changing QoS policies yet"); + } + } + return ret; +} + +static dds_return_t dds_publisher_status_validate (uint32_t mask) +{ + return (mask & ~(DDS_PUBLISHER_STATUS_MASK)) ? + DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Invalid status mask") : + DDS_RETCODE_OK; +} + +_Pre_satisfies_((participant & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) +_Must_inspect_result_ dds_entity_t +dds_create_publisher( + _In_ dds_entity_t participant, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener) +{ + dds_entity * par; + dds_publisher * pub; + dds_entity_t hdl; + dds_qos_t * new_qos = NULL; + dds_return_t ret; + dds__retcode_t rc; + + DDS_REPORT_STACK(); + + rc = dds_entity_lock(participant, DDS_KIND_PARTICIPANT, &par); + if (rc != DDS_RETCODE_OK) { + hdl = DDS_ERRNO(rc, "Error occurred on locking participant"); + goto lock_err; + } + + /* Validate qos */ + if (qos) { + ret = dds_publisher_qos_validate(qos, false); + if (ret != DDS_RETCODE_OK) { + hdl = ret; + goto qos_err; + } + new_qos = dds_qos_create (); + /* Only returns failure when one of the qos args is NULL, which + * is not the case here. */ + (void)dds_qos_copy(new_qos, qos); + } + + /* Create publisher */ + pub = dds_alloc (sizeof (*pub)); + hdl = dds_entity_init (&pub->m_entity, par, DDS_KIND_PUBLISHER, new_qos, listener, DDS_PUBLISHER_STATUS_MASK); + pub->m_entity.m_deriver.set_qos = dds_publisher_qos_set; + pub->m_entity.m_deriver.get_instance_hdl = dds_publisher_instance_hdl; + pub->m_entity.m_deriver.validate_status = dds_publisher_status_validate; + +qos_err: + dds_entity_unlock(par); +lock_err: + DDS_REPORT_FLUSH(hdl <= 0); + return hdl; +} + + +_Pre_satisfies_((publisher & DDS_ENTITY_KIND_MASK) == DDS_KIND_PUBLISHER) +DDS_EXPORT dds_return_t +dds_suspend( + _In_ dds_entity_t publisher) +{ + dds_return_t ret; + + DDS_REPORT_STACK(); + + if(dds_entity_kind(publisher) != DDS_KIND_PUBLISHER) { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Provided entity is not a publisher kind"); + goto err; + } + /* TODO: CHAM-123 Currently unsupported. */ + ret = DDS_ERRNO(DDS_RETCODE_UNSUPPORTED, "Suspend publication operation does not being supported yet"); +err: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + + +_Pre_satisfies_((publisher & DDS_ENTITY_KIND_MASK) == DDS_KIND_PUBLISHER) +dds_return_t +dds_resume( + _In_ dds_entity_t publisher) +{ + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + if(dds_entity_kind(publisher) != DDS_KIND_PUBLISHER) { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER,"Provided entity is not a publisher kind"); + goto err; + } + /* TODO: CHAM-123 Currently unsupported. */ + ret = DDS_ERRNO(DDS_RETCODE_UNSUPPORTED, "Suspend publication operation does not being supported yet"); +err: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + + +_Pre_satisfies_(((publisher_or_writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER ) ||\ + ((publisher_or_writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_PUBLISHER) ) +dds_return_t +dds_wait_for_acks( + _In_ dds_entity_t publisher_or_writer, + _In_ dds_duration_t timeout) +{ + dds_return_t ret; + DDS_REPORT_STACK(); + + /* TODO: CHAM-125 Currently unsupported. */ + OS_UNUSED_ARG(timeout); + + switch(dds_entity_kind(publisher_or_writer)) { + case DDS_KIND_WRITER: + ret = DDS_ERRNO(DDS_RETCODE_UNSUPPORTED, "Wait for acknowledgments on a writer is not being supported yet"); + break; + case DDS_KIND_PUBLISHER: + ret = DDS_ERRNO(DDS_RETCODE_UNSUPPORTED, "Wait for acknowledgments on a publisher is not being supported yet"); + break; + default: + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Provided entity is not a publisher nor a writer"); + break; + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +dds_return_t +dds_publisher_begin_coherent( + _In_ dds_entity_t e) +{ + /* TODO: CHAM-124 Currently unsupported. */ + return DDS_ERRNO(DDS_RETCODE_UNSUPPORTED, "Using coherency to get a coherent data set is not being supported yet"); +} + +dds_return_t +dds_publisher_end_coherent( + _In_ dds_entity_t e) +{ + /* TODO: CHAM-124 Currently unsupported. */ + return DDS_ERRNO(DDS_RETCODE_UNSUPPORTED, "Using coherency to get a coherent data set is not being supported yet"); +} + diff --git a/src/core/ddsc/src/dds_qos.c b/src/core/ddsc/src/dds_qos.c new file mode 100644 index 0000000..805630f --- /dev/null +++ b/src/core/ddsc/src/dds_qos.c @@ -0,0 +1,996 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include "dds__qos.h" +#include "dds__err.h" +#include "ddsi/q_config.h" +#include "dds__report.h" + +/* TODO: dd_duration_t is converted to nn_ddsi_time_t declared in q_time.h + This structure contain seconds and fractions. + Revisit on the conversion as default values are { 0x7fffffff, 0xffffffff } +*/ + +static void +dds_qos_data_copy_in( + _Inout_ nn_octetseq_t * data, + _In_reads_bytes_opt_(sz) const void * __restrict value, + _In_ size_t sz) +{ + if (data->value) { + dds_free (data->value); + data->value = NULL; + } + data->length = (uint32_t) sz; + if (sz && value) { + data->value = dds_alloc (sz); + memcpy (data->value, value, sz); + } +} + +static void +dds_qos_data_copy_out( + _In_ const nn_octetseq_t * data, + _When_(*sz == 0, _At_(*value, _Post_null_)) + _When_(*sz > 0, _At_(*value, _Post_notnull_)) + _Outptr_result_bytebuffer_all_maybenull_(*sz) void ** value, + _Out_ size_t * sz) +{ + if ((*sz = data->length) != 0) { + assert(data->value); + *value = dds_alloc(data->length); + memcpy(*value, data->value, data->length); + } else { + *value = NULL; + } +} + +bool +validate_octetseq( + const nn_octetseq_t* seq) +{ + /* default value is NULL with length 0 */ + return (((seq->length == 0) && (seq->value == NULL)) || (seq->length > 0)); +} + +bool +validate_stringseq( + const nn_stringseq_t* seq) +{ + if (seq->n != 0) { + unsigned i; + for (i = 0; i < seq->n; i++) { + if (!seq->strs[i]) { + break; + } + } + return (seq->n == i); + } else { + return (seq->strs == NULL); + } +} + +bool +validate_entityfactory_qospolicy( + const nn_entity_factory_qospolicy_t * entityfactory) +{ + /* Bools must be 0 or 1, i.e., only the lsb may be set */ + return !(entityfactory->autoenable_created_entities & ~1); +} + +bool +validate_reliability_qospolicy( + const nn_reliability_qospolicy_t * reliability) +{ + return ( + (reliability->kind == NN_BEST_EFFORT_RELIABILITY_QOS || reliability->kind == NN_RELIABLE_RELIABILITY_QOS) && + (validate_duration(&reliability->max_blocking_time) == 0) + ); +} + +bool +validate_deadline_and_timebased_filter( + const nn_duration_t deadline, + const nn_duration_t minimum_separation) +{ + return ( + (validate_duration(&deadline) == 0) && + (validate_duration(&minimum_separation) == 0) && + (nn_from_ddsi_duration(minimum_separation) <= nn_from_ddsi_duration(deadline)) + ); +} + +bool +dds_qos_validate_common ( + const dds_qos_t *qos) +{ + return !( + ((qos->present & QP_DURABILITY) && (validate_durability_qospolicy (&qos->durability) != 0)) || + ((qos->present & QP_DEADLINE) && (validate_duration (&qos->deadline.deadline) != 0)) || + ((qos->present & QP_LATENCY_BUDGET) && (validate_duration (&qos->latency_budget.duration) != 0)) || + ((qos->present & QP_OWNERSHIP) && (validate_ownership_qospolicy (&qos->ownership) != 0)) || + ((qos->present & QP_LIVELINESS) && (validate_liveliness_qospolicy (&qos->liveliness) != 0)) || + ((qos->present & QP_RELIABILITY) && ! validate_reliability_qospolicy (&qos->reliability)) || + ((qos->present & QP_DESTINATION_ORDER) && (validate_destination_order_qospolicy (&qos->destination_order) != 0)) || + ((qos->present & QP_HISTORY) && (validate_history_qospolicy (&qos->history) != 0)) || + ((qos->present & QP_RESOURCE_LIMITS) && (validate_resource_limits_qospolicy (&qos->resource_limits) != 0)) + ); +} + +dds_return_t +dds_qos_validate_mutable_common ( + _In_ const dds_qos_t *qos) +{ + dds_return_t ret = DDS_RETCODE_OK; + + /* TODO: Check whether immutable QoS are changed should actually incorporate change to current QoS */ + if (qos->present & QP_DEADLINE) { + ret = DDS_ERRNO(DDS_RETCODE_IMMUTABLE_POLICY, "Deadline QoS policy caused immutable error"); + } + if (qos->present & QP_OWNERSHIP) { + ret = DDS_ERRNO(DDS_RETCODE_IMMUTABLE_POLICY, "Ownership QoS policy caused immutable error"); + } + if (qos->present & QP_LIVELINESS) { + ret = DDS_ERRNO(DDS_RETCODE_IMMUTABLE_POLICY, "Liveliness QoS policy caused immutable error"); + } + if (qos->present & QP_RELIABILITY) { + ret = DDS_ERRNO(DDS_RETCODE_IMMUTABLE_POLICY, "Reliability QoS policy caused immutable error"); + } + if (qos->present & QP_DESTINATION_ORDER) { + ret = DDS_ERRNO(DDS_RETCODE_IMMUTABLE_POLICY, "Destination order QoS policy caused immutable error"); + } + if (qos->present & QP_HISTORY) { + ret = DDS_ERRNO(DDS_RETCODE_IMMUTABLE_POLICY, "History QoS policy caused immutable error"); + } + if (qos->present & QP_RESOURCE_LIMITS) { + ret = DDS_ERRNO(DDS_RETCODE_IMMUTABLE_POLICY, "Resource limits QoS policy caused immutable error"); + } + + return ret; +} + +static void +dds_qos_init_defaults ( + _Inout_ dds_qos_t * __restrict qos) +{ + assert (qos); + memset (qos, 0, sizeof (*qos)); + qos->durability.kind = (nn_durability_kind_t) DDS_DURABILITY_VOLATILE; + qos->deadline.deadline = nn_to_ddsi_duration (DDS_INFINITY); + qos->durability_service.service_cleanup_delay = nn_to_ddsi_duration (0); + qos->durability_service.history.kind = (nn_history_kind_t) DDS_HISTORY_KEEP_LAST; + qos->durability_service.history.depth = 1; + qos->durability_service.resource_limits.max_samples = DDS_LENGTH_UNLIMITED; + qos->durability_service.resource_limits.max_instances = DDS_LENGTH_UNLIMITED; + qos->durability_service.resource_limits.max_samples_per_instance = DDS_LENGTH_UNLIMITED; + qos->presentation.access_scope = (nn_presentation_access_scope_kind_t) DDS_PRESENTATION_INSTANCE; + qos->latency_budget.duration = nn_to_ddsi_duration (0); + qos->ownership.kind = (nn_ownership_kind_t) DDS_OWNERSHIP_SHARED; + qos->liveliness.kind = (nn_liveliness_kind_t) DDS_LIVELINESS_AUTOMATIC; + qos->liveliness.lease_duration = nn_to_ddsi_duration (DDS_INFINITY); + qos->time_based_filter.minimum_separation = nn_to_ddsi_duration (0); + qos->reliability.kind = (nn_reliability_kind_t) DDS_RELIABILITY_BEST_EFFORT; + qos->reliability.max_blocking_time = nn_to_ddsi_duration (DDS_MSECS (100)); + qos->lifespan.duration = nn_to_ddsi_duration (DDS_INFINITY); + qos->destination_order.kind = (nn_destination_order_kind_t) DDS_DESTINATIONORDER_BY_RECEPTION_TIMESTAMP; + qos->history.kind = (nn_history_kind_t) DDS_HISTORY_KEEP_LAST; + qos->history.depth = 1; + qos->resource_limits.max_samples = DDS_LENGTH_UNLIMITED; + qos->resource_limits.max_instances = DDS_LENGTH_UNLIMITED; + qos->resource_limits.max_samples_per_instance = DDS_LENGTH_UNLIMITED; + qos->writer_data_lifecycle.autodispose_unregistered_instances = true; + qos->reader_data_lifecycle.autopurge_nowriter_samples_delay = nn_to_ddsi_duration (DDS_INFINITY); + qos->reader_data_lifecycle.autopurge_disposed_samples_delay = nn_to_ddsi_duration (DDS_INFINITY); +} + +_Ret_notnull_ +dds_qos_t * dds_qos_create (void) +{ + dds_qos_t *qos = dds_alloc (sizeof (dds_qos_t)); + dds_qos_init_defaults (qos); + return qos; +} + +void +dds_qos_reset( + _Out_ dds_qos_t * __restrict qos) +{ + if (qos) { + nn_xqos_fini (qos); + dds_qos_init_defaults (qos); + } else { + DDS_WARNING(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void +dds_qos_delete( + _In_ _Post_invalid_ dds_qos_t * __restrict qos) +{ + if (qos) { + dds_qos_reset(qos); + dds_free(qos); + } +} + +dds_return_t +dds_qos_copy ( + _Out_ dds_qos_t * __restrict dst, + _In_ const dds_qos_t * __restrict src) +{ + if(!src){ + return DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument source(src) is NULL"); + } + if(!dst){ + return DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument destination(dst) is NULL"); + } + nn_xqos_copy (dst, src); + return DDS_RETCODE_OK; +} + +void dds_qos_merge ( + _Inout_ dds_qos_t * __restrict dst, + _In_ const dds_qos_t * __restrict src) +{ + if(!src){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument source(src) is NULL"); + return ; + } + if(!dst){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument destination(dst) is NULL"); + return ; + } + /* Copy qos from source to destination unless already set */ + nn_xqos_mergein_missing (dst, src); +} + +void dds_qset_userdata( + _Inout_ dds_qos_t * __restrict qos, + _In_reads_bytes_opt_(sz) const void * __restrict value, + _In_ size_t sz) +{ + if (!qos) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + return ; + } + dds_qos_data_copy_in(&qos->user_data, value, sz); + qos->present |= QP_USER_DATA; +} + +void dds_qset_topicdata( + _Inout_ dds_qos_t * __restrict qos, + _In_reads_bytes_opt_(sz) const void * __restrict value, + _In_ size_t sz) +{ + if (!qos) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + return ; + } + dds_qos_data_copy_in (&qos->topic_data, value, sz); + qos->present |= QP_TOPIC_DATA; +} + +void dds_qset_groupdata( + _Inout_ dds_qos_t * __restrict qos, + _In_reads_bytes_opt_(sz) const void * __restrict value, + _In_ size_t sz) +{ + if (!qos) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + return ; + } + dds_qos_data_copy_in (&qos->group_data, value, sz); + qos->present |= QP_GROUP_DATA; +} + +void dds_qset_durability +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(DDS_DURABILITY_VOLATILE, DDS_DURABILITY_PERSISTENT) dds_durability_kind_t kind +) +{ + if (qos) { + qos->durability.kind = (nn_durability_kind_t) kind; + qos->present |= QP_DURABILITY; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qset_history +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(DDS_HISTORY_KEEP_LAST, DDS_HISTORY_KEEP_ALL) dds_history_kind_t kind, + _In_range_(>=, DDS_LENGTH_UNLIMITED) int32_t depth +) +{ + if (qos) { + qos->history.kind = (nn_history_kind_t) kind; + qos->history.depth = depth; + qos->present |= QP_HISTORY; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qset_resource_limits +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(>=, DDS_LENGTH_UNLIMITED) int32_t max_samples, + _In_range_(>=, DDS_LENGTH_UNLIMITED) int32_t max_instances, + _In_range_(>=, DDS_LENGTH_UNLIMITED) int32_t max_samples_per_instance +) +{ + if (qos) { + qos->resource_limits.max_samples = max_samples; + qos->resource_limits.max_instances = max_instances; + qos->resource_limits.max_samples_per_instance = max_samples_per_instance; + qos->present |= QP_RESOURCE_LIMITS; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qset_presentation +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(DDS_PRESENTATION_INSTANCE, DDS_PRESENTATION_GROUP) dds_presentation_access_scope_kind_t access_scope, + _In_ bool coherent_access, + _In_ bool ordered_access +) +{ + if (qos) { + qos->presentation.access_scope = (nn_presentation_access_scope_kind_t) access_scope; + qos->presentation.coherent_access = coherent_access; + qos->presentation.ordered_access = ordered_access; + qos->present |= QP_PRESENTATION; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qset_lifespan +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(0, DDS_INFINITY) dds_duration_t lifespan +) +{ + if (qos) { + qos->lifespan.duration = nn_to_ddsi_duration (lifespan); + qos->present |= QP_LIFESPAN; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qset_deadline +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(0, DDS_INFINITY) dds_duration_t deadline +) +{ + if (qos) { + qos->deadline.deadline = nn_to_ddsi_duration (deadline); + qos->present |= QP_DEADLINE; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qset_latency_budget +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(0, DDS_INFINITY) dds_duration_t duration +) +{ + if (qos) { + qos->latency_budget.duration = nn_to_ddsi_duration (duration); + qos->present |= QP_LATENCY_BUDGET; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qset_ownership +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(DDS_OWNERSHIP_SHARED, DDS_OWNERSHIP_EXCLUSIVE) dds_ownership_kind_t kind +) +{ + if (qos) { + qos->ownership.kind = (nn_ownership_kind_t) kind; + qos->present |= QP_OWNERSHIP; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qset_ownership_strength +( + _Inout_ dds_qos_t * __restrict qos, + _In_ int32_t value +) +{ + if (qos) { + qos->ownership_strength.value = value; + qos->present |= QP_OWNERSHIP_STRENGTH; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qset_liveliness +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(DDS_LIVELINESS_AUTOMATIC, DDS_LIVELINESS_MANUAL_BY_TOPIC) dds_liveliness_kind_t kind, + _In_range_(0, DDS_INFINITY) dds_duration_t lease_duration +) +{ + if (qos) { + qos->liveliness.kind = (nn_liveliness_kind_t) kind; + qos->liveliness.lease_duration = nn_to_ddsi_duration (lease_duration); + qos->present |= QP_LIVELINESS; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qset_time_based_filter +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(0, DDS_INFINITY) dds_duration_t minimum_separation +) +{ + if (qos) { + qos->time_based_filter.minimum_separation = nn_to_ddsi_duration (minimum_separation); + qos->present |= QP_TIME_BASED_FILTER; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qset_partition +( + _Inout_ dds_qos_t * __restrict qos, + _In_ uint32_t n, + _In_count_(n) _Deref_pre_z_ const char ** __restrict ps +) +{ + uint32_t i; + size_t len; + + if(!qos) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos may not be NULL"); + return ; + } + if(n && !ps) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument ps is NULL, but n (%u) > 0", n); + return ; + } + + if (qos->partition.strs != NULL){ + for (i = 0; i < qos->partition.n; i++) { + dds_free(qos->partition.strs[i]); + } + dds_free(qos->partition.strs); + qos->partition.strs = NULL; + } + + qos->partition.n = n; + if(n){ + qos->partition.strs = dds_alloc (sizeof (char*) * n); + } + + for (i = 0; i < n; i++) { + len = strlen (ps[i]) + 1; + qos->partition.strs[i] = dds_alloc (len); + strncpy (qos->partition.strs[i], ps[i], len); + } + qos->present |= QP_PARTITION; +} + +void dds_qset_reliability +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(DDS_RELIABILITY_BEST_EFFORT, DDS_RELIABILITY_RELIABLE) dds_reliability_kind_t kind, + _In_range_(0, DDS_INFINITY) dds_duration_t max_blocking_time +) +{ + if (qos) { + qos->reliability.kind = (nn_reliability_kind_t) kind; + qos->reliability.max_blocking_time = nn_to_ddsi_duration (max_blocking_time); + qos->present |= QP_RELIABILITY; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qset_transport_priority +( + _Inout_ dds_qos_t * __restrict qos, + _In_ int32_t value +) +{ + if (qos) { + qos->transport_priority.value = value; + qos->present |= QP_TRANSPORT_PRIORITY; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qset_destination_order +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(DDS_DESTINATIONORDER_BY_RECEPTION_TIMESTAMP, + DDS_DESTINATIONORDER_BY_SOURCE_TIMESTAMP) dds_destination_order_kind_t kind +) +{ + if (qos) { + qos->destination_order.kind = (nn_destination_order_kind_t) kind; + qos->present |= QP_DESTINATION_ORDER; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qset_writer_data_lifecycle +( + _Inout_ dds_qos_t * __restrict qos, + _In_ bool autodispose +) +{ + if(qos) { + qos->writer_data_lifecycle.autodispose_unregistered_instances = autodispose; + qos->present |= QP_PRISMTECH_WRITER_DATA_LIFECYCLE; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qset_reader_data_lifecycle +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(0, DDS_INFINITY) dds_duration_t autopurge_nowriter_samples_delay, + _In_range_(0, DDS_INFINITY) dds_duration_t autopurge_disposed_samples_delay +) +{ + if (qos) { + qos->reader_data_lifecycle.autopurge_nowriter_samples_delay = \ + nn_to_ddsi_duration (autopurge_nowriter_samples_delay); + qos->reader_data_lifecycle.autopurge_disposed_samples_delay = \ + nn_to_ddsi_duration (autopurge_disposed_samples_delay); + qos->present |= QP_PRISMTECH_READER_DATA_LIFECYCLE; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qset_durability_service +( + _Inout_ dds_qos_t * __restrict qos, + _In_range_(0, DDS_INFINITY) dds_duration_t service_cleanup_delay, + _In_range_(DDS_HISTORY_KEEP_LAST, DDS_HISTORY_KEEP_ALL) dds_history_kind_t history_kind, + _In_range_(>=, DDS_LENGTH_UNLIMITED) int32_t history_depth, + _In_range_(>=, DDS_LENGTH_UNLIMITED) int32_t max_samples, + _In_range_(>=, DDS_LENGTH_UNLIMITED) int32_t max_instances, + _In_range_(>=, DDS_LENGTH_UNLIMITED) int32_t max_samples_per_instance +) +{ + if (qos) { + qos->durability_service.service_cleanup_delay = nn_to_ddsi_duration (service_cleanup_delay); + qos->durability_service.history.kind = (nn_history_kind_t) history_kind; + qos->durability_service.history.depth = history_depth; + qos->durability_service.resource_limits.max_samples = max_samples; + qos->durability_service.resource_limits.max_instances = max_instances; + qos->durability_service.resource_limits.max_samples_per_instance = max_samples_per_instance; + qos->present |= QP_DURABILITY_SERVICE; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + } +} + +void dds_qget_userdata +( + _In_ const dds_qos_t * __restrict qos, + _Outptr_result_bytebuffer_maybenull_(*sz) void ** value, + _Out_ size_t * sz +) +{ + if(!qos) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + return ; + } + if(!value) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument value is NULL"); + return ; + } + if(!sz) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument sz is NULL"); + return ; + } + dds_qos_data_copy_out (&qos->user_data, value, sz); +} + +void dds_qget_topicdata +( + _In_ const dds_qos_t * __restrict qos, + _Outptr_result_bytebuffer_maybenull_(*sz) void ** value, + _Out_ size_t * sz +) +{ + if(!qos) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + return ; + } + if(!value) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument value is NULL"); + return ; + } + if(!sz) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument sz is NULL"); + return ; + } + dds_qos_data_copy_out (&qos->topic_data, value, sz); +} + +void dds_qget_groupdata +( + _In_ const dds_qos_t * __restrict qos, + _Outptr_result_bytebuffer_maybenull_(*sz) void ** value, + _Out_ size_t * sz +) +{ + if(!qos) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + return ; + } + if(!value) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument value is NULL"); + return ; + } + if(!sz) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument sz is NULL"); + return ; + } + dds_qos_data_copy_out (&qos->group_data, value, sz); +} + +void dds_qget_durability +( + _In_ const dds_qos_t * __restrict qos, + _Out_ dds_durability_kind_t *kind +) +{ + if(!qos) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is NULL"); + return ; + } + if(!kind) { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument kind is NULL"); + return ; + } + *kind = (dds_durability_kind_t) qos->durability.kind; +} + +void dds_qget_history +( + _In_ const dds_qos_t * __restrict qos, + _Out_opt_ dds_history_kind_t * kind, + _Out_opt_ int32_t *depth +) +{ + if (qos) { + if (kind) *kind = (dds_history_kind_t) qos->history.kind; + if (depth) *depth = qos->history.depth; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + } +} + +void dds_qget_resource_limits +( + _In_ const dds_qos_t * __restrict qos, + _Out_opt_ int32_t *max_samples, + _Out_opt_ int32_t *max_instances, + _Out_opt_ int32_t *max_samples_per_instance +) +{ + if (qos) { + if (max_samples) *max_samples = qos->resource_limits.max_samples; + if (max_instances) *max_instances = qos->resource_limits.max_instances; + if (max_samples_per_instance) { + *max_samples_per_instance = qos->resource_limits.max_samples_per_instance; + } + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + } +} + +void dds_qget_presentation +( + _In_ const dds_qos_t * __restrict qos, + _Out_opt_ dds_presentation_access_scope_kind_t *access_scope, + _Out_opt_ bool *coherent_access, + _Out_opt_ bool *ordered_access +) +{ + if (qos) { + if (access_scope) *access_scope = (dds_presentation_access_scope_kind_t) qos->presentation.access_scope; + if (coherent_access) *coherent_access = qos->presentation.coherent_access; + if (ordered_access) *ordered_access = qos->presentation.ordered_access; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + } +} + +void dds_qget_lifespan +( + _In_ const dds_qos_t * __restrict qos, + _Out_ dds_duration_t * lifespan +) +{ + if(!qos){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + return ; + } + if(!lifespan){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument lifespan is NULL"); + return ; + } + *lifespan = nn_from_ddsi_duration (qos->lifespan.duration); +} + +void dds_qget_deadline +( + _In_ const dds_qos_t * __restrict qos, + _Out_ dds_duration_t * deadline +) +{ + if(!qos){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + return ; + } + if(!deadline){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument deadline is NULL"); + return ; + } + *deadline = nn_from_ddsi_duration (qos->deadline.deadline); +} + +void dds_qget_latency_budget +( + _In_ const dds_qos_t * __restrict qos, + _Out_ dds_duration_t *duration +) +{ + if(!qos){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + return ; + } + if(!duration){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument duration is NULL"); + return ; + } + *duration = nn_from_ddsi_duration (qos->latency_budget.duration); +} + +void dds_qget_ownership +( + _In_ const dds_qos_t * __restrict qos, + _Out_ dds_ownership_kind_t *kind +) +{ + if(!qos){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + return ; + } + if(!kind){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument kind is NULL"); + return ; + } + *kind = (dds_ownership_kind_t) qos->ownership.kind; +} + +void dds_qget_ownership_strength +( + _In_ const dds_qos_t * __restrict qos, + _Out_ int32_t *value +) +{ + if(!qos){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + return ; + } + if(!value){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument value is NULL"); + return ; + } + *value = qos->ownership_strength.value; +} + +void dds_qget_liveliness +( + _In_ const dds_qos_t * __restrict qos, + _Out_opt_ dds_liveliness_kind_t *kind, + _Out_opt_ dds_duration_t *lease_duration +) +{ + if (qos) { + if (kind) *kind = (dds_liveliness_kind_t) qos->liveliness.kind; + if (lease_duration) *lease_duration = nn_from_ddsi_duration (qos->liveliness.lease_duration); + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + } +} + +void dds_qget_time_based_filter +( + _In_ const dds_qos_t * __restrict qos, + _Out_ dds_duration_t *minimum_separation +) +{ + if(!qos){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + return ; + } + if(!minimum_separation){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument minimum_separation is NULL"); + return ; + } + *minimum_separation = nn_from_ddsi_duration (qos->time_based_filter.minimum_separation); +} + +void dds_qget_partition +( + _In_ const dds_qos_t * __restrict qos, + _Out_ uint32_t *n, + _Outptr_opt_result_buffer_all_maybenull_(*n) char *** ps +) +{ + size_t len; + uint32_t i; + + if(!qos){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + return ; + } + if(!n){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument n is NULL"); + return ; + } + + *n = qos->partition.n; + if ( ps ) { + if ( qos->partition.n != 0 ) { + *ps = dds_alloc(sizeof(char*) * qos->partition.n); + for ( i = 0; i < qos->partition.n; i++ ) { + len = strlen(qos->partition.strs[i]) + 1; + (*ps)[i] = dds_alloc(len); + strncpy((*ps)[i], qos->partition.strs[i], len); + } + } else { + *ps = NULL; + } + } +} + +void dds_qget_reliability +( + _In_ const dds_qos_t * __restrict qos, + _Out_opt_ dds_reliability_kind_t *kind, + _Out_opt_ dds_duration_t *max_blocking_time +) +{ + if (qos) { + if (kind) *kind = (dds_reliability_kind_t) qos->reliability.kind; + if (max_blocking_time) *max_blocking_time = nn_from_ddsi_duration (qos->reliability.max_blocking_time); + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + } +} + +void dds_qget_transport_priority +( + _In_ const dds_qos_t * __restrict qos, + _Out_ int32_t *value +) +{ + if(!qos){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + return ; + } + if(!value){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument value is NULL"); + return ; + } + *value = qos->transport_priority.value; +} + +void dds_qget_destination_order +( + _In_ const dds_qos_t * __restrict qos, + _Out_ dds_destination_order_kind_t *kind +) +{ + if(!qos){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + return ; + } + if(!kind){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument kind is NULL"); + return ; + } + *kind = (dds_destination_order_kind_t) qos->destination_order.kind; +} + +void dds_qget_writer_data_lifecycle +( + _In_ const dds_qos_t * __restrict qos, + _Out_ bool * autodispose +) +{ + if(!qos){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + return ; + } + if(!autodispose){ + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument autodispose is NULL"); + return ; + } + *autodispose = qos->writer_data_lifecycle.autodispose_unregistered_instances; +} + +void dds_qget_reader_data_lifecycle +( + _In_ const dds_qos_t * __restrict qos, + _Out_opt_ dds_duration_t *autopurge_nowriter_samples_delay, + _Out_opt_ dds_duration_t *autopurge_disposed_samples_delay +) +{ + if (qos) { + if (autopurge_nowriter_samples_delay) { + *autopurge_nowriter_samples_delay = \ + nn_from_ddsi_duration (qos->reader_data_lifecycle.autopurge_nowriter_samples_delay); + } + if (autopurge_disposed_samples_delay) { + *autopurge_disposed_samples_delay = \ + nn_from_ddsi_duration (qos->reader_data_lifecycle.autopurge_disposed_samples_delay); + } + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + } +} + +void dds_qget_durability_service +( + _In_ const dds_qos_t * qos, + _Out_opt_ dds_duration_t * service_cleanup_delay, + _Out_opt_ dds_history_kind_t * history_kind, + _Out_opt_ int32_t * history_depth, + _Out_opt_ int32_t * max_samples, + _Out_opt_ int32_t * max_instances, + _Out_opt_ int32_t * max_samples_per_instance +) +{ + if (qos) { + if (service_cleanup_delay) *service_cleanup_delay = nn_from_ddsi_duration (qos->durability_service.service_cleanup_delay); + if (history_kind) *history_kind = (dds_history_kind_t) qos->durability_service.history.kind; + if (history_depth) *history_depth = qos->durability_service.history.depth; + if (max_samples) *max_samples = qos->durability_service.resource_limits.max_samples; + if (max_instances) *max_instances = qos->durability_service.resource_limits.max_instances; + if (max_samples_per_instance) *max_samples_per_instance = qos->durability_service.resource_limits.max_samples_per_instance; + } else { + DDS_ERROR(DDS_RETCODE_BAD_PARAMETER, "Argument qos is NULL"); + } +} diff --git a/src/core/ddsc/src/dds_querycond.c b/src/core/ddsc/src/dds_querycond.c new file mode 100644 index 0000000..fd3da3c --- /dev/null +++ b/src/core/ddsc/src/dds_querycond.c @@ -0,0 +1,60 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include "dds__entity.h" +#include "dds__reader.h" +#include "dds__topic.h" +#include "dds__querycond.h" +#include "dds__readcond.h" +#include "dds__err.h" +#include "ddsi/ddsi_ser.h" +#include "dds__report.h" + +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +DDS_EXPORT dds_entity_t +dds_create_querycondition( + _In_ dds_entity_t reader, + _In_ uint32_t mask, + _In_ dds_querycondition_filter_fn filter) +{ + dds_entity_t topic; + dds_entity_t hdl; + dds__retcode_t rc; + dds_reader *r; + dds_topic *t; + + DDS_REPORT_STACK(); + + rc = dds_reader_lock(reader, &r); + if (rc == DDS_RETCODE_OK) { + dds_readcond *cond = dds_create_readcond(r, DDS_KIND_COND_QUERY, mask); + assert(cond); + hdl = cond->m_entity.m_hdl; + cond->m_query.m_filter = filter; + topic = r->m_topic->m_entity.m_hdl; + dds_reader_unlock(r); + rc = dds_topic_lock(topic, &t); + if (rc == DDS_RETCODE_OK) { + if (t->m_stopic->filter_sample == NULL) { + t->m_stopic->filter_sample = dds_alloc(t->m_descriptor->m_size); + } + dds_topic_unlock(t); + } else { + (void)dds_delete(hdl); + hdl = DDS_ERRNO(rc, "Error occurred on locking topic"); + } + } else { + hdl = DDS_ERRNO(rc, "Error occurred on locking reader"); + } + DDS_REPORT_FLUSH(hdl <= 0); + return hdl; +} diff --git a/src/core/ddsc/src/dds_read.c b/src/core/ddsc/src/dds_read.c new file mode 100644 index 0000000..809ac5e --- /dev/null +++ b/src/core/ddsc/src/dds_read.c @@ -0,0 +1,865 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include "dds__entity.h" +#include "dds__reader.h" +#include "dds__tkmap.h" +#include "dds__rhc.h" +#include "dds__err.h" +#include "ddsi/q_thread.h" +#include "ddsi/q_ephash.h" +#include "ddsi/q_entity.h" +#include "dds__report.h" + + +static _Check_return_ dds__retcode_t +dds_read_lock( + _In_ dds_entity_t hdl, + _Out_ dds_reader **reader, + _Out_ dds_readcond **condition, + _In_ bool only_reader) +{ + dds__retcode_t rc = hdl; + assert(reader); + assert(condition); + *reader = NULL; + *condition = NULL; + + rc = dds_entity_lock(hdl, DDS_KIND_READER, (dds_entity**)reader); + if (rc == DDS_RETCODE_ILLEGAL_OPERATION) { + if (!only_reader) { + if ((dds_entity_kind(hdl) == DDS_KIND_COND_READ ) || (dds_entity_kind(hdl) == DDS_KIND_COND_QUERY) ){ + rc = dds_entity_lock(hdl, DDS_KIND_DONTCARE, (dds_entity**)condition); + if (rc == DDS_RETCODE_OK) { + dds_entity *parent = ((dds_entity*)*condition)->m_parent; + assert(parent); + rc = dds_entity_lock(parent->m_hdl, DDS_KIND_READER, (dds_entity**)reader); + if (rc != DDS_RETCODE_OK) { + dds_entity_unlock((dds_entity*)*condition); + DDS_ERROR(rc, "Failed to lock condition reader."); + } + } else { + DDS_ERROR(rc, "Failed to lock condition."); + } + } else { + DDS_ERROR(rc, "Given entity is not a reader nor a condition."); + } + } else { + DDS_ERROR(rc, "Given entity is not a reader."); + } + } else if (rc != DDS_RETCODE_OK) { + DDS_ERROR(rc, "Failed to lock reader."); + } + return rc; +} + +static void +dds_read_unlock( + _In_ dds_reader *reader, + _In_ dds_readcond *condition) +{ + assert(reader); + dds_entity_unlock((dds_entity*)reader); + if (condition) { + dds_entity_unlock((dds_entity*)condition); + } +} +/* + dds_read_impl: Core read/take function. Usually maxs is size of buf and si + into which samples/status are written, when set to zero is special case + indicating that size set from number of samples in cache and also that cache + has been locked. This is used to support C++ API reading length unlimited + which is interpreted as "all relevant samples in cache". +*/ +static dds_return_t +dds_read_impl( + _In_ bool take, + _In_ dds_entity_t reader_or_condition, + _Inout_ void **buf, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _Out_ dds_sample_info_t *si, + _In_ uint32_t mask, + _In_ dds_instance_handle_t hand, + _In_ bool lock, + _In_ bool only_reader) +{ + uint32_t i; + dds_return_t ret = DDS_RETCODE_OK; + dds__retcode_t rc; + struct dds_reader * rd; + struct dds_readcond * cond; + struct thread_state1 * const thr = lookup_thread_state (); + const bool asleep = !vtime_awake_p (thr->vtime); + + if (asleep) { + thread_state_awake (thr); + } + if (buf == NULL) { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "The provided buffer is NULL"); + goto fail; + } + if (si == NULL) { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Provided pointer to an array of dds_sample_info_t is NULL"); + goto fail; + } + if (maxs == 0) { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "The maximum number of samples to read is zero"); + goto fail; + } + if (bufsz == 0) { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "The size of buffer is zero"); + goto fail; + } + if (bufsz < maxs) { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "The provided size of buffer is smaller than the maximum number of samples to read"); + goto fail; + } + + rc = dds_read_lock(reader_or_condition, &rd, &cond, only_reader); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + goto fail; + } + if (hand != DDS_HANDLE_NIL) { + if (dds_tkmap_find_by_id(gv.m_tkmap, hand) == NULL) { + ret = DDS_ERRNO(DDS_RETCODE_PRECONDITION_NOT_MET, "Could not find instance"); + dds_read_unlock(rd, cond); + goto fail; + } + } + /* Allocate samples if not provided (assuming all or none provided) */ + if (buf[0] == NULL) { + char * loan; + const size_t sz = rd->m_topic->m_descriptor->m_size; + const uint32_t loan_size = (uint32_t) (sz * maxs); + /* Allocate, use or reallocate loan cached on reader */ + if (rd->m_loan_out) { + loan = dds_alloc (loan_size); + } else { + if (rd->m_loan) { + if (rd->m_loan_size < loan_size) { + rd->m_loan = dds_realloc_zero (rd->m_loan, loan_size); + rd->m_loan_size = loan_size; + } + } else { + rd->m_loan = dds_alloc (loan_size); + rd->m_loan_size = loan_size; + } + loan = rd->m_loan; + rd->m_loan_out = true; + } + for (i = 0; i < maxs; i++) { + buf[i] = loan; + loan += sz; + } + } + if (take) { + ret = (dds_return_t)dds_rhc_take(rd->m_rd->rhc, lock, buf, si, maxs, mask, hand, cond); + } else { + ret = (dds_return_t)dds_rhc_read(rd->m_rd->rhc, lock, buf, si, maxs, mask, hand, cond); + } + /* read/take resets data available status */ + dds_entity_status_reset(rd, DDS_DATA_AVAILABLE_STATUS); + /* reset DATA_ON_READERS status on subscriber after successful read/take */ + if (dds_entity_kind(((dds_entity*)rd)->m_parent->m_hdl) == DDS_KIND_SUBSCRIBER) { + dds_entity_status_reset(((dds_entity*)rd)->m_parent, DDS_DATA_ON_READERS_STATUS); + } + dds_read_unlock(rd, cond); + +fail: + if (asleep) { + thread_state_asleep (thr); + } + return ret; +} + +static dds_return_t +dds_readcdr_impl( + _In_ bool take, + _In_ dds_entity_t reader_or_condition, + _Out_ struct serdata ** buf, + _In_ uint32_t maxs, + _Out_ dds_sample_info_t * si, + _In_ uint32_t mask, + _In_ dds_instance_handle_t hand, + _In_ bool lock) +{ + dds_return_t ret = DDS_RETCODE_OK; + dds__retcode_t rc; + struct dds_reader * rd; + struct dds_readcond * cond; + struct thread_state1 * const thr = lookup_thread_state (); + const bool asleep = !vtime_awake_p (thr->vtime); + + assert (take); + assert (buf); + assert (si); + assert (hand == DDS_HANDLE_NIL); + assert (maxs > 0); + + if (asleep) + { + thread_state_awake (thr); + } + rc = dds_read_lock(reader_or_condition, &rd, &cond, false); + if (rc >= DDS_RETCODE_OK) { + ret = dds_rhc_takecdr + ( + rd->m_rd->rhc, lock, buf, si, maxs, + mask & DDS_ANY_SAMPLE_STATE, + mask & DDS_ANY_VIEW_STATE, + mask & DDS_ANY_INSTANCE_STATE, + hand + ); + + /* read/take resets data available status */ + dds_entity_status_reset(rd, DDS_DATA_AVAILABLE_STATUS); + + /* reset DATA_ON_READERS status on subscriber after successful read/take */ + + if (dds_entity_kind(((dds_entity*)rd)->m_parent->m_hdl) == DDS_KIND_SUBSCRIBER) + { + dds_entity_status_reset(((dds_entity*)rd)->m_parent, DDS_DATA_ON_READERS_STATUS); + } + dds_read_unlock(rd, cond); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + + if (asleep) + { + thread_state_asleep (thr); + } + + return ret; +} + +_Pre_satisfies_(((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +dds_return_t +dds_read( + _In_ dds_entity_t rd_or_cnd, + _Inout_ void ** buf, + _Out_ dds_sample_info_t * si, + _In_ size_t bufsz, + _In_ uint32_t maxs) +{ + bool lock = true; + dds_return_t ret; + + DDS_REPORT_STACK(); + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs, so use bufsz instead. + * CHAM-306 will remove this ugly piece of code. */ + maxs = (uint32_t)bufsz; + } + ret = dds_read_impl (false, rd_or_cnd, buf, bufsz, maxs, si, NO_STATE_MASK_SET, DDS_HANDLE_NIL, lock, false); + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + +_Pre_satisfies_(((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +dds_return_t +dds_read_wl( + _In_ dds_entity_t rd_or_cnd, + _Inout_ void ** buf, + _Out_ dds_sample_info_t * si, + _In_ uint32_t maxs) +{ + bool lock = true; + dds_return_t ret; + + DDS_REPORT_STACK(); + + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs. Just an arbitrarily number. + * CHAM-306 will remove this ugly piece of code. */ + maxs = 100; + } + ret = dds_read_impl (false, rd_or_cnd, buf, maxs, maxs, si, NO_STATE_MASK_SET, DDS_HANDLE_NIL, lock, false); + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + +_Pre_satisfies_(((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +dds_return_t +dds_read_mask( + _In_ dds_entity_t rd_or_cnd, + _Inout_ void ** buf, + _Out_ dds_sample_info_t * si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ uint32_t mask) +{ + bool lock = true; + dds_return_t ret; + + DDS_REPORT_STACK(); + + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs, so use bufsz instead. + * CHAM-306 will remove this ugly piece of code. */ + maxs = (uint32_t)bufsz; + } + ret = dds_read_impl (false, rd_or_cnd, buf, bufsz, maxs, si, mask, DDS_HANDLE_NIL, lock, false); + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + +_Pre_satisfies_(((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +dds_return_t +dds_read_mask_wl( + _In_ dds_entity_t rd_or_cnd, + _Inout_ void ** buf, + _Out_ dds_sample_info_t * si, + _In_ uint32_t maxs, + _In_ uint32_t mask) +{ + bool lock = true; + dds_return_t ret; + + DDS_REPORT_STACK(); + + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs. Just an arbitrarily number. + * CHAM-306 will remove this ugly piece of code. */ + maxs = 100; + } + ret = dds_read_impl (false, rd_or_cnd, buf, maxs, maxs, si, mask, DDS_HANDLE_NIL, lock, false); + DDS_REPORT_FLUSH(ret < 0 ); + return ret; +} + +_Pre_satisfies_(((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +dds_return_t +dds_read_instance( + _In_ dds_entity_t rd_or_cnd, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle) +{ + dds_return_t ret = DDS_RETCODE_OK; + bool lock = true; + DDS_REPORT_STACK(); + + if (handle == DDS_HANDLE_NIL) { + ret = DDS_ERRNO(DDS_RETCODE_PRECONDITION_NOT_MET, "DDS_HANDLE_NIL was provided"); + goto fail; + } + + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs. Just an arbitrarily number. + * CHAM-306 will remove this ugly piece of code. */ + maxs = 100; + } + ret = dds_read_impl(false, rd_or_cnd, buf, bufsz, maxs, si, NO_STATE_MASK_SET, handle, lock, false); +fail: + DDS_REPORT_FLUSH(ret < 0 ); + return ret; +} + +_Pre_satisfies_(((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +dds_return_t +dds_read_instance_wl( + _In_ dds_entity_t rd_or_cnd, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle) +{ + dds_return_t ret = DDS_RETCODE_OK; + bool lock = true; + DDS_REPORT_STACK(); + + if (handle == DDS_HANDLE_NIL) { + ret = DDS_ERRNO(DDS_RETCODE_PRECONDITION_NOT_MET, "DDS_HANDLE_NIL was provided"); + goto fail; + } + + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs. Just an arbitrarily number. + * CHAM-306 will remove this ugly piece of code. */ + maxs = 100; + } + ret = dds_read_impl(false, rd_or_cnd, buf, maxs, maxs, si, NO_STATE_MASK_SET, handle, lock, false); +fail: + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + + +_Pre_satisfies_(((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +dds_return_t +dds_read_instance_mask( + _In_ dds_entity_t rd_or_cnd, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle, + _In_ uint32_t mask) +{ + dds_return_t ret = DDS_RETCODE_OK; + bool lock = true; + DDS_REPORT_STACK(); + + if (handle == DDS_HANDLE_NIL) { + ret = DDS_ERRNO(DDS_RETCODE_PRECONDITION_NOT_MET, "DDS_HANDLE_NIL was provided"); + goto fail; + } + + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs. Just an arbitrarily number. + * CHAM-306 will remove this ugly piece of code. */ + maxs = 100; + } + ret = dds_read_impl(false, rd_or_cnd, buf, bufsz, maxs, si, mask, handle, lock, false); +fail: + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + + +_Pre_satisfies_(((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +dds_return_t +dds_read_instance_mask_wl( + _In_ dds_entity_t rd_or_cnd, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle, + _In_ uint32_t mask) +{ + dds_return_t ret = DDS_RETCODE_OK; + bool lock = true; + DDS_REPORT_STACK(); + + if (handle == DDS_HANDLE_NIL) { + ret = DDS_ERRNO(DDS_RETCODE_PRECONDITION_NOT_MET, "DDS_HANDLE_NIL was provided"); + goto fail; + } + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs. Just an arbitrarily number. + * CHAM-306 will remove this ugly piece of code. */ + maxs = 100; + } + ret = dds_read_impl(false, rd_or_cnd, buf, maxs, maxs, si, mask, handle, lock, false); +fail: + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) +dds_return_t +dds_read_next( + _In_ dds_entity_t reader, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + + DDS_REPORT_STACK(); + + ret = dds_read_impl (false, reader, buf, 1u, 1u, si, mask, DDS_HANDLE_NIL, true, true); + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) +dds_return_t +dds_read_next_wl( + _In_ dds_entity_t reader, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + + DDS_REPORT_STACK(); + ret = dds_read_impl (false, reader, buf, 1u, 1u, si, mask, DDS_HANDLE_NIL, true, true); + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_(((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +dds_return_t +dds_take( + _In_ dds_entity_t rd_or_cnd, + _Inout_ void ** buf, + _Out_ dds_sample_info_t * si, + _In_ size_t bufsz, + _In_ uint32_t maxs) +{ + bool lock = true; + dds_return_t ret; + + DDS_REPORT_STACK(); + + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs, so use bufsz instead. + * CHAM-306 will remove this ugly piece of code. */ + maxs = (uint32_t)bufsz; + } + ret = dds_read_impl (true, rd_or_cnd, buf, bufsz, maxs, si, NO_STATE_MASK_SET, DDS_HANDLE_NIL, lock, false); + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + +_Pre_satisfies_(((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +dds_return_t +dds_take_wl( + _In_ dds_entity_t rd_or_cnd, + _Inout_ void ** buf, + _Out_ dds_sample_info_t * si, + _In_ uint32_t maxs) +{ + bool lock = true; + dds_return_t ret; + + DDS_REPORT_STACK(); + + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs. Just an arbitrarily number. + * CHAM-306 will remove this ugly piece of code. */ + maxs = 100; + } + ret = dds_read_impl (true, rd_or_cnd, buf, maxs, maxs, si, NO_STATE_MASK_SET, DDS_HANDLE_NIL, lock, false); + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + +_Pre_satisfies_(((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +dds_return_t +dds_take_mask( + _In_ dds_entity_t rd_or_cnd, + _Inout_ void ** buf, + _Out_ dds_sample_info_t * si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ uint32_t mask) +{ + bool lock = true; + dds_return_t ret; + + DDS_REPORT_STACK(); + + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs, so use bufsz instead. + * CHAM-306 will remove this ugly piece of code. */ + maxs = (uint32_t)bufsz; + } + ret = dds_read_impl (true, rd_or_cnd, buf, bufsz, maxs, si, mask, DDS_HANDLE_NIL, lock, false); + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + +_Pre_satisfies_(((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +dds_return_t +dds_take_mask_wl( + _In_ dds_entity_t rd_or_cnd, + _Inout_ void ** buf, + _Out_ dds_sample_info_t * si, + _In_ uint32_t maxs, + _In_ uint32_t mask) +{ + bool lock = true; + dds_return_t ret; + + DDS_REPORT_STACK(); + + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs. Just an arbitrarily number. + * CHAM-306 will remove this ugly piece of code. */ + maxs = 100; + } + ret = dds_read_impl (true, rd_or_cnd, buf, maxs, maxs, si, mask, DDS_HANDLE_NIL, lock, false); + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + +int +dds_takecdr( + dds_entity_t rd_or_cnd, + struct serdata **buf, + uint32_t maxs, + dds_sample_info_t *si, + uint32_t mask) +{ + bool lock = true; + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs. Just an arbitrarily number. + * CHAM-306 will remove this ugly piece of code. */ + maxs = 100; + } + return dds_readcdr_impl (true, rd_or_cnd, buf, maxs, si, mask, DDS_HANDLE_NIL, lock); +} + + +_Pre_satisfies_(((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +dds_return_t +dds_take_instance( + _In_ dds_entity_t rd_or_cnd, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle) +{ + dds_return_t ret = DDS_RETCODE_OK; + bool lock = true; + + DDS_REPORT_STACK(); + + if (handle == DDS_HANDLE_NIL) { + ret = DDS_ERRNO(DDS_RETCODE_PRECONDITION_NOT_MET, "DDS_HANDLE_NIL was provided"); + goto fail; + } + + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs. Just an arbitrarily number. + * CHAM-306 will remove this ugly piece of code. */ + maxs = 100; + } + ret = dds_read_impl(true, rd_or_cnd, buf, bufsz, maxs, si, NO_STATE_MASK_SET, handle, lock, false); +fail: + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + +_Pre_satisfies_(((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +dds_return_t +dds_take_instance_wl( + _In_ dds_entity_t rd_or_cnd, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle) +{ + dds_return_t ret = DDS_RETCODE_OK; + bool lock = true; + + DDS_REPORT_STACK(); + + if (handle == DDS_HANDLE_NIL) { + ret = DDS_ERRNO(DDS_RETCODE_PRECONDITION_NOT_MET, "DDS_HANDLE_NIL was provided"); + goto fail; + } + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs. Just an arbitrarily number. + * CHAM-306 will remove this ugly piece of code. */ + maxs = 100; + } + ret = dds_read_impl(true, rd_or_cnd, buf, maxs, maxs, si, NO_STATE_MASK_SET, handle, lock, false); +fail: + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + + +_Pre_satisfies_(((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +dds_return_t +dds_take_instance_mask( + _In_ dds_entity_t rd_or_cnd, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ size_t bufsz, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle, + _In_ uint32_t mask) +{ + dds_return_t ret = DDS_RETCODE_OK; + bool lock = true; + + DDS_REPORT_STACK(); + + if (handle == DDS_HANDLE_NIL) { + ret = DDS_ERRNO(DDS_RETCODE_PRECONDITION_NOT_MET, "DDS_HANDLE_NIL was provided"); + goto fail; + } + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs. Just an arbitrarily number. + * CHAM-306 will remove this ugly piece of code. */ + maxs = 100; + } + ret = dds_read_impl(true, rd_or_cnd, buf, bufsz, maxs, si, mask, handle, lock, false); +fail: + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + + +_Pre_satisfies_(((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((rd_or_cnd & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +dds_return_t +dds_take_instance_mask_wl( + _In_ dds_entity_t rd_or_cnd, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si, + _In_ uint32_t maxs, + _In_ dds_instance_handle_t handle, + _In_ uint32_t mask) +{ + dds_return_t ret = DDS_RETCODE_OK; + bool lock = true; + + DDS_REPORT_STACK(); + + if (handle == DDS_HANDLE_NIL) { + ret = DDS_ERRNO(DDS_RETCODE_PRECONDITION_NOT_MET, "DDS_HANDLE_NIL was provided"); + goto fail; + } + if (maxs == DDS_READ_WITHOUT_LOCK) { + lock = false; + /* Use a more sensible maxs. Just an arbitrarily number. + * CHAM-306 will remove this ugly piece of code. */ + maxs = 100; + } + ret = dds_read_impl(true, rd_or_cnd, buf, maxs, maxs, si, mask, handle, lock, false); +fail: + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) +dds_return_t +dds_take_next( + _In_ dds_entity_t reader, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + DDS_REPORT_STACK(); + ret = dds_read_impl (true, reader, buf, 1u, 1u, si, mask, DDS_HANDLE_NIL, true, true); + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) +dds_return_t +dds_take_next_wl( + _In_ dds_entity_t reader, + _Inout_ void **buf, + _Out_ dds_sample_info_t *si) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + DDS_REPORT_STACK(); + ret = dds_read_impl (true, reader, buf, 1u, 1u, si, mask, DDS_HANDLE_NIL, true, true); + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_(((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) ||\ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((reader_or_condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY )) +_Must_inspect_result_ dds_return_t +dds_return_loan( + _In_ dds_entity_t reader_or_condition, + _Inout_updates_(bufsz) void **buf, + _In_ size_t bufsz) +{ + dds__retcode_t rc; + const dds_topic_descriptor_t * desc; + dds_reader *rd; + dds_readcond *cond; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + if (!buf ) { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument buf is NULL"); + goto fail; + } + if(*buf == NULL && bufsz > 0){ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument buf is NULL"); + goto fail; + } + + rc = dds_read_lock(reader_or_condition, &rd, &cond, false); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + goto fail; + } + desc = rd->m_topic->m_descriptor; + + /* Only free sample contents if they have been allocated */ + if (desc->m_flagset & DDS_TOPIC_NO_OPTIMIZE) { + size_t i = 0; + for (i = 0; i < bufsz; i++) { + dds_sample_free(buf[i], desc, DDS_FREE_CONTENTS); + } + } + + /* If possible return loan buffer to reader */ + if (rd->m_loan != 0 && (buf[0] == rd->m_loan)) { + rd->m_loan_out = false; + memset (rd->m_loan, 0, rd->m_loan_size); + buf[0] = NULL; + } + + dds_read_unlock(rd, cond); +fail: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} diff --git a/src/core/ddsc/src/dds_readcond.c b/src/core/ddsc/src/dds_readcond.c new file mode 100644 index 0000000..5706d7e --- /dev/null +++ b/src/core/ddsc/src/dds_readcond.c @@ -0,0 +1,129 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include "dds__reader.h" +#include "dds__readcond.h" +#include "dds__rhc.h" +#include "dds__entity.h" +#include "dds__err.h" +#include "ddsi/q_ephash.h" +#include "ddsi/q_entity.h" +#include "ddsi/q_thread.h" +#include "dds__report.h" + +static dds_return_t +dds_readcond_delete( + dds_entity *e) +{ + dds_rhc_remove_readcondition((dds_readcond*)e); + return DDS_RETCODE_OK; +} + +_Must_inspect_result_ dds_readcond* +dds_create_readcond( + _In_ dds_reader *rd, + _In_ dds_entity_kind_t kind, + _In_ uint32_t mask) +{ + dds_readcond * cond = dds_alloc(sizeof(*cond)); + assert(kind == DDS_KIND_COND_READ || kind == DDS_KIND_COND_QUERY); + cond->m_entity.m_hdl = dds_entity_init(&cond->m_entity, (dds_entity*)rd, kind, NULL, NULL, 0); + cond->m_entity.m_deriver.delete = dds_readcond_delete; + cond->m_rhc = rd->m_rd->rhc; + cond->m_sample_states = mask & DDS_ANY_SAMPLE_STATE; + cond->m_view_states = mask & DDS_ANY_VIEW_STATE; + cond->m_instance_states = mask & DDS_ANY_INSTANCE_STATE; + cond->m_rd_guid = ((dds_entity*)rd)->m_guid; + dds_rhc_add_readcondition (cond); + return cond; +} + +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +_Must_inspect_result_ dds_entity_t +dds_create_readcondition( + _In_ dds_entity_t reader, + _In_ uint32_t mask) +{ + dds_entity_t hdl; + dds_reader * rd; + dds__retcode_t rc; + + DDS_REPORT_STACK(); + + rc = dds_reader_lock(reader, &rd); + if (rc == DDS_RETCODE_OK) { + dds_readcond *cond = dds_create_readcond(rd, DDS_KIND_COND_READ, mask); + assert(cond); + hdl = cond->m_entity.m_hdl; + dds_reader_unlock(rd); + } else { + hdl = DDS_ERRNO(rc, "Error occurred on locking reader"); + } + DDS_REPORT_FLUSH(hdl <= 0); + return hdl; +} + +_Pre_satisfies_(((condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY) ) +dds_entity_t +dds_get_datareader( + _In_ dds_entity_t condition) +{ + dds_entity_t hdl; + + DDS_REPORT_STACK(); + if (dds_entity_kind(condition) == DDS_KIND_COND_READ) { + hdl = dds_get_parent(condition); + } else if (dds_entity_kind(condition) == DDS_KIND_COND_QUERY) { + hdl = dds_get_parent(condition); + } else { + hdl = DDS_ERRNO(dds_valid_hdl(condition, DDS_KIND_COND_READ), "Argument condition is not valid"); + } + DDS_REPORT_FLUSH(hdl <= 0); + return hdl; +} + + +_Pre_satisfies_(((condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((condition & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY) ) +_Check_return_ dds_return_t +dds_get_mask( + _In_ dds_entity_t condition, + _Out_ uint32_t *mask) +{ + dds_return_t ret; + dds_readcond *cond; + dds__retcode_t rc; + + DDS_REPORT_STACK(); + + if (mask != NULL) { + *mask = 0; + if ((dds_entity_kind(condition) == DDS_KIND_COND_READ ) || + (dds_entity_kind(condition) == DDS_KIND_COND_QUERY) ){ + rc = dds_entity_lock(condition, DDS_KIND_DONTCARE, (dds_entity**)&cond); + if (rc == DDS_RETCODE_OK) { + *mask = (cond->m_sample_states | cond->m_view_states | cond->m_instance_states); + dds_entity_unlock((dds_entity*)cond); + ret = DDS_RETCODE_OK; + } else{ + ret = DDS_ERRNO(rc, "Error occurred on locking condition"); + } + } else { + ret = DDS_ERRNO(dds_valid_hdl(condition, DDS_KIND_COND_READ), "Argument condition is not valid"); + } + } else { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument mask is NULL"); + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} diff --git a/src/core/ddsc/src/dds_reader.c b/src/core/ddsc/src/dds_reader.c new file mode 100644 index 0000000..799a3ce --- /dev/null +++ b/src/core/ddsc/src/dds_reader.c @@ -0,0 +1,791 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include "ddsc/dds.h" +#include "dds__subscriber.h" +#include "dds__reader.h" +#include "dds__listener.h" +#include "dds__qos.h" +#include "dds__init.h" +#include "dds__rhc.h" +#include "dds__err.h" +#include "ddsi/q_entity.h" +#include "ddsi/q_thread.h" +#include "dds__report.h" +#include "dds__builtin.h" +#include "ddsc/ddsc_project.h" + +#include +#include "os/os.h" + + +#define DDS_READER_STATUS_MASK \ + DDS_SAMPLE_REJECTED_STATUS |\ + DDS_LIVELINESS_CHANGED_STATUS |\ + DDS_REQUESTED_DEADLINE_MISSED_STATUS |\ + DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS |\ + DDS_DATA_AVAILABLE_STATUS |\ + DDS_SAMPLE_LOST_STATUS |\ + DDS_SUBSCRIPTION_MATCHED_STATUS + +static dds_return_t +dds_reader_instance_hdl( + dds_entity *e, + dds_instance_handle_t *i) +{ + assert(e); + assert(i); + *i = (dds_instance_handle_t)reader_instance_id(&e->m_guid); + return DDS_RETCODE_OK; +} + +static dds_return_t +dds_reader_close( + dds_entity *e) +{ + dds__retcode_t rc; + dds_return_t ret = DDS_RETCODE_OK; + struct thread_state1 * const thr = lookup_thread_state(); + const bool asleep = !vtime_awake_p(thr->vtime); + + assert(e); + assert(thr); + + if (asleep) { + thread_state_awake(thr); + } + if (delete_reader(&e->m_guid) != 0) { + rc = DDS_RETCODE_ERROR; + ret = DDS_ERRNO(rc, "Internal error"); + } + if (asleep) { + thread_state_asleep(thr); + } + return ret; +} + +static dds_return_t +dds_reader_delete( + dds_entity *e) +{ + dds_reader *rd = (dds_reader*)e; + dds_return_t ret; + assert(e); + ret = dds_delete(rd->m_topic->m_entity.m_hdl); + if(ret == DDS_RETCODE_OK){ + ret = dds_delete_impl(e->m_parent->m_hdl, true); + if(dds_err_nr(ret) == DDS_RETCODE_ALREADY_DELETED){ + ret = DDS_RETCODE_OK; + } + } + dds_free(rd->m_loan); + return ret; +} + +static dds_return_t +dds_reader_qos_validate( + const dds_qos_t *qos, + bool enabled) +{ + dds_return_t ret = DDS_RETCODE_OK; + + assert(qos); + + /* Check consistency. */ + if(!dds_qos_validate_common(qos)) { + ret = DDS_ERRNO(DDS_RETCODE_ERROR, "Argument Qos is not valid"); + } + if((qos->present & QP_USER_DATA) && !(validate_octetseq (&qos->user_data))) { + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "User data policy is inconsistent and caused an error"); + } + if((qos->present & QP_PRISMTECH_READER_DATA_LIFECYCLE) && (validate_reader_data_lifecycle (&qos->reader_data_lifecycle) != 0)) { + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Prismtech reader data lifecycle policy is inconsistent and caused an error"); + } + if((qos->present & QP_TIME_BASED_FILTER) && (validate_duration (&qos->time_based_filter.minimum_separation) != 0)) { + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Time based filter policy is inconsistent and caused an error"); + } + if((qos->present & QP_HISTORY) && (qos->present & QP_RESOURCE_LIMITS) && (validate_history_and_resource_limits (&qos->history, &qos->resource_limits) != 0)) { + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "History and resource limits policy is inconsistent and caused an error"); + } + if((qos->present & QP_TIME_BASED_FILTER) && (qos->present & QP_DEADLINE) && !(validate_deadline_and_timebased_filter (qos->deadline.deadline, qos->time_based_filter.minimum_separation))) { + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Time based filter and deadline policy is inconsistent and caused an error"); + } + if(ret == DDS_RETCODE_OK && enabled) { + ret = dds_qos_validate_mutable_common(qos); + } + + return ret; +} + +static dds_return_t +dds_reader_qos_set( + dds_entity *e, + const dds_qos_t *qos, + bool enabled) +{ + dds_return_t ret = dds_reader_qos_validate(qos, enabled); + if (ret == DDS_RETCODE_OK) { + if (enabled) { + /* TODO: CHAM-95: DDSI does not support changing QoS policies. */ + ret = DDS_ERRNO(DDS_RETCODE_UNSUPPORTED, DDSC_PROJECT_NAME" does not support changing QoS policies"); + } + } + return ret; +} + +static dds_return_t +dds_reader_status_validate( + uint32_t mask) +{ + return (mask & ~(DDS_READER_STATUS_MASK)) ? + DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Invalid status mask") : + DDS_RETCODE_OK; +} + +void +dds_reader_status_cb( + void *entity, + const status_cb_data_t *data) +{ + dds_reader *rd; + dds__retcode_t rc; + void *metrics = NULL; + + DDS_REPORT_STACK(); + + /* When data is NULL, it means that the DDSI reader is deleted. */ + if (data == NULL) { + /* Release the initial claim that was done during the create. This + * will indicate that further API deletion is now possible. */ + ut_handle_release(((dds_entity*)entity)->m_hdl, ((dds_entity*)entity)->m_hdllink); + return; + } + + if (dds_reader_lock(((dds_entity*)entity)->m_hdl, &rd) != DDS_RETCODE_OK) { + /* There's a deletion or closing going on. */ + DDS_REPORT_FLUSH(false); + return; + } + assert(rd == entity); + + /* Reset the status for possible Listener call. + * When a listener is not called, the status will be set (again). */ + dds_entity_status_reset(entity, data->status); + + /* Update status metrics. */ + switch (data->status) { + case DDS_REQUESTED_DEADLINE_MISSED_STATUS: { + rd->m_requested_deadline_missed_status.total_count++; + rd->m_requested_deadline_missed_status.total_count_change++; + rd->m_requested_deadline_missed_status.last_instance_handle = data->handle; + metrics = (void*)&(rd->m_requested_deadline_missed_status); + break; + } + case DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS: { + rd->m_requested_incompatible_qos_status.total_count++; + rd->m_requested_incompatible_qos_status.total_count_change++; + rd->m_requested_incompatible_qos_status.last_policy_id = data->extra; + metrics = (void*)&(rd->m_requested_incompatible_qos_status); + break; + } + case DDS_SAMPLE_LOST_STATUS: { + rd->m_sample_lost_status.total_count++; + rd->m_sample_lost_status.total_count_change++; + metrics = (void*)&(rd->m_sample_lost_status); + break; + } + case DDS_SAMPLE_REJECTED_STATUS: { + rd->m_sample_rejected_status.total_count++; + rd->m_sample_rejected_status.total_count_change++; + rd->m_sample_rejected_status.last_reason = data->extra; + rd->m_sample_rejected_status.last_instance_handle = data->handle; + metrics = (void*)&(rd->m_sample_rejected_status); + break; + } + case DDS_DATA_AVAILABLE_STATUS: { + metrics = NULL; + break; + } + case DDS_LIVELINESS_CHANGED_STATUS: { + if (data->add) { + rd->m_liveliness_changed_status.alive_count++; + rd->m_liveliness_changed_status.alive_count_change++; + if (rd->m_liveliness_changed_status.not_alive_count > 0) { + rd->m_liveliness_changed_status.not_alive_count--; + } + } else { + rd->m_liveliness_changed_status.alive_count--; + rd->m_liveliness_changed_status.not_alive_count++; + rd->m_liveliness_changed_status.not_alive_count_change++; + } + rd->m_liveliness_changed_status.last_publication_handle = data->handle; + metrics = (void*)&(rd->m_liveliness_changed_status); + break; + } + case DDS_SUBSCRIPTION_MATCHED_STATUS: { + if (data->add) { + rd->m_subscription_matched_status.total_count++; + rd->m_subscription_matched_status.total_count_change++; + rd->m_subscription_matched_status.current_count++; + rd->m_subscription_matched_status.current_count_change++; + } else { + rd->m_subscription_matched_status.current_count--; + rd->m_subscription_matched_status.current_count_change--; + } + rd->m_subscription_matched_status.last_publication_handle = data->handle; + metrics = (void*)&(rd->m_subscription_matched_status); + break; + } + default: assert (0); + } + + /* The reader needs to be unlocked when propagating the (possible) listener + * call because the application should be able to call this reader within + * the callback function. */ + dds_reader_unlock(rd); + + /* DATA_AVAILABLE is handled differently to normal status changes. */ + if (data->status == DDS_DATA_AVAILABLE_STATUS) { + dds_entity *parent = rd->m_entity.m_parent; + /* First, try to ship it off to its parent(s) DDS_DATA_ON_READERS_STATUS. */ + rc = dds_entity_listener_propagation(parent, parent, DDS_DATA_ON_READERS_STATUS, NULL, true); + + if (rc == DDS_RETCODE_NO_DATA) { + /* No parent was interested (NO_DATA == NO_CALL). + * What about myself with DDS_DATA_AVAILABLE_STATUS? */ + rc = dds_entity_listener_propagation(entity, entity, DDS_DATA_AVAILABLE_STATUS, NULL, false); + } + + if ( rc == DDS_RETCODE_NO_DATA ) { + /* Nobody was interested (NO_DATA == NO_CALL). Set the status on the subscriber. */ + dds_entity_status_set(parent, DDS_DATA_ON_READERS_STATUS); + /* Notify possible interested observers of the subscriber. */ + dds_entity_status_signal(parent); + } + } else { + /* Is anybody interested within the entity hierarchy through listeners? */ + rc = dds_entity_listener_propagation(entity, entity, data->status, metrics, true); + } + + if (rc == DDS_RETCODE_OK) { + /* Event was eaten by a listener. */ + if (dds_reader_lock(((dds_entity*)entity)->m_hdl, &rd) == DDS_RETCODE_OK) { + assert(rd == entity); + + /* Reset the change counts of the metrics. */ + switch (data->status) { + case DDS_REQUESTED_DEADLINE_MISSED_STATUS: { + rd->m_requested_deadline_missed_status.total_count_change = 0; + break; + } + case DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS: { + rd->m_requested_incompatible_qos_status.total_count_change = 0; + break; + } + case DDS_SAMPLE_LOST_STATUS: { + rd->m_sample_lost_status.total_count_change = 0; + break; + } + case DDS_SAMPLE_REJECTED_STATUS: { + rd->m_sample_rejected_status.total_count_change = 0; + break; + } + case DDS_DATA_AVAILABLE_STATUS: { + /* Nothing to reset. */; + break; + } + case DDS_LIVELINESS_CHANGED_STATUS: { + rd->m_liveliness_changed_status.alive_count_change = 0; + rd->m_liveliness_changed_status.not_alive_count_change = 0; + break; + } + case DDS_SUBSCRIPTION_MATCHED_STATUS: { + rd->m_subscription_matched_status.total_count_change = 0; + rd->m_subscription_matched_status.current_count_change = 0; + break; + } + default: assert (0); + } + dds_reader_unlock(rd); + } else { + /* There's a deletion or closing going on. */ + } + } else if (rc == DDS_RETCODE_NO_DATA) { + /* Nobody was interested through a listener (NO_DATA == NO_CALL): set the status. */ + dds_entity_status_set(entity, data->status); + /* Notify possible interested observers. */ + dds_entity_status_signal(entity); + rc = DDS_RETCODE_OK; + } else if (rc == DDS_RETCODE_ALREADY_DELETED) { + /* An entity up the hierarchy is being deleted. */ + rc = DDS_RETCODE_OK; + } else { + /* Something went wrong up the hierarchy. */ + } + + DDS_REPORT_FLUSH(rc != DDS_RETCODE_OK); +} + + +_Pre_satisfies_(((participant_or_subscriber & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER ) ||\ + ((participant_or_subscriber & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) ) +_Pre_satisfies_(((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC ) ||\ + ((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_INTERNAL) ) +dds_entity_t +dds_create_reader( + _In_ dds_entity_t participant_or_subscriber, + _In_ dds_entity_t topic, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener) +{ + dds_qos_t * rqos; + dds__retcode_t rc; + dds_entity * sub = NULL; + dds_entity_t subscriber; + dds_reader * rd; + struct rhc * rhc; + dds_entity * tp; + dds_entity_t reader; + dds_entity_t t; + struct thread_state1 * const thr = lookup_thread_state (); + const bool asleep = !vtime_awake_p (thr->vtime); + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + if (dds_entity_kind(topic) != DDS_KIND_INTERNAL) { + /* Try claiming a participant. If that's not working, then it could be a subscriber. */ + if (dds_entity_kind(participant_or_subscriber) == DDS_KIND_PARTICIPANT) { + subscriber = dds_create_subscriber(participant_or_subscriber, qos, NULL); + } else { + subscriber = participant_or_subscriber; + } + t = topic; + } else { + /* TODO If qos is provided, we need to compare with writer qos to determine compatibility */ + subscriber = dds__get_builtin_subscriber(participant_or_subscriber); + t = dds__get_builtin_topic(subscriber, topic); + } + + rc = dds_entity_lock(subscriber, DDS_KIND_SUBSCRIBER, &sub); + if (rc != DDS_RETCODE_OK) { + reader = DDS_ERRNO(rc, "Error occurred on locking subscriber"); + goto err_sub_lock; + } + + if ((subscriber != participant_or_subscriber) && + (dds_entity_kind(topic) != DDS_KIND_INTERNAL)) { + /* Delete implicit subscriber if reader creation fails */ + sub->m_flags |= DDS_ENTITY_IMPLICIT; + } + + rc = dds_entity_lock(t, DDS_KIND_TOPIC, &tp); + if (rc != DDS_RETCODE_OK) { + reader = DDS_ERRNO(rc, "Error occurred on locking topic"); + goto err_tp_lock; + } + assert (((dds_topic*)tp)->m_stopic); + assert (sub->m_domain == tp->m_domain); + + /* Merge qos from topic and subscriber */ + rqos = dds_qos_create (); + if (qos) { + /* Only returns failure when one of the qos args is NULL, which + * is not the case here. */ + (void)dds_qos_copy(rqos, qos); + } + + if(sub->m_qos){ + dds_qos_merge (rqos, sub->m_qos); + } + + if (tp->m_qos) { + dds_qos_merge (rqos, tp->m_qos); + + /* reset the following qos policies if set during topic qos merge as they aren't applicable for reader */ + rqos->present &= ~(QP_DURABILITY_SERVICE | QP_TRANSPORT_PRIORITY | QP_LIFESPAN); + } + nn_xqos_mergein_missing (rqos, &gv.default_xqos_rd); + + ret = dds_reader_qos_validate (rqos, false); + if (ret != 0) { + dds_qos_delete(rqos); + reader = ret; + goto err_bad_qos; + } + + /* Create reader and associated read cache */ + rd = dds_alloc (sizeof (*rd)); + reader = dds_entity_init (&rd->m_entity, sub, DDS_KIND_READER, rqos, listener, DDS_READER_STATUS_MASK); + rd->m_sample_rejected_status.last_reason = DDS_NOT_REJECTED; + rd->m_topic = (dds_topic*)tp; + rhc = dds_rhc_new (rd, ((dds_topic*)tp)->m_stopic); + dds_entity_add_ref_nolock (tp); + rd->m_entity.m_deriver.close = dds_reader_close; + rd->m_entity.m_deriver.delete = dds_reader_delete; + rd->m_entity.m_deriver.set_qos = dds_reader_qos_set; + rd->m_entity.m_deriver.validate_status = dds_reader_status_validate; + rd->m_entity.m_deriver.get_instance_hdl = dds_reader_instance_hdl; + + /* Extra claim of this reader to make sure that the delete waits until DDSI + * has deleted its reader as well. This can be known through the callback. */ + if (ut_handle_claim(rd->m_entity.m_hdl, rd->m_entity.m_hdllink, DDS_KIND_READER, NULL) != UT_HANDLE_OK) { + assert(0); + } + + os_mutexUnlock(&tp->m_mutex); + os_mutexUnlock(&sub->m_mutex); + + if (asleep) { + thread_state_awake (thr); + } + rd->m_rd = new_reader(&rd->m_entity.m_guid, NULL, &sub->m_participant->m_guid, ((dds_topic*)tp)->m_stopic, + rqos, rhc, dds_reader_status_cb, rd); + os_mutexLock(&sub->m_mutex); + os_mutexLock(&tp->m_mutex); + assert (rd->m_rd); + if (asleep) { + thread_state_asleep (thr); + } + + /* For persistent data register reader with durability */ + if (dds_global.m_dur_reader && (rd->m_entity.m_qos->durability.kind > NN_TRANSIENT_LOCAL_DURABILITY_QOS)) { + (dds_global.m_dur_reader) (rd, rhc); + } + dds_entity_unlock(tp); + dds_entity_unlock(sub); + + if (dds_entity_kind(topic) == DDS_KIND_INTERNAL) { + /* If topic is builtin, then the topic entity is local and should + * be deleted because the application won't. */ + dds_delete(t); + } + + DDS_REPORT_FLUSH(reader <= 0); + return reader; + +err_bad_qos: + dds_entity_unlock(tp); +err_tp_lock: + dds_entity_unlock(sub); + if((sub->m_flags & DDS_ENTITY_IMPLICIT) != 0){ + (void)dds_delete(subscriber); + } +err_sub_lock: + DDS_REPORT_FLUSH(reader <= 0); + return reader; +} + +void +dds_reader_ddsi2direct( + dds_entity_t entity, + ddsi2direct_directread_cb_t cb, + void *cbarg) +{ + dds_reader *dds_rd; + + if (ut_handle_claim(entity, NULL, DDS_KIND_READER, (void**)&dds_rd) == UT_HANDLE_OK) + { + struct reader *rd = dds_rd->m_rd; + nn_guid_t pwrguid; + struct proxy_writer *pwr; + struct rd_pwr_match *m; + memset (&pwrguid, 0, sizeof (pwrguid)); + os_mutexLock (&rd->e.lock); + + rd->ddsi2direct_cb = cb; + rd->ddsi2direct_cbarg = cbarg; + while ((m = ut_avlLookupSuccEq (&rd_writers_treedef, &rd->writers, &pwrguid)) != NULL) + { + /* have to be careful walking the tree -- pretty is different, but + I want to check this before I write a lookup_succ function. */ + struct rd_pwr_match *m_next; + nn_guid_t pwrguid_next; + pwrguid = m->pwr_guid; + if ((m_next = ut_avlFindSucc (&rd_writers_treedef, &rd->writers, m)) != NULL) + pwrguid_next = m_next->pwr_guid; + else + { + memset (&pwrguid_next, 0xff, sizeof (pwrguid_next)); + pwrguid_next.entityid.u = (pwrguid_next.entityid.u & ~0xff) | NN_ENTITYID_KIND_WRITER_NO_KEY; + } + os_mutexUnlock (&rd->e.lock); + if ((pwr = ephash_lookup_proxy_writer_guid (&pwrguid)) != NULL) + { + os_mutexLock (&pwr->e.lock); + pwr->ddsi2direct_cb = cb; + pwr->ddsi2direct_cbarg = cbarg; + os_mutexUnlock (&pwr->e.lock); + } + pwrguid = pwrguid_next; + os_mutexLock (&rd->e.lock); + } + os_mutexUnlock (&rd->e.lock); + ut_handle_release(entity, ((dds_entity*)rd)->m_hdllink); + } +} + +uint32_t +dds_reader_lock_samples( + dds_entity_t reader) +{ + uint32_t ret = 0; + dds_reader *rd; + + ret = dds_reader_lock(reader, &rd); + if (ret == DDS_RETCODE_OK) { + ret = dds_rhc_lock_samples(rd->m_rd->rhc); + dds_reader_unlock(rd); + } else { + ret = 0; + } + return ret; +} + +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +int +dds_reader_wait_for_historical_data( + dds_entity_t reader, + dds_duration_t max_wait) +{ + int ret; + dds_reader *rd; + + DDS_REPORT_STACK(); + assert (reader); + + ret = dds_reader_lock(reader, &rd); + if (ret == DDS_RETCODE_OK) { + if (((dds_entity*)rd)->m_qos->durability.kind > NN_TRANSIENT_LOCAL_DURABILITY_QOS) { + ret = (dds_global.m_dur_wait) (rd, max_wait); + } else { + ret = DDS_ERRNO(DDS_RETCODE_ERROR, "Can not wait for historical data on a reader with volatile durability"); + } + dds_reader_unlock(rd); + } else { + ret = DDS_ERRNO(ret, "Error occurred on locking reader"); + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_(((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_READ ) || \ + ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_COND_QUERY) ) +dds_entity_t +dds_get_subscriber( + _In_ dds_entity_t entity) +{ + dds_entity_t hdl; + + DDS_REPORT_STACK(); + + if (dds_entity_kind(entity) == DDS_KIND_READER) { + hdl = dds_get_parent(entity); + } else if (dds_entity_kind(entity) == DDS_KIND_COND_READ || dds_entity_kind(entity) == DDS_KIND_COND_QUERY) { + hdl = dds_get_parent(entity); + if(hdl > 0){ + hdl = dds_get_subscriber(hdl); + } else { + DDS_ERROR(hdl, "Reader of this condition is already deleted"); + } + } else { + hdl = DDS_ERRNO(dds_valid_hdl(entity, DDS_KIND_READER), "Provided entity is not a reader nor a condition"); + } + DDS_REPORT_FLUSH(hdl <= 0); + return hdl; +} + +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +dds_return_t +dds_get_subscription_matched_status ( + _In_ dds_entity_t reader, + _Out_opt_ dds_subscription_matched_status_t * status) +{ + dds__retcode_t rc; + dds_reader *rd; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + rc = dds_reader_lock(reader, &rd); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking reader"); + goto fail; + } + /* status = NULL, application do not need the status, but reset the counter & triggered bit */ + if (status) { + *status = rd->m_subscription_matched_status; + } + if (((dds_entity*)rd)->m_status_enable & DDS_SUBSCRIPTION_MATCHED_STATUS) { + rd->m_subscription_matched_status.total_count_change = 0; + rd->m_subscription_matched_status.current_count_change = 0; + dds_entity_status_reset(rd, DDS_SUBSCRIPTION_MATCHED_STATUS); + } + dds_reader_unlock(rd); +fail: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +dds_return_t +dds_get_liveliness_changed_status ( + _In_ dds_entity_t reader, + _Out_opt_ dds_liveliness_changed_status_t * status) +{ + dds__retcode_t rc; + dds_reader *rd; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + rc = dds_reader_lock(reader, &rd); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking reader"); + goto fail; + } + /* status = NULL, application do not need the status, but reset the counter & triggered bit */ + if (status) { + *status = rd->m_liveliness_changed_status; + } + if (((dds_entity*)rd)->m_status_enable & DDS_LIVELINESS_CHANGED_STATUS) { + rd->m_liveliness_changed_status.alive_count_change = 0; + rd->m_liveliness_changed_status.not_alive_count_change = 0; + dds_entity_status_reset(rd, DDS_LIVELINESS_CHANGED_STATUS); + } + dds_reader_unlock(rd); +fail: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +dds_return_t dds_get_sample_rejected_status ( + _In_ dds_entity_t reader, + _Out_opt_ dds_sample_rejected_status_t * status) +{ + dds__retcode_t rc; + dds_reader *rd; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + rc = dds_reader_lock(reader, &rd); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking reader"); + goto fail; + } + /* status = NULL, application do not need the status, but reset the counter & triggered bit */ + if (status) { + *status = rd->m_sample_rejected_status; + } + if (((dds_entity*)rd)->m_status_enable & DDS_SAMPLE_REJECTED_STATUS) { + rd->m_sample_rejected_status.total_count_change = 0; + rd->m_sample_rejected_status.last_reason = DDS_NOT_REJECTED; + dds_entity_status_reset(rd, DDS_SAMPLE_REJECTED_STATUS); + } + dds_reader_unlock(rd); +fail: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +dds_return_t dds_get_sample_lost_status ( + _In_ dds_entity_t reader, + _Out_opt_ dds_sample_lost_status_t * status) +{ + dds__retcode_t rc; + dds_reader *rd; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + rc = dds_reader_lock(reader, &rd); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking reader"); + goto fail; + } + /* status = NULL, application do not need the status, but reset the counter & triggered bit */ + if (status) { + *status = rd->m_sample_lost_status; + } + if (((dds_entity*)rd)->m_status_enable & DDS_SAMPLE_LOST_STATUS) { + rd->m_sample_lost_status.total_count_change = 0; + dds_entity_status_reset(rd, DDS_SAMPLE_LOST_STATUS); + } + dds_reader_unlock(rd); +fail: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +dds_return_t dds_get_requested_deadline_missed_status ( + _In_ dds_entity_t reader, + _Out_opt_ dds_requested_deadline_missed_status_t * status) +{ + dds__retcode_t rc; + dds_reader *rd; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + rc = dds_reader_lock(reader, &rd); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking reader"); + goto fail; + } + /* status = NULL, application do not need the status, but reset the counter & triggered bit */ + if (status) { + *status = rd->m_requested_deadline_missed_status; + } + if (((dds_entity*)rd)->m_status_enable & DDS_REQUESTED_DEADLINE_MISSED_STATUS) { + rd->m_requested_deadline_missed_status.total_count_change = 0; + dds_entity_status_reset(rd, DDS_REQUESTED_DEADLINE_MISSED_STATUS); + } + dds_reader_unlock(rd); +fail: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_((reader & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) +dds_return_t dds_get_requested_incompatible_qos_status ( + _In_ dds_entity_t reader, + _Out_opt_ dds_requested_incompatible_qos_status_t * status) +{ + dds__retcode_t rc; + dds_reader *rd; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + rc = dds_reader_lock(reader, &rd); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking reader"); + goto fail; + } + /* status = NULL, application do not need the status, but reset the counter & triggered bit */ + if (status) { + *status = rd->m_requested_incompatible_qos_status; + } + if (((dds_entity*)rd)->m_status_enable & DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS) { + rd->m_requested_incompatible_qos_status.total_count_change = 0; + dds_entity_status_reset(rd, DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS); + } + dds_reader_unlock(rd); +fail: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} diff --git a/src/core/ddsc/src/dds_report.c b/src/core/ddsc/src/dds_report.c new file mode 100644 index 0000000..13406a3 --- /dev/null +++ b/src/core/ddsc/src/dds_report.c @@ -0,0 +1,53 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include "os/os.h" +#include "os/os_report.h" +#include +#include +#include "dds__report.h" + +void +dds_report( + os_reportType reportType, + const char *function, + int32_t line, + const char *file, + dds_return_t code, + const char *format, + ...) +{ + const char *retcode = NULL; + /* os_report truncates messages to bytes */ + char buffer[OS_REPORT_BUFLEN]; + size_t offset = 0; + va_list args; + + assert (function != NULL); + assert (file != NULL); + assert (format != NULL); + /* probably never happens, but you can never be to sure */ + assert (OS_REPORT_BUFLEN > 0); + + retcode = dds_err_str(code*-1); + offset = strlen(retcode); + assert (offset < OS_REPORT_BUFLEN); + (void)memcpy(buffer, retcode, offset); + buffer[offset] = ' '; + offset++; + + va_start (args, format); + (void)os_vsnprintf (buffer + offset, sizeof(buffer) - offset, format, args); + va_end (args); + os_report (reportType, function, file, line, code, "%s", buffer); +} + diff --git a/src/core/ddsc/src/dds_rhc.c b/src/core/ddsc/src/dds_rhc.c new file mode 100644 index 0000000..33df761 --- /dev/null +++ b/src/core/ddsc/src/dds_rhc.c @@ -0,0 +1,2411 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include + +#if HAVE_VALGRIND && ! defined (NDEBUG) +#include +#define USE_VALGRIND 1 +#else +#define USE_VALGRIND 0 +#endif + +#include "os/os.h" + +#include "dds__entity.h" +#include "dds__reader.h" +#include "dds__rhc.h" +#include "dds__tkmap.h" +#include "util/ut_hopscotch.h" + +#include "util/ut_avl.h" +#include "ddsi/q_xqos.h" +#include "ddsi/q_error.h" +#include "ddsi/q_unused.h" +#include "q__osplser.h" +#include "ddsi/q_config.h" +#include "ddsi/q_globals.h" +#include "ddsi/q_radmin.h" /* sampleinfo */ +#include "ddsi/q_entity.h" /* proxy_writer_info */ +#include "ddsi/sysdeps.h" +#include "dds__report.h" + +/* INSTANCE MANAGEMENT + =================== + + Instances are created implicitly by "write" and "dispose", unregistered by + "unregister". Valid samples are added only by write operations (possibly + a combined with dispose and/or unregister), invalid samples only by dispose + and unregister operations, and only when there is no sample or the latest + available sample is read. (This might be a bit funny in the oddish case + where someone would take only the latest of multiple valid samples.) + + There is at most one invalid sample per instance, its sample info is taken + straight from the instance when it is returned to the reader and its + presence and sample_state are represented by two bits. Any incoming sample + (or "incoming invalid sample") will cause an existing invalid sample to be + dropped. Thus, invalid samples are used solely to signal an instance state + change when there are no samples. + + (Note: this can fairly easily be changed to let an invalid sample always + be generated on dispose/unregister.) + + The instances and the RHC as a whole keep track of the number of valid + samples and the number of read valid samples, as well as the same for the + invalid ones, with the twist that the "inv_exists" and "inv_isread" booleans + in the RHC serve as flags and as counters at the same time. + + Instances are dropped when the number of samples (valid & invalid combined) + and the number of registrations both go to 0. The number of registrations + is kept track of in "wrcount", and a unique identifier for the most recent + writer is typically in "wr_iid". Typically, because an unregister by + "wr_iid" clears it. The actual set of registrations is in principle a set + of tuples stored in "registrations", but excluded from it + are those instances that have "wrcount" = 1 and "wr_iid" != 0. The typical + case is a single active writer for an instance, and this means the typical + case has no tuples in "registrations". + + It is unfortunate that this model complicates the transitions from 1 writer + to 2 writers, and from 2 writers back to 1 writer. This seems a reasonable + price to pay for the significant performance gain from not having to do + anything in the case of a single (or single dominant) writer. + + (Note: "registrations" may perhaps be moved to a global registration table + of tuples, using a lock-free hash table, but that + doesn't affect the model.) + + The unique identifiers for instances and writers are approximately uniformly + drawn from the set of positive unsigned 64-bit integers. This means they + are excellent hash keys, and both the instance hash table and the writer + registrations hash table use these directly. + + QOS SUPPORT + =========== + + History is implemented as a (circular) linked list, but the invalid samples + model implemented here allows this to trivially be changed to an array of + samples, and this is probably profitable for shallow histories. Currently + the instance has a single sample embedded in particular to optimise the + KEEP_LAST with depth=1 case. + + BY_SOURCE ordering is implemented differently from OpenSplice and does not + perform back-filling of the history. The arguments against that can be + found in JIRA, but in short: (1) not backfilling is significantly simpler + (and thus faster), (2) backfilling potentially requires rewriting the + states of samples already read, (3) it is as much "eventually consistent", + the only difference is that the model implemented here considers the + dataspace to fundamentally be "keep last 1" and always move forward (source + timestamp increases), and the per-reader history to be a record of sampling + that dataspace by the reader, whereas with backfilling the model is that + the eventual consistency applies to the full history. + + (As it happens, the model implemented here is that also used by RTI and + probably other implementations -- OpenSplice is the odd one out in this + regard.) + + Exclusive ownership is implemented by dropping all data from all writers + other than "wr_iid", unless "wr_iid" is 0 or the strength of the arriving + sample is higher than the current strength of the instance (in "strength"). + The writer id is only reset by unregistering, in which case it is natural + that ownership is up for grabs again. QoS changes (not supported in this + DDSI implementation, but still) will be done by also reseting "wr_iid" + when an exclusive ownership writer lowers its strength. + + Lifespan, time base filter and deadline, are based on the instance + timestamp ("tstamp"). This time stamp needs to be changed to either source + or reception timestamp, depending on the ordering chosen. + + READ CONDITIONS + =============== + + Read conditions are currently *always* attached to the reader, creating a + read condition and not attaching it to a waitset is a bit of a waste of + resources. This can be changed, of course, but it is doubtful many read + conditions get created without actually being used. + + The "trigger" of a read condition counts the number of instances + matching its condition and is synchronously updated whenever the state + of instances and/or samples changes. The instance/sample states are + reduced to a triplet of a bitmask representing the instance and view + states, whether or not the instance has unread samples, and whether or not + it has read ones. (Invalid samples included.) Two of these triplets, + pre-change and post-change are passed to "update_conditions_locked", + which then runs over the array of attached read conditions and updates + the trigger. It returns whether or not a trigger changed + from 0 to 1, as this indicates the attached waitsets must be signalled. + The actual signalling of the waitsets then takes places later, by calling + "signal_conditions" after releasing the RHC lock. +*/ + +static const status_cb_data_t dds_rhc_data_avail_cb_data = { DDS_DATA_AVAILABLE_STATUS, 0, 0, true }; + +/* FIXME: populate tkmap with key-only derived serdata, with timestamp + set to invalid. An invalid timestamp is (logically) unordered with + respect to valid timestamps, and that would mean BY_SOURCE order + would be respected even when generating an invalid sample for an + unregister message using the tkmap data. */ + +/****************************** + ****** LIVE WRITERS ****** + ******************************/ + +struct lwreg +{ + uint64_t iid; + uint64_t wr_iid; +}; + +struct lwregs +{ + struct ut_ehh * regs; +}; + +static uint32_t lwreg_hash (const void *vl) +{ + const struct lwreg * l = vl; + return (uint32_t) (l->iid ^ l->wr_iid); +} + +static int lwreg_equals (const void *va, const void *vb) +{ + const struct lwreg * a = va; + const struct lwreg * b = vb; + return a->iid == b->iid && a->wr_iid == b->wr_iid; +} + +static void lwregs_init (struct lwregs *rt) +{ + rt->regs = ut_ehhNew (sizeof (struct lwreg), 1, lwreg_hash, lwreg_equals); +} + +static void lwregs_fini (struct lwregs *rt) +{ + ut_ehhFree (rt->regs); +} + +static int lwregs_contains (struct lwregs *rt, uint64_t iid, uint64_t wr_iid) +{ + struct lwreg dummy = { .iid = iid, .wr_iid = wr_iid }; + return ut_ehhLookup (rt->regs, &dummy) != NULL; +} + +static int lwregs_add (struct lwregs *rt, uint64_t iid, uint64_t wr_iid) +{ + struct lwreg dummy = { .iid = iid, .wr_iid = wr_iid }; + return ut_ehhAdd (rt->regs, &dummy); +} + +static int lwregs_delete (struct lwregs *rt, uint64_t iid, uint64_t wr_iid) +{ + struct lwreg dummy = { .iid = iid, .wr_iid = wr_iid }; + return ut_ehhRemove (rt->regs, &dummy); +} + +/************************* + ****** RHC ****** + *************************/ + +struct rhc_sample +{ + struct serdata *sample; /* serialised data (either just_key or real data) */ + struct rhc_sample *next; /* next sample in time ordering, or oldest sample if most recent */ + uint64_t wr_iid; /* unique id for writer of this sample (perhaps better in serdata) */ + nn_wctime_t rtstamp; /* reception timestamp (not really required; perhaps better in serdata) */ + bool isread; /* READ or NOT_READ sample state */ + unsigned disposed_gen; /* snapshot of instance counter at time of insertion */ + unsigned no_writers_gen; /* __/ */ +}; + +struct rhc_instance +{ + uint64_t iid; /* unique instance id, key of table, also serves as instance handle */ + uint64_t wr_iid; /* unique of id of writer of latest sample or 0 */ + struct rhc_sample *latest; /* latest received sample; circular list old->new; null if no sample */ + unsigned nvsamples; /* number of "valid" samples in instance */ + unsigned nvread; /* number of READ "valid" samples in instance (0 <= nvread <= nvsamples) */ + uint32_t wrcount; /* number of live writers */ + bool isnew; /* NEW or NOT_NEW view state */ + bool a_sample_free; /* whether or not a_sample is in use */ + bool isdisposed; /* DISPOSED or NOT_DISPOSED (if not disposed, wrcount determines ALIVE/NOT_ALIVE_NO_WRITERS) */ + bool has_changed; /* To track changes in an instance - if number of samples are added or data is overwritten */ + unsigned inv_exists : 1; /* whether or not state change occurred since last sample (i.e., must return invalid sample) */ + unsigned inv_isread : 1; /* whether or not that state change has been read before */ + unsigned disposed_gen; /* bloody generation counters - worst invention of mankind */ + unsigned no_writers_gen; /* __/ */ + uint32_t strength; /* "current" ownership strength */ + nn_guid_t wr_guid; /* guid of last writer (if wr_iid != 0 then wr_guid is the corresponding guid, else undef) */ + nn_wctime_t tstamp; /* source time stamp of last update */ + struct rhc_instance *next; /* next non-empty instance in arbitrary ordering */ + struct rhc_instance *prev; + struct tkmap_instance *tk; /* backref into TK for unref'ing */ + struct rhc_sample a_sample; /* pre-allocated storage for 1 sample */ +}; + +typedef enum rhc_store_result +{ + RHC_STORED, + RHC_FILTERED, + RHC_REJECTED +} +rhc_store_result_t; + +struct rhc +{ + struct ut_hh *instances; + struct rhc_instance *nonempty_instances; /* circular, points to most recently added one, NULL if none */ + struct lwregs registrations; /* should be a global one (with lock-free lookups) */ + + /* Instance/Sample maximums from resource limits QoS */ + + os_atomic_uint32_t n_cbs; /* # callbacks in progress */ + int32_t max_instances; /* FIXME: probably better as uint32_t with MAX_UINT32 for unlimited */ + int32_t max_samples; /* FIXME: probably better as uint32_t with MAX_UINT32 for unlimited */ + int32_t max_samples_per_instance; /* FIXME: probably better as uint32_t with MAX_UINT32 for unlimited */ + + uint32_t n_instances; /* # instances, including empty [NOT USED] */ + uint32_t n_nonempty_instances; /* # non-empty instances */ + uint32_t n_not_alive_disposed; /* # disposed, non-empty instances [NOT USED] */ + uint32_t n_not_alive_no_writers; /* # not-alive-no-writers, non-empty instances [NOT USED] */ + uint32_t n_new; /* # new, non-empty instances [NOT USED] */ + uint32_t n_vsamples; /* # "valid" samples over all instances */ + uint32_t n_vread; /* # read "valid" samples over all instances [NOT USED] */ + uint32_t n_invsamples; /* # invalid samples over all instances [NOT USED] */ + uint32_t n_invread; /* # read invalid samples over all instances [NOT USED] */ + + bool by_source_ordering; /* true if BY_SOURCE, false if BY_RECEPTION */ + bool exclusive_ownership; /* true if EXCLUSIVE, false if SHARED */ + bool reliable; /* true if reliability RELIABLE */ + + dds_reader * reader; /* reader */ + const struct sertopic * topic; /* topic description */ + unsigned history_depth; /* depth, 1 for KEEP_LAST_1, 2**32-1 for KEEP_ALL */ + + os_mutex lock; + os_mutex conds_lock; + dds_readcond * conds; /* List of associated read conditions */ + uint32_t nconds; /* Number of associated read conditions */ +}; + +struct trigger_info +{ + unsigned qminst; + bool has_read; + bool has_not_read; + bool has_changed; +}; + +#define QMASK_OF_SAMPLE(s) ((s)->isread ? DDS_READ_SAMPLE_STATE : DDS_NOT_READ_SAMPLE_STATE) +#define QMASK_OF_INVSAMPLE(i) ((i)->inv_isread ? DDS_READ_SAMPLE_STATE : DDS_NOT_READ_SAMPLE_STATE) +#define INST_NSAMPLES(i) ((i)->nvsamples + (i)->inv_exists) +#define INST_NREAD(i) ((i)->nvread + ((i)->inv_exists & (i)->inv_isread)) +#define INST_IS_EMPTY(i) (INST_NSAMPLES (i) == 0) +#define INST_HAS_READ(i) (INST_NREAD (i) > 0) +#define INST_HAS_UNREAD(i) (INST_NREAD (i) < INST_NSAMPLES (i)) + +static unsigned qmask_of_inst (const struct rhc_instance *inst); +static bool update_conditions_locked +(struct rhc *rhc, const struct trigger_info *pre, const struct trigger_info *post, const struct serdata *sample); +static void signal_conditions (struct rhc *rhc); +#ifndef NDEBUG +static int rhc_check_counts_locked (struct rhc *rhc, bool check_conds); +#endif + +static uint32_t instance_iid_hash (const void *va) +{ + const struct rhc_instance *a = va; + return (uint32_t) a->iid; +} + +static int instance_iid_eq (const void *va, const void *vb) +{ + const struct rhc_instance *a = va; + const struct rhc_instance *b = vb; + return (a->iid == b->iid); +} + +static void add_inst_to_nonempty_list (_Inout_ struct rhc *rhc, _Inout_ struct rhc_instance *inst) +{ + if (rhc->nonempty_instances == NULL) + { + inst->next = inst->prev = inst; + } + else + { + struct rhc_instance * const hd = rhc->nonempty_instances; +#ifndef NDEBUG + { + const struct rhc_instance *x = hd; + do { assert (x != inst); x = x->next; } while (x != hd); + } +#endif + inst->next = hd->next; + inst->prev = hd; + hd->next = inst; + inst->next->prev = inst; + } + rhc->nonempty_instances = inst; + rhc->n_nonempty_instances++; +} + +static void remove_inst_from_nonempty_list (struct rhc *rhc, struct rhc_instance *inst) +{ + assert (INST_IS_EMPTY (inst)); +#ifndef NDEBUG + { + const struct rhc_instance *x = rhc->nonempty_instances; + do { if (x == inst) break; x = x->next; } while (x != rhc->nonempty_instances); + assert (x == inst); + } +#endif + + if (inst->next == inst) + { + rhc->nonempty_instances = NULL; + } + else + { + struct rhc_instance * const inst_prev = inst->prev; + struct rhc_instance * const inst_next = inst->next; + inst_prev->next = inst_next; + inst_next->prev = inst_prev; + if (rhc->nonempty_instances == inst) + rhc->nonempty_instances = inst_prev; + } + assert (rhc->n_nonempty_instances > 0); + rhc->n_nonempty_instances--; +} + +struct rhc * dds_rhc_new (dds_reader * reader, const struct sertopic * topic) +{ + struct rhc * rhc = dds_alloc (sizeof (*rhc)); + + lwregs_init (&rhc->registrations); + os_mutexInit (&rhc->lock); + os_mutexInit (&rhc->conds_lock); + rhc->instances = ut_hhNew (1, instance_iid_hash, instance_iid_eq); + rhc->topic = topic; + rhc->reader = reader; + + return rhc; +} + +void dds_rhc_set_qos (struct rhc * rhc, const nn_xqos_t * qos) +{ + /* Set read related QoS */ + + rhc->max_samples = qos->resource_limits.max_samples; + rhc->max_instances = qos->resource_limits.max_instances; + rhc->max_samples_per_instance = qos->resource_limits.max_samples_per_instance; + rhc->by_source_ordering = (qos->destination_order.kind == NN_BY_SOURCE_TIMESTAMP_DESTINATIONORDER_QOS); + rhc->exclusive_ownership = (qos->ownership.kind == NN_EXCLUSIVE_OWNERSHIP_QOS); + rhc->reliable = (qos->reliability.kind == NN_RELIABLE_RELIABILITY_QOS); + rhc->history_depth = (qos->history.kind == NN_KEEP_LAST_HISTORY_QOS) ? qos->history.depth : ~0u; +} + +static struct rhc_sample * alloc_sample (struct rhc_instance *inst) +{ + if (inst->a_sample_free) + { + inst->a_sample_free = false; +#if USE_VALGRIND + VALGRIND_MAKE_MEM_UNDEFINED (&inst->a_sample, sizeof (inst->a_sample)); +#endif + return &inst->a_sample; + } + else + { + /* This instead of sizeof(rhc_sample) gets us type checking */ + struct rhc_sample *s; + s = dds_alloc (sizeof (*s)); + return s; + } +} + +static void free_sample (struct rhc_instance *inst, struct rhc_sample *s) +{ + ddsi_serdata_unref (s->sample); + if (s == &inst->a_sample) + { + assert (!inst->a_sample_free); +#if USE_VALGRIND + VALGRIND_MAKE_MEM_NOACCESS (&inst->a_sample, sizeof (inst->a_sample)); +#endif + inst->a_sample_free = true; + } + else + { + dds_free (s); + } +} + +static void inst_clear_invsample (struct rhc *rhc, struct rhc_instance *inst) +{ + assert (inst->inv_exists); + inst->inv_exists = 0; + if (inst->inv_isread) + { + rhc->n_invread--; + } + rhc->n_invsamples--; +} + +static void inst_clear_invsample_if_exists (struct rhc *rhc, struct rhc_instance *inst) +{ + if (inst->inv_exists) + { + inst_clear_invsample (rhc, inst); + } +} + +static void inst_set_invsample (struct rhc *rhc, struct rhc_instance *inst) +{ + /* Obviously optimisable, but that is perhaps not worth the bother */ + inst_clear_invsample_if_exists (rhc, inst); + inst->inv_exists = 1; + inst->inv_isread = 0; + rhc->n_invsamples++; +} + +static void free_instance (void *vnode, void *varg) +{ + struct rhc *rhc = varg; + struct rhc_instance *inst = vnode; + struct rhc_sample *s = inst->latest; + const bool was_empty = INST_IS_EMPTY (inst); + if (s) + { + do { + struct rhc_sample * const s1 = s->next; + free_sample (inst, s); + s = s1; + } while (s != inst->latest); + rhc->n_vsamples -= inst->nvsamples; + rhc->n_vread -= inst->nvread; + inst->nvsamples = 0; + inst->nvread = 0; + } + inst_clear_invsample_if_exists (rhc, inst); + if (!was_empty) + { + remove_inst_from_nonempty_list (rhc, inst); + } + dds_tkmap_instance_unref (inst->tk); + dds_free (inst); +} + +uint32_t dds_rhc_lock_samples (struct rhc *rhc) +{ + uint32_t no; + os_mutexLock (&rhc->lock); + no = rhc->n_vsamples + rhc->n_invsamples; + if (no == 0) + { + os_mutexUnlock (&rhc->lock); + } + return no; +} + +void dds_rhc_free (struct rhc *rhc) +{ + assert (rhc_check_counts_locked (rhc, true)); + ut_hhEnum (rhc->instances, free_instance, rhc); + assert (rhc->nonempty_instances == NULL); + ut_hhFree (rhc->instances); + lwregs_fini (&rhc->registrations); + os_mutexDestroy (&rhc->lock); + os_mutexDestroy (&rhc->conds_lock); + dds_free (rhc); +} + +void dds_rhc_fini (struct rhc * rhc) +{ + os_mutexLock (&rhc->lock); + rhc->reader = NULL; + os_mutexUnlock (&rhc->lock); + + /* Wait for all callbacks to complete */ + + while (os_atomic_ld32 (&rhc->n_cbs) > 0) + { + dds_sleepfor (DDS_MSECS (1)); + } +} + +static void init_trigger_info_nonmatch (struct trigger_info *info) +{ + info->qminst = ~0u; + info->has_read = false; + info->has_not_read = false; + info->has_changed = false; +} + +static void get_trigger_info (struct trigger_info *info, struct rhc_instance *inst, bool pre) +{ + info->qminst = qmask_of_inst (inst); + info->has_read = INST_HAS_READ (inst); + info->has_not_read = INST_HAS_UNREAD (inst); + /* reset instance has_changed before adding/overwriting a sample */ + if (pre) + { + inst->has_changed = false; + } + info->has_changed = inst->has_changed; +} + +static bool trigger_info_differs (const struct trigger_info *pre, const struct trigger_info *post) +{ + return pre->qminst != post->qminst || pre->has_read != post->has_read || pre->has_not_read != post->has_not_read || + pre->has_changed != post->has_changed; +} + +static bool add_sample +( + struct rhc * rhc, + struct rhc_instance * inst, + const struct nn_rsample_info * sampleinfo, + const struct serdata * sample, + status_cb_data_t * cb_data +) +{ + struct rhc_sample *s; + assert (sample->v.bswap == sampleinfo->bswap); + + /* Adding a sample always clears an invalid sample (because the information + contained in the invalid sample - the instance state and the generation + counts - are included in the sample). While this would be place to do it, + we do it later to avoid having to roll back on allocation failure */ + + /* We don't do backfilling in BY_SOURCE mode -- we could, but + choose not to -- and having already filtered out samples + preceding inst->latest, we can simply insert it without any + searching */ + if (inst->nvsamples == rhc->history_depth) + { + /* replace oldest sample; latest points to the latest one, the + list is circular from old -> new, so latest->next is the oldest */ + + inst_clear_invsample_if_exists (rhc, inst); + assert (inst->latest != NULL); + s = inst->latest->next; + ddsi_serdata_unref (s->sample); + if (s->isread) + { + inst->nvread--; + rhc->n_vread--; + } + + /* set a flag to indicate instance has changed to notify data_available since the sample is overwritten */ + inst->has_changed = true; + } + else + { + /* Check if resource max_samples QoS exceeded */ + + if (rhc->reader && rhc->max_samples != DDS_LENGTH_UNLIMITED && rhc->n_vsamples >= (uint32_t) rhc->max_samples) + { + cb_data->status = DDS_SAMPLE_REJECTED_STATUS; + cb_data->extra = DDS_REJECTED_BY_SAMPLES_LIMIT; + cb_data->handle = inst->iid; + cb_data->add = true; + return false; + } + + /* Check if resource max_samples_per_instance QoS exceeded */ + + if (rhc->reader && rhc->max_samples_per_instance != DDS_LENGTH_UNLIMITED && inst->nvsamples >= (uint32_t) rhc->max_samples_per_instance) + { + cb_data->status = DDS_SAMPLE_REJECTED_STATUS; + cb_data->extra = DDS_REJECTED_BY_SAMPLES_PER_INSTANCE_LIMIT; + cb_data->handle = inst->iid; + cb_data->add = true; + return false; + } + + /* add new latest sample */ + + s = alloc_sample (inst); + inst_clear_invsample_if_exists (rhc, inst); + if (inst->latest == NULL) + { + s->next = s; + } + else + { + s->next = inst->latest->next; + inst->latest->next = s; + } + inst->nvsamples++; + rhc->n_vsamples++; + } + + s->sample = ddsi_serdata_ref ((serdata_t) sample); /* drops const (tho refcount does change) */ + s->wr_iid = sampleinfo->pwr_info.iid; + s->rtstamp = sampleinfo->reception_timestamp; + s->isread = false; + s->disposed_gen = inst->disposed_gen; + s->no_writers_gen = inst->no_writers_gen; + inst->latest = s; + + return true; +} + +static bool content_filter_accepts (const struct sertopic * topic, const struct serdata *sample) +{ + bool ret = true; + + if (topic->filter_fn) + { + deserialize_into ((char*) topic->filter_sample, sample); + ret = (topic->filter_fn) (topic->filter_sample, topic->filter_ctx); + } + return ret; +} + +static int inst_accepts_sample_by_writer_guid (const struct rhc_instance *inst, const struct nn_rsample_info *sampleinfo) +{ + return inst->wr_iid == sampleinfo->pwr_info.iid || + memcmp (&sampleinfo->pwr_info.guid, &inst->wr_guid, sizeof (inst->wr_guid)) < 0; +} + +static int inst_accepts_sample +( + const struct rhc *rhc, const struct rhc_instance *inst, + const struct nn_rsample_info *sampleinfo, + const struct serdata *sample, const bool has_data +) +{ + if (rhc->by_source_ordering) + { + if (sample->v.msginfo.timestamp.v > inst->tstamp.v) + { + /* ok */ + } + else if (sample->v.msginfo.timestamp.v < inst->tstamp.v) + { + return 0; + } + else if (inst_accepts_sample_by_writer_guid (inst, sampleinfo)) + { + /* ok */ + } + else + { + return 0; + } + } + if (rhc->exclusive_ownership && inst->wr_iid != sampleinfo->pwr_info.iid) + { + uint32_t strength = sampleinfo->pwr_info.ownership_strength; + if (strength > inst->strength) { + /* ok */ + } else if (strength < inst->strength) { + return 0; + } else if (inst_accepts_sample_by_writer_guid (inst, sampleinfo)) { + /* ok */ + } else { + return 0; + } + } + if (has_data && !content_filter_accepts (rhc->topic, sample)) + { + return 0; + } + return 1; +} + +static void update_inst +( + const struct rhc *rhc, struct rhc_instance *inst, + const struct proxy_writer_info * __restrict pwr_info, nn_wctime_t tstamp) +{ + if (inst->wr_iid != pwr_info->iid) + { + inst->wr_guid = pwr_info->guid; + } + inst->tstamp = tstamp; + inst->wr_iid = (inst->wrcount == 0) ? 0 : pwr_info->iid; + inst->strength = pwr_info->ownership_strength; +} + +static void drop_instance_noupdate_no_writers (struct rhc *rhc, struct rhc_instance *inst) +{ + int ret; + assert (INST_IS_EMPTY (inst)); + + rhc->n_instances--; + + ret = ut_hhRemove (rhc->instances, inst); + assert (ret); + (void) ret; + + free_instance (inst, rhc); +} + +static void dds_rhc_register (struct rhc *rhc, struct rhc_instance *inst, uint64_t wr_iid, bool iid_update) +{ + TRACE ((" register:")); + + /* Is an implicitly registering dispose semantically equivalent to + register ; dispose? If so, both no_writers_gen and disposed_gen + need to be incremented if the old instance state was DISPOSED, + else just disposed_gen. (Shudder.) Interpreting it as + equivalent. + + Is a dispose a sample? I don't think so (though a write dispose + is). Is a pure register a sample? Don't think so either. */ + if (inst->wr_iid == wr_iid) + { + /* Same writer as last time => we know it is registered already. + This is the fast path -- we don't have to check anything + else. */ + TRACE (("cached")); + assert (inst->wrcount > 0); + return; + } + + if (inst->wrcount == 0) + { + /* Currently no writers at all */ + assert (inst->wr_iid == 0); + + /* to avoid wr_iid update when register is called for sample rejected */ + if (iid_update) + { + inst->wr_iid = wr_iid; + } + inst->wrcount++; + inst->no_writers_gen++; + TRACE (("new1")); + + if (!INST_IS_EMPTY (inst) && !inst->isdisposed) + rhc->n_not_alive_no_writers--; + } + else if (inst->wr_iid == 0 && inst->wrcount == 1) + { + /* Writers exist, but wr_iid is null => someone unregistered. + + With wrcount 1, if wr_iid happens to be the remaining writer, + we remove the explicit registration and once again rely on + inst->wr_iid, but if wr_iid happens to be a new writer, we + increment the writer count & explicitly register the second + one, too. + + If I decide on a global table of registrations implemented + using concurrent hopscotch-hashing, then this should still + scale well because lwregs_add first calls lwregs_contains, + which is lock-free. (Not that it probably can't be optimised + by a combined add-if-unknown-delete-if-known operation -- but + the value of that is likely negligible because the + registrations should be fairly stable.) */ + if (lwregs_add (&rhc->registrations, inst->iid, wr_iid)) + { + inst->wrcount++; + TRACE (("new2iidnull")); + } + else + { + int x = lwregs_delete (&rhc->registrations, inst->iid, wr_iid); + assert (x); + (void) x; + TRACE (("restore")); + } + /* to avoid wr_iid update when register is called for sample rejected */ + if (iid_update) + { + inst->wr_iid = wr_iid; + } + } + else + { + /* As above -- if using concurrent hopscotch hashing, if the + writer is already known, lwregs_add is lock-free */ + if (inst->wrcount == 1) + { + /* 2nd writer => properly register the one we knew about */ + TRACE (("rescue1")); + int x; + x = lwregs_add (&rhc->registrations, inst->iid, inst->wr_iid); + assert (x); + (void) x; + } + if (lwregs_add (&rhc->registrations, inst->iid, wr_iid)) + { + /* as soon as we reach at least two writers, we have to check + the result of lwregs_add to know whether this sample + registers a previously unknown writer or not */ + TRACE (("new3")); + inst->wrcount++; + } + else + { + TRACE (("known")); + } + assert (inst->wrcount >= 2); + /* the most recent writer gets the fast path */ + /* to avoid wr_iid update when register is called for sample rejected */ + if (iid_update) + { + inst->wr_iid = wr_iid; + } + } +} + +static void account_for_empty_to_nonempty_transition (struct rhc *rhc, struct rhc_instance *inst) +{ + assert (INST_NSAMPLES (inst) == 1); + add_inst_to_nonempty_list (rhc, inst); + if (inst->isnew) + { + rhc->n_new++; + } + if (inst->isdisposed) + rhc->n_not_alive_disposed++; + else if (inst->wrcount == 0) + rhc->n_not_alive_no_writers++; +} + +static int rhc_unregister_isreg_w_sideeffects (struct rhc *rhc, const struct rhc_instance *inst, uint64_t wr_iid) +{ + /* Returns 1 if last registration just disappeared */ + if (inst->wrcount == 0) + { + TRACE (("unknown(#0)")); + return 0; + } + else if (inst->wrcount == 1 && inst->wr_iid != 0) + { + if (wr_iid != inst->wr_iid) + { + TRACE (("unknown(cache)")); + return 0; + } + else + { + TRACE (("last(cache)")); + return 1; + } + } + else if (!lwregs_delete (&rhc->registrations, inst->iid, wr_iid)) + { + TRACE (("unknown(regs)")); + return 0; + } + else + { + TRACE (("delreg")); + /* If we transition from 2 to 1 writer, and we are deleting a + writer other than the one cached in the instance, that means + afterward there will be 1 writer, it will be cached, and its + registration record must go (invariant that with wrcount = 1 + and wr_iid != 0 the wr_iid is not in "registrations") */ + if (inst->wrcount == 2 && inst->wr_iid != wr_iid) + { + TRACE ((",delreg(remain)")); + lwregs_delete (&rhc->registrations, inst->iid, inst->wr_iid); + } + return 1; + } +} + +static int rhc_unregister_updateinst +( + struct rhc *rhc, struct rhc_instance *inst, + const struct proxy_writer_info * __restrict pwr_info, nn_wctime_t tstamp) +{ + assert (inst->wrcount > 0); + + if (pwr_info->iid == inst->wr_iid) + { + /* Next register will have to do real work before we have a cached + wr_iid again */ + inst->wr_iid = 0; + + /* Reset the ownership strength to allow samples to be read from other + writer(s) */ + inst->strength = 0; + TRACE ((",clearcache")); + } + + if (--inst->wrcount > 0) + return 0; + else if (!INST_IS_EMPTY (inst)) + { + /* Instance still has content - do not drop until application + takes the last sample. Set the invalid sample if the latest + sample has been read already, so that the application can + read the change to not-alive. (If the latest sample is still + unread, we don't bother, even though it means the application + won't see the timestamp for the unregister event. It shouldn't + care.) */ + if (inst->latest == NULL /*|| inst->latest->isread*/) + { + inst_set_invsample (rhc, inst); + update_inst (rhc, inst, pwr_info, tstamp); + } + if (!inst->isdisposed) + { + rhc->n_not_alive_no_writers++; + } + return 0; + } + else if (inst->isdisposed) + { + /* No content left, no registrations left, so drop */ + TRACE ((",#0,empty,disposed,drop")); + drop_instance_noupdate_no_writers (rhc, inst); + return 1; + } + else + { + /* Add invalid samples for transition to no-writers */ + TRACE ((",#0,empty,nowriters")); + assert (INST_IS_EMPTY (inst)); + inst_set_invsample (rhc, inst); + update_inst (rhc, inst, pwr_info, tstamp); + account_for_empty_to_nonempty_transition (rhc, inst); + return 0; + } +} + +static void dds_rhc_unregister +( + struct trigger_info *post, struct rhc *rhc, struct rhc_instance *inst, + const struct proxy_writer_info * __restrict pwr_info, nn_wctime_t tstamp +) +{ + /* 'post' always gets set; instance may have been freed upon return. */ + TRACE ((" unregister:")); + if (!rhc_unregister_isreg_w_sideeffects (rhc, inst, pwr_info->iid)) + { + /* other registrations remain */ + get_trigger_info (post, inst, false); + } + else if (rhc_unregister_updateinst (rhc, inst, pwr_info, tstamp)) + { + /* instance dropped */ + init_trigger_info_nonmatch (post); + } + else + { + /* no writers remain, but instance not empty */ + get_trigger_info (post, inst, false); + } +} + +static struct rhc_instance * alloc_new_instance +( + const struct rhc *rhc, + const struct nn_rsample_info *sampleinfo, + struct serdata *serdata, + struct tkmap_instance *tk +) +{ + struct rhc_instance *inst; + + dds_tkmap_instance_ref (tk); + inst = dds_alloc (sizeof (*inst)); + inst->iid = tk->m_iid; + inst->tk = tk; + inst->wrcount = (serdata->v.msginfo.statusinfo & NN_STATUSINFO_UNREGISTER) ? 0 : 1; + inst->isdisposed = (serdata->v.msginfo.statusinfo & NN_STATUSINFO_DISPOSE); + inst->isnew = true; + inst->inv_exists = 0; + inst->inv_isread = 0; /* don't care */ + inst->a_sample_free = true; + update_inst (rhc, inst, &sampleinfo->pwr_info, serdata->v.msginfo.timestamp); + return inst; +} + +static rhc_store_result_t rhc_store_new_instance +( + struct trigger_info * post, + struct rhc *rhc, + const struct nn_rsample_info *sampleinfo, + struct serdata *sample, + struct tkmap_instance *tk, + const bool has_data, + status_cb_data_t * cb_data +) +{ + struct rhc_instance *inst; + int ret; + + /* New instance for this reader. May still filter out key value. + + Doing the filtering here means avoiding filter processing in + the normal case of accepting data, accepting some extra + overhead in the case where the data would be filtered out. + Naturally using an avl tree is not so smart for these IIDs, and + if the AVL tree is replaced by a hash table, the overhead + trade-off should be quite nice with the filtering code right + here. + + Note: never instantiating based on a sample that's filtered out, + though one could argue that if it is rejected based on an + attribute (rather than a key), an empty instance should be + instantiated. */ + + if (has_data && !content_filter_accepts (rhc->topic, sample)) + { + return RHC_FILTERED; + } + /* Check if resource max_instances QoS exceeded */ + + if (rhc->reader && rhc->max_instances != DDS_LENGTH_UNLIMITED && rhc->n_instances >= (uint32_t) rhc->max_instances) + { + cb_data->status = DDS_SAMPLE_REJECTED_STATUS; + cb_data->extra = DDS_REJECTED_BY_INSTANCES_LIMIT; + cb_data->handle = tk->m_iid; + cb_data->add = true; + return RHC_REJECTED; + } + + inst = alloc_new_instance (rhc, sampleinfo, sample, tk); + if (has_data) + { + if (!add_sample (rhc, inst, sampleinfo, sample, cb_data)) + { + free_instance (inst, rhc); + return RHC_REJECTED; + } + } + else + { + if (inst->isdisposed) { + inst_set_invsample(rhc, inst); + } + } + + account_for_empty_to_nonempty_transition (rhc, inst); + ret = ut_hhAdd (rhc->instances, inst); + assert (ret); + (void) ret; + rhc->n_instances++; + get_trigger_info (post, inst, false); + + return RHC_STORED; +} + +/* + dds_rhc_store: DDSI up call into read cache to store new sample. Returns whether sample + delivered (true unless a reliable sample rejected). +*/ + +bool dds_rhc_store +( + struct rhc * __restrict rhc, const struct nn_rsample_info * __restrict sampleinfo, + struct serdata * __restrict sample, struct tkmap_instance * __restrict tk +) +{ + const uint64_t wr_iid = sampleinfo->pwr_info.iid; + const unsigned statusinfo = sample->v.msginfo.statusinfo; + const bool has_data = (sample->v.st->kind == STK_DATA); + const int is_dispose = (statusinfo & NN_STATUSINFO_DISPOSE) != 0; + struct rhc_instance dummy_instance; + struct rhc_instance *inst; + struct trigger_info pre, post; + bool trigger_waitsets; + rhc_store_result_t stored; + status_cb_data_t cb_data; /* Callback data for reader status callback */ + bool notify_data_available = true; + bool delivered = true; + + TRACE (("rhc_store(%"PRIx64",%"PRIx64" si %x has_data %d:", tk->m_iid, wr_iid, statusinfo, has_data)); + if (!has_data && statusinfo == 0) + { + /* Write with nothing but a key -- I guess that would be a + register, which we do implicitly. (Currently DDSI2 won't allow + it through anyway.) */ + TRACE ((" ignore explicit register)\n")); + return delivered; + } + + dummy_instance.iid = tk->m_iid; + stored = RHC_FILTERED; + cb_data.status = 0; + + os_mutexLock (&rhc->lock); + + inst = ut_hhLookup (rhc->instances, &dummy_instance); + if (inst == NULL) + { + /* New instance for this reader. If no data content -- not (also) + a write -- ignore it, I think we can get away with ignoring dispose or unregisters + on unknown instances. + */ + if (!has_data && !is_dispose) + { + TRACE ((" disp/unreg on unknown instance")); + goto error_or_nochange; + } + else + { + TRACE ((" new instance")); + stored = rhc_store_new_instance (&post, rhc, sampleinfo, sample, tk, has_data, &cb_data); + if (stored != RHC_STORED) + { + goto error_or_nochange; + } + init_trigger_info_nonmatch (&pre); + } + } + else if (!inst_accepts_sample (rhc, inst, sampleinfo, sample, has_data)) + { + /* Rejected samples (and disposes) should still register the writer; + unregister *must* be processed, or we have a memory leak. (We + will raise a SAMPLE_REJECTED, and indicate that the system should + kill itself.) Not letting instances go to ALIVE or NEW based on + a rejected sample - (no one knows, it seemed) */ + TRACE ((" instance rejects sample")); + + + get_trigger_info (&pre, inst, true); + if (has_data || is_dispose) + { + dds_rhc_register (rhc, inst, wr_iid, false); + } + if (statusinfo & NN_STATUSINFO_UNREGISTER) + { + dds_rhc_unregister (&post, rhc, inst, &sampleinfo->pwr_info, sample->v.msginfo.timestamp); + } + else + { + get_trigger_info (&post, inst, false); + } + /* notify sample lost */ + + cb_data.status = DDS_SAMPLE_LOST_STATUS; + cb_data.extra = 0; + cb_data.handle = 0; + cb_data.add = true; + goto error_or_nochange; + + /* FIXME: deadline (and other) QoS? */ + } + else + { + get_trigger_info (&pre, inst, true); + + if (has_data || is_dispose) + { + /* View state must be NEW following receipt of a sample when + instance was NOT_ALIVE (whether DISPOSED or NO_WRITERS). + Once we start fiddling with the state, we can no longer + figure out whether it is alive or not, so determine whether + it is currently NOT_ALIVE. */ + const int not_alive = inst->wrcount == 0 || inst->isdisposed; + const bool old_isdisposed = inst->isdisposed; + const bool old_isnew = inst->isnew; + const bool was_empty = INST_IS_EMPTY (inst); + int inst_became_disposed = 0; + + /* Not just an unregister, so a write and/or a dispose (possibly + combined with an unregister). Write & dispose create a + registration and we always do that, even if we have to delete + it immediately afterward. It seems unlikely to be worth the + effort of optimising this, but it can be done. On failure + (i.e., out-of-memory), abort the operation and hope that the + caller can still notify the application. */ + + dds_rhc_register (rhc, inst, wr_iid, true); + + /* Sample arriving for a NOT_ALIVE instance => view state NEW */ + if (has_data && not_alive) + { + TRACE ((" notalive->alive")); + inst->isnew = true; + } + + /* Desired effect on instance state and disposed_gen: + op DISPOSED NOT_DISPOSED + W ND;gen++ ND + D D D + WD D;gen++ D + Simplest way is to toggle istate when it is currently DISPOSED + and the operation is WD. */ + if (has_data && inst->isdisposed) + { + TRACE ((" disposed->notdisposed")); + inst->isdisposed = false; + inst->disposed_gen++; + } + if (is_dispose) + { + inst->isdisposed = true; + inst_became_disposed = !old_isdisposed; + TRACE ((" dispose(%d)", inst_became_disposed)); + } + + /* Only need to add a sample to the history if the input actually + is a sample. */ + if (has_data) + { + TRACE ((" add_sample")); + if (! add_sample (rhc, inst, sampleinfo, sample, &cb_data)) + { + TRACE (("(reject)")); + stored = RHC_REJECTED; + goto error_or_nochange; + } + } + + /* If instance became disposed, add an invalid sample if there are no samples left */ + if (inst_became_disposed && (inst->latest == NULL )) + inst_set_invsample (rhc, inst); + + update_inst (rhc, inst, &sampleinfo->pwr_info, sample->v.msginfo.timestamp); + + /* Can only add samples => only need to give special treatment + to instances that were empty before. It is, however, not + guaranteed that we end up with a non-empty instance: for + example, if the instance was disposed & empty, nothing + changes. */ + if (inst->latest || inst_became_disposed) + { + if (was_empty) + { + /* general function is slightly slower than a specialised + one, but perhaps it is wiser to use the general one */ + account_for_empty_to_nonempty_transition (rhc, inst); + } + else + { + rhc->n_not_alive_disposed += inst->isdisposed - old_isdisposed; + rhc->n_new += (inst->isnew ? 1 : 0) - (old_isnew ? 1 : 0); + } + } + else + { + assert (INST_IS_EMPTY (inst) == was_empty); + } + } + + assert (rhc_check_counts_locked (rhc, false)); + + if (statusinfo & NN_STATUSINFO_UNREGISTER) + { + /* Either a pure unregister, or the instance rejected the sample + because of time stamps, content filter, or something else. If + the writer unregisters the instance, I think we should ignore + the acceptance filters and process it anyway. + + It is a bit unclear what + + write_w_timestamp(x,1) ; unregister_w_timestamp(x,0) + + actually means if BY_SOURCE ordering is selected: does that + mean an application reading "x" after the write and reading it + again after the unregister will see a change in the + no_writers_generation field? */ + dds_rhc_unregister (&post, rhc, inst, &sampleinfo->pwr_info, sample->v.msginfo.timestamp); + } + else + { + get_trigger_info (&post, inst, false); + } + } + + TRACE ((")\n")); + + /* do not send data available notification when an instance is dropped */ + if ((post.qminst == ~0u) && (post.has_read == 0) && (post.has_not_read == 0) && (post.has_changed == false)) + { + notify_data_available = false; + } + trigger_waitsets = trigger_info_differs (&pre, &post) + && update_conditions_locked (rhc, &pre, &post, sample); + + assert (rhc_check_counts_locked (rhc, true)); + + os_mutexUnlock (&rhc->lock); + + if (notify_data_available && (trigger_info_differs (&pre, &post))) + { + if (rhc->reader && (rhc->reader->m_entity.m_status_enable & DDS_DATA_AVAILABLE_STATUS)) + { + os_atomic_inc32 (&rhc->n_cbs); + dds_reader_status_cb (&rhc->reader->m_entity, &dds_rhc_data_avail_cb_data); + os_atomic_dec32 (&rhc->n_cbs); + } + } + + if (rhc->reader && trigger_waitsets) + { + dds_entity_status_signal((dds_entity*)(rhc->reader)); + } + + return delivered; + +error_or_nochange: + + if (rhc->reliable && (stored == RHC_REJECTED)) + { + delivered = false; + } + + os_mutexUnlock (&rhc->lock); + TRACE ((")\n")); + + /* Make any reader status callback */ + + if (cb_data.status && rhc->reader && rhc->reader->m_entity.m_status_enable) + { + os_atomic_inc32 (&rhc->n_cbs); + dds_reader_status_cb (&rhc->reader->m_entity, &cb_data); + os_atomic_dec32 (&rhc->n_cbs); + } + + return delivered; +} + +void dds_rhc_unregister_wr +( + struct rhc * __restrict rhc, + const struct proxy_writer_info * __restrict pwr_info +) +{ + /* Only to be called when writer with ID WR_IID has died. + + If we require that it will NEVER be resurrected, i.e., that next + time a new WR_IID will be used for the same writer, then we have + all the time in the world to scan the cache & clean up and that + we don't have to keep it locked all the time (even if we do it + that way now). */ + bool trigger_waitsets = false; + struct rhc_instance *inst; + struct ut_hhIter iter; + const uint64_t wr_iid = pwr_info->iid; + const int auto_dispose = pwr_info->auto_dispose; + + os_mutexLock (&rhc->lock); + TRACE (("rhc_unregister_wr_iid(%"PRIx64",%d:\n", wr_iid, auto_dispose)); + for (inst = ut_hhIterFirst (rhc->instances, &iter); inst; inst = ut_hhIterNext (&iter)) + { + if (inst->wr_iid == wr_iid || lwregs_contains (&rhc->registrations, inst->iid, wr_iid)) + { + struct trigger_info pre, post; + get_trigger_info (&pre, inst, true); + + TRACE ((" %"PRIx64":", inst->iid)); + + assert (inst->wrcount > 0); + if (auto_dispose && !inst->isdisposed) + { + inst->isdisposed = true; + + /* Set invalid sample for disposing it (unregister may also set it for unregistering) */ + if (inst->latest) + { + assert (!inst->inv_exists); + rhc->n_not_alive_disposed++; + } + else + { + const bool was_empty = INST_IS_EMPTY (inst); + inst_set_invsample (rhc, inst); + update_inst (rhc, inst, pwr_info, inst->tstamp); + if (was_empty) + account_for_empty_to_nonempty_transition (rhc, inst); + else + rhc->n_not_alive_disposed++; + } + } + + dds_rhc_unregister (&post, rhc, inst, pwr_info, inst->tstamp); + + TRACE (("\n")); + + if (trigger_info_differs (&pre, &post)) + { + if (update_conditions_locked (rhc, &pre, &post, NULL)) + { + trigger_waitsets = true; + } + } + + assert (rhc_check_counts_locked (rhc, true)); + } + } + TRACE ((")\n")); + os_mutexUnlock (&rhc->lock); + + if (trigger_waitsets) + { + dds_entity_status_signal((dds_entity*)(rhc->reader)); + } +} + +void dds_rhc_relinquish_ownership (struct rhc * __restrict rhc, const uint64_t wr_iid) +{ + struct rhc_instance *inst; + struct ut_hhIter iter; + os_mutexLock (&rhc->lock); + TRACE (("rhc_relinquish_ownership(%"PRIx64":\n", wr_iid)); + for (inst = ut_hhIterFirst (rhc->instances, &iter); inst; inst = ut_hhIterNext (&iter)) + { + if (inst->wr_iid == wr_iid) + { + inst->wr_iid = 0; + } + } + TRACE ((")\n")); + assert (rhc_check_counts_locked (rhc, true)); + os_mutexUnlock (&rhc->lock); +} + +/* STATUSES: + + sample: ANY, READ, NOT_READ + view: ANY, NEW, NOT_NEW + instance: ANY, ALIVE, NOT_ALIVE, NOT_ALIVE_NO_WRITERS, NOT_ALIVE_DISPOSED +*/ + +static unsigned qmask_of_inst (const struct rhc_instance *inst) +{ + unsigned qm = inst->isnew ? DDS_NEW_VIEW_STATE : DDS_NOT_NEW_VIEW_STATE; + + if (inst->isdisposed) + qm |= DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE; + else if (inst->wrcount > 0) + qm |= DDS_ALIVE_INSTANCE_STATE; + else + qm |= DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE; + + return qm; +} + +static unsigned qmask_from_dcpsquery (unsigned sample_states, unsigned view_states, unsigned instance_states) +{ + unsigned qminv = 0; + + switch ((dds_sample_state_t) sample_states) + { + case DDS_SST_READ: + qminv |= DDS_NOT_READ_SAMPLE_STATE; + break; + case DDS_SST_NOT_READ: + qminv |= DDS_READ_SAMPLE_STATE; + break; + } + switch ((dds_view_state_t) view_states) + { + case DDS_VST_NEW: + qminv |= DDS_NOT_NEW_VIEW_STATE; + break; + case DDS_VST_OLD: + qminv |= DDS_NEW_VIEW_STATE; + break; + } + switch (instance_states) + { + case DDS_IST_ALIVE: + qminv |= DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE | DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE; + break; + case DDS_IST_ALIVE | DDS_IST_NOT_ALIVE_DISPOSED: + qminv |= DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE; + break; + case DDS_IST_ALIVE | DDS_IST_NOT_ALIVE_NO_WRITERS: + qminv |= DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE; + break; + case DDS_IST_NOT_ALIVE_DISPOSED: + qminv |= DDS_ALIVE_INSTANCE_STATE | DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE; + break; + case DDS_IST_NOT_ALIVE_DISPOSED | DDS_IST_NOT_ALIVE_NO_WRITERS: + qminv |= DDS_ALIVE_INSTANCE_STATE; + break; + case DDS_IST_NOT_ALIVE_NO_WRITERS: + qminv |= DDS_ALIVE_INSTANCE_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE; + break; + } + return qminv; +} + +static unsigned qmask_from_mask_n_cond(uint32_t mask, dds_readcond* cond) +{ + unsigned qminv; + if (mask == NO_STATE_MASK_SET) { + if (cond) { + /* No mask set, use the one from the condition. */ + qminv = cond->m_qminv; + } else { + /* No mask set and no condition: read all. */ + qminv = qmask_from_dcpsquery(DDS_ANY_SAMPLE_STATE, DDS_ANY_VIEW_STATE, DDS_ANY_INSTANCE_STATE); + } + } else { + /* Merge given mask with the condition mask when needed. */ + qminv = qmask_from_dcpsquery(mask & DDS_ANY_SAMPLE_STATE, mask & DDS_ANY_VIEW_STATE, mask & DDS_ANY_INSTANCE_STATE); + if (cond != NULL) { + qminv &= cond->m_qminv; + } + } + return qminv; +} + +static void set_sample_info (dds_sample_info_t *si, const struct rhc_instance *inst, const struct rhc_sample *sample) +{ + si->sample_state = sample->isread ? DDS_SST_READ : DDS_SST_NOT_READ; + si->view_state = inst->isnew ? DDS_VST_NEW : DDS_VST_OLD; + si->instance_state = inst->isdisposed ? DDS_IST_NOT_ALIVE_DISPOSED : (inst->wrcount == 0) ? DDS_IST_NOT_ALIVE_NO_WRITERS : DDS_IST_ALIVE; + si->instance_handle = inst->iid; + si->publication_handle = sample->wr_iid; + si->disposed_generation_count = sample->disposed_gen; + si->no_writers_generation_count = sample->no_writers_gen; + si->sample_rank = 0; /* patch afterward: don't know last sample in returned set yet */ + si->generation_rank = 0; /* __/ */ + si->absolute_generation_rank = (inst->disposed_gen + inst->no_writers_gen) - (sample->disposed_gen + sample->no_writers_gen); + si->valid_data = true; + si->source_timestamp = sample->sample->v.msginfo.timestamp.v; + si->reception_timestamp = sample->rtstamp.v; +} + +static void set_sample_info_invsample (dds_sample_info_t *si, const struct rhc_instance *inst) +{ + si->sample_state = inst->inv_isread ? DDS_SST_READ : DDS_SST_NOT_READ; + si->view_state = inst->isnew ? DDS_VST_NEW : DDS_VST_OLD; + si->instance_state = inst->isdisposed ? DDS_IST_NOT_ALIVE_DISPOSED : (inst->wrcount == 0) ? DDS_IST_NOT_ALIVE_NO_WRITERS : DDS_IST_ALIVE; + si->instance_handle = inst->iid; + si->publication_handle = inst->wr_iid; + si->disposed_generation_count = inst->disposed_gen; + si->no_writers_generation_count = inst->no_writers_gen; + si->sample_rank = 0; /* by construction always last in the set (but will get patched) */ + si->generation_rank = 0; /* __/ */ + si->absolute_generation_rank = 0; + si->valid_data = false; + si->source_timestamp = inst->tstamp.v; + + /* Not storing the underlying "sample" so the reception time is lost */ + si->reception_timestamp = 0; +} + +static void patch_generations (dds_sample_info_t *si, uint32_t last_of_inst) +{ + if (last_of_inst > 0) + { + const unsigned ref = + si[last_of_inst].disposed_generation_count + si[last_of_inst].no_writers_generation_count; + uint32_t i; + assert (si[last_of_inst].sample_rank == 0); + assert (si[last_of_inst].generation_rank == 0); + for (i = 0; i < last_of_inst; i++) + { + si[i].sample_rank = last_of_inst - i; + si[i].generation_rank = ref - (si[i].disposed_generation_count + si[i].no_writers_generation_count); + } + } +} + +static int dds_rhc_read_w_qminv +( + struct rhc *rhc, bool lock, void ** values, dds_sample_info_t *info_seq, + uint32_t max_samples, unsigned qminv, dds_instance_handle_t handle, dds_readcond *cond +) +{ + bool trigger_waitsets = false; + uint32_t n = 0; + const struct dds_topic_descriptor * desc = (const struct dds_topic_descriptor *) rhc->topic->type; + + if (lock) + { + os_mutexLock (&rhc->lock); + } + + TRACE (("read_w_qminv(%p,%p,%p,%u,%x) - inst %u nonempty %u disp %u nowr %u new %u samples %u+%u read %u+%u\n", + (void *) rhc, (void *) values, (void *) info_seq, max_samples, qminv, + rhc->n_instances, rhc->n_nonempty_instances, rhc->n_not_alive_disposed, + rhc->n_not_alive_no_writers, rhc->n_new, rhc->n_vsamples, rhc->n_invsamples, + rhc->n_vread, rhc->n_invread)); + + if (rhc->nonempty_instances) + { + struct rhc_instance * inst = rhc->nonempty_instances->next; + struct rhc_instance * const end = inst; + do + { + if (handle == DDS_HANDLE_NIL || inst->iid == handle) + { + if (!INST_IS_EMPTY (inst) && (qmask_of_inst (inst) & qminv) == 0) + { + /* samples present & instance, view state matches */ + struct trigger_info pre, post; + const unsigned nread = INST_NREAD (inst); + const uint32_t n_first = n; + get_trigger_info (&pre, inst, true); + + if (inst->latest) + { + struct rhc_sample *sample = inst->latest->next, * const end1 = sample; + do + { + if ((QMASK_OF_SAMPLE (sample) & qminv) == 0) + { + /* sample state matches too */ + set_sample_info (info_seq + n, inst, sample); + deserialize_into ((char*) values[n], sample->sample); + if (cond == NULL + || (dds_entity_kind(cond->m_entity.m_hdl) != DDS_KIND_COND_QUERY) + || (cond->m_query.m_filter != NULL && cond->m_query.m_filter(values[n]))) + { + if (!sample->isread) + { + TRACE (("s")); + sample->isread = true; + inst->nvread++; + rhc->n_vread++; + } + + if (++n == max_samples) + { + break; + } + } + else + { + /* The filter didn't match, so free the deserialised copy. */ + dds_sample_free(values[n], desc, DDS_FREE_CONTENTS); + } + } + sample = sample->next; + } + while (sample != end1); + } + + if (inst->inv_exists && n < max_samples && (QMASK_OF_INVSAMPLE (inst) & qminv) == 0) + { + set_sample_info_invsample (info_seq + n, inst); + deserialize_into ((char*) values[n], inst->tk->m_sample); + if (!inst->inv_isread) + { + inst->inv_isread = 1; + rhc->n_invread++; + } + ++n; + } + + if (n > n_first && inst->isnew) + { + inst->isnew = false; + rhc->n_new--; + } + if (nread != INST_NREAD (inst)) + { + get_trigger_info (&post, inst, false); + if (update_conditions_locked (rhc, &pre, &post, NULL)) + { + trigger_waitsets = true; + } + } + + if (n > n_first) { + patch_generations (info_seq + n_first, n - n_first - 1); + } + } + if (inst->iid == handle) + { + break; + } + } + inst = inst->next; + } + while (inst != end && n < max_samples); + } + TRACE (("read: returning %u\n", n)); + assert (rhc_check_counts_locked (rhc, true)); + os_mutexUnlock (&rhc->lock); + + if (trigger_waitsets) + { + dds_entity_status_signal((dds_entity*)(rhc->reader)); + } + + return n; +} + +static int dds_rhc_take_w_qminv +( + struct rhc *rhc, bool lock, void ** values, dds_sample_info_t *info_seq, + uint32_t max_samples, unsigned qminv, dds_instance_handle_t handle, dds_readcond *cond +) +{ + bool trigger_waitsets = false; + uint64_t iid; + uint32_t n = 0; + const struct dds_topic_descriptor * desc = (const struct dds_topic_descriptor *) rhc->topic->type; + + if (lock) + { + os_mutexLock (&rhc->lock); + } + + TRACE (("take_w_qminv(%p,%p,%p,%u,%x) - inst %u nonempty %u disp %u nowr %u new %u samples %u+%u read %u+%u\n", + (void*) rhc, (void*) values, (void*) info_seq, max_samples, qminv, + rhc->n_instances, rhc->n_nonempty_instances, rhc->n_not_alive_disposed, + rhc->n_not_alive_no_writers, rhc->n_new, rhc->n_vsamples, + rhc->n_invsamples, rhc->n_vread, rhc->n_invread)); + + if (rhc->nonempty_instances) + { + struct rhc_instance *inst = rhc->nonempty_instances->next; + unsigned n_insts = rhc->n_nonempty_instances; + while (n_insts-- > 0 && n < max_samples) + { + struct rhc_instance * const inst1 = inst->next; + iid = inst->iid; + if (handle == DDS_HANDLE_NIL || iid == handle) + { + if (!INST_IS_EMPTY (inst) && (qmask_of_inst (inst) & qminv) == 0) + { + struct trigger_info pre, post; + unsigned nvsamples = inst->nvsamples; + const uint32_t n_first = n; + get_trigger_info (&pre, inst, true); + + if (inst->latest) + { + struct rhc_sample * psample = inst->latest; + struct rhc_sample * sample = psample->next; + while (nvsamples--) + { + struct rhc_sample * const sample1 = sample->next; + + if ((QMASK_OF_SAMPLE (sample) & qminv) != 0) + { + psample = sample; + } + else + { + set_sample_info (info_seq + n, inst, sample); + deserialize_into ((char*) values[n], sample->sample); + if (cond == NULL + || (dds_entity_kind(cond->m_entity.m_hdl) != DDS_KIND_COND_QUERY) + || ( cond->m_query.m_filter != NULL && cond->m_query.m_filter(values[n]))) + { + rhc->n_vsamples--; + if (sample->isread) + { + inst->nvread--; + rhc->n_vread--; + } + + if (--inst->nvsamples > 0) + { + if (inst->latest == sample) { + inst->latest = psample; + } + psample->next = sample1; + } + else + { + inst->latest = NULL; + } + + free_sample (inst, sample); + + if (++n == max_samples) + { + break; + } + } + else + { + /* The filter didn't match, so free the deserialised copy. */ + dds_sample_free(values[n], desc, DDS_FREE_CONTENTS); + } + } + sample = sample1; + } + } + + if (inst->inv_exists && n < max_samples && (QMASK_OF_INVSAMPLE (inst) & qminv) == 0) + { + set_sample_info_invsample (info_seq + n, inst); + deserialize_into ((char*) values[n], inst->tk->m_sample); + inst_clear_invsample (rhc, inst); + ++n; + } + + if (n > n_first && inst->isnew) + { + inst->isnew = false; + rhc->n_new--; + } + + if (n > n_first) + { + /* if nsamples = 0, it won't match anything, so no need to do + anything here for drop_instance_noupdate_no_writers */ + get_trigger_info (&post, inst, false); + if (update_conditions_locked (rhc, &pre, &post, NULL)) + { + trigger_waitsets = true; + } + } + + if (INST_IS_EMPTY (inst)) + { + remove_inst_from_nonempty_list (rhc, inst); + + if (inst->isdisposed) + { + rhc->n_not_alive_disposed--; + } + if (inst->wrcount == 0) + { + TRACE (("take: iid %"PRIx64" #0,empty,drop\n", iid)); + if (!inst->isdisposed) + { + /* disposed has priority over no writers (why not just 2 bits?) */ + rhc->n_not_alive_no_writers--; + } + drop_instance_noupdate_no_writers (rhc, inst); + } + } + + if (n > n_first) { + patch_generations (info_seq + n_first, n - n_first - 1); + } + } + if (iid == handle) + { + break; + } + } + inst = inst1; + } + } + TRACE (("take: returning %u\n", n)); + assert (rhc_check_counts_locked (rhc, true)); + os_mutexUnlock (&rhc->lock); + + if (trigger_waitsets) + { + dds_entity_status_signal((dds_entity*)(rhc->reader)); + } + + return n; +} + +static int dds_rhc_takecdr_w_qminv +( + struct rhc *rhc, bool lock, struct serdata ** values, dds_sample_info_t *info_seq, + uint32_t max_samples, unsigned qminv, dds_instance_handle_t handle, dds_readcond *cond + ) +{ + bool trigger_waitsets = false; + uint64_t iid; + uint32_t n = 0; + + if (lock) + { + os_mutexLock (&rhc->lock); + } + + TRACE (("take_w_qminv(%p,%p,%p,%u,%x) - inst %u nonempty %u disp %u nowr %u new %u samples %u+%u read %u+%u\n", + (void*) rhc, (void*) values, (void*) info_seq, max_samples, qminv, + rhc->n_instances, rhc->n_nonempty_instances, rhc->n_not_alive_disposed, + rhc->n_not_alive_no_writers, rhc->n_new, rhc->n_vsamples, + rhc->n_invsamples, rhc->n_vread, rhc->n_invread)); + + if (rhc->nonempty_instances) + { + struct rhc_instance *inst = rhc->nonempty_instances->next; + unsigned n_insts = rhc->n_nonempty_instances; + while (n_insts-- > 0 && n < max_samples) + { + struct rhc_instance * const inst1 = inst->next; + iid = inst->iid; + if (handle == DDS_HANDLE_NIL || iid == handle) + { + if (!INST_IS_EMPTY (inst) && (qmask_of_inst (inst) & qminv) == 0) + { + struct trigger_info pre, post; + unsigned nvsamples = inst->nvsamples; + const uint32_t n_first = n; + get_trigger_info (&pre, inst, true); + + if (inst->latest) + { + struct rhc_sample * psample = inst->latest; + struct rhc_sample * sample = psample->next; + while (nvsamples--) + { + struct rhc_sample * const sample1 = sample->next; + + if ((QMASK_OF_SAMPLE (sample) & qminv) != 0) + { + psample = sample; + } + else + { + set_sample_info (info_seq + n, inst, sample); + values[n] = ddsi_serdata_ref(sample->sample); + rhc->n_vsamples--; + if (sample->isread) + { + inst->nvread--; + rhc->n_vread--; + } + free_sample (inst, sample); + + if (--inst->nvsamples > 0) + { + psample->next = sample1; + } + else + { + inst->latest = NULL; + } + + if (++n == max_samples) + { + break; + } + } + sample = sample1; + } + } + + if (inst->inv_exists && n < max_samples && (QMASK_OF_INVSAMPLE (inst) & qminv) == 0) + { + set_sample_info_invsample (info_seq + n, inst); + values[n] = ddsi_serdata_ref(inst->tk->m_sample); + inst_clear_invsample (rhc, inst); + ++n; + } + + if (n > n_first && inst->isnew) + { + inst->isnew = false; + rhc->n_new--; + } + + if (n > n_first) + { + /* if nsamples = 0, it won't match anything, so no need to do + anything here for drop_instance_noupdate_no_writers */ + get_trigger_info (&post, inst, false); + if (update_conditions_locked (rhc, &pre, &post, NULL)) + { + trigger_waitsets = true; + } + } + + if (INST_IS_EMPTY (inst)) + { + remove_inst_from_nonempty_list (rhc, inst); + + if (inst->isdisposed) + { + rhc->n_not_alive_disposed--; + } + if (inst->wrcount == 0) + { + TRACE (("take: iid %"PRIx64" #0,empty,drop\n", iid)); + if (!inst->isdisposed) + { + /* disposed has priority over no writers (why not just 2 bits?) */ + rhc->n_not_alive_no_writers--; + } + drop_instance_noupdate_no_writers (rhc, inst); + } + } + + if (n > n_first) { + patch_generations (info_seq + n_first, n - n_first - 1); + } + } + if (iid == handle) + { + break; + } + } + inst = inst1; + } + } + TRACE (("take: returning %u\n", n)); + assert (rhc_check_counts_locked (rhc, true)); + os_mutexUnlock (&rhc->lock); + + if (trigger_waitsets) + { + dds_entity_status_signal((dds_entity*)(rhc->reader)); + } + + return n; +} + +/************************* + ****** WAITSET ****** + *************************/ + +static uint32_t rhc_get_cond_trigger (struct rhc_instance * const inst, const dds_readcond * const c) +{ + bool m = ((qmask_of_inst (inst) & c->m_qminv) == 0); + switch (c->m_sample_states) + { + case DDS_SST_READ: + m = m && INST_HAS_READ (inst); + break; + case DDS_SST_NOT_READ: + m = m && INST_HAS_UNREAD (inst); + break; + case DDS_SST_READ | DDS_SST_NOT_READ: + case 0: + /* note: we get here only if inst not empty, so this is a no-op */ + m = m && !INST_IS_EMPTY (inst); + break; + default: + NN_FATAL ("update_readconditions: sample_states invalid: %x\n", c->m_sample_states); + } + return m ? 1 : 0; +} + +void dds_rhc_add_readcondition (dds_readcond * cond) +{ + /* On the assumption that a readcondition will be attached to a + waitset for nearly all of its life, we keep track of all + readconditions on a reader in one set, without distinguishing + between those attached to a waitset or not. */ + + struct rhc * rhc = cond->m_rhc; + struct ut_hhIter iter; + struct rhc_instance * inst; + + cond->m_qminv = qmask_from_dcpsquery (cond->m_sample_states, cond->m_view_states, cond->m_instance_states); + + os_mutexLock (&rhc->lock); + for (inst = ut_hhIterFirst (rhc->instances, &iter); inst; inst = ut_hhIterNext (&iter)) + { + if (dds_entity_kind(cond->m_entity.m_hdl) == DDS_KIND_COND_READ) + { + ((dds_entity*)cond)->m_trigger += rhc_get_cond_trigger (inst, cond); + if (((dds_entity*)cond)->m_trigger) { + dds_entity_status_signal((dds_entity*)cond); + } + } + } + os_mutexLock (&rhc->conds_lock); + cond->m_rhc_next = rhc->conds; + rhc->nconds++; + rhc->conds = cond; + + TRACE (("add_readcondition(%p, %x, %x, %x) => %p qminv %x ; rhc %u conds\n", + (void *) rhc, cond->m_sample_states, cond->m_view_states, + cond->m_instance_states, cond, cond->m_qminv, rhc->nconds)); + + os_mutexUnlock (&rhc->conds_lock); + os_mutexUnlock (&rhc->lock); +} + +void dds_rhc_remove_readcondition (dds_readcond * cond) +{ + struct rhc * rhc = cond->m_rhc; + dds_readcond * iter; + dds_readcond * prev = NULL; + + os_mutexLock (&rhc->lock); + os_mutexLock (&rhc->conds_lock); + iter = rhc->conds; + while (iter) + { + if (iter == cond) + { + if (prev) + { + prev->m_rhc_next = iter->m_rhc_next; + } + else + { + rhc->conds = iter->m_rhc_next; + } + rhc->nconds--; + break; + } + prev = iter; + iter = iter->m_rhc_next; + } + os_mutexUnlock (&rhc->conds_lock); + os_mutexUnlock (&rhc->lock); +} + +static bool update_conditions_locked +( + struct rhc *rhc, const struct trigger_info *pre, + const struct trigger_info *post, + const struct serdata *sample +) +{ + /* Pre: rhc->lock held; returns 1 if triggering required, else 0. */ + bool trigger = false; + dds_readcond * iter; + int m_pre; + int m_post; + bool deserialised = (rhc->topic->filter_fn != NULL); + + TRACE (("update_conditions_locked(%p) - inst %u nonempty %u disp %u nowr %u new %u samples %u read %u\n", + (void *) rhc, rhc->n_instances, rhc->n_nonempty_instances, rhc->n_not_alive_disposed, + rhc->n_not_alive_no_writers, rhc->n_new, rhc->n_vsamples, rhc->n_vread)); + + assert (rhc->n_nonempty_instances >= rhc->n_not_alive_disposed + rhc->n_not_alive_no_writers); + assert (rhc->n_nonempty_instances >= rhc->n_new); + assert (rhc->n_vsamples >= rhc->n_vread); + + iter = rhc->conds; + while (iter) + { + m_pre = ((pre->qminst & iter->m_qminv) == 0); + m_post = ((post->qminst & iter->m_qminv) == 0); + + /* FIXME: use bitmask? */ + switch (iter->m_sample_states) + { + case DDS_SST_READ: + m_pre = m_pre && pre->has_read; + m_post = m_post && post->has_read; + break; + case DDS_SST_NOT_READ: + m_pre = m_pre && pre->has_not_read; + m_post = m_post && post->has_not_read; + break; + case DDS_SST_READ | DDS_SST_NOT_READ: + case 0: + m_pre = m_pre && (pre->has_read + pre->has_not_read); + m_post = m_post && (post->has_read + post->has_not_read); + break; + default: + NN_FATAL ("update_readconditions: sample_states invalid: %x\n", iter->m_sample_states); + } + + TRACE ((" cond %p: ", (void *) iter)); + if (m_pre == m_post) + { + TRACE (("no change")); + } + else if (m_pre < m_post) + { + if (sample && !deserialised && (dds_entity_kind(iter->m_entity.m_hdl) == DDS_KIND_COND_QUERY)) + { + deserialize_into ((char*)rhc->topic->filter_sample, sample); + deserialised = true; + } + if + ( + (sample == NULL) + || (dds_entity_kind(iter->m_entity.m_hdl) != DDS_KIND_COND_QUERY) + || (iter->m_query.m_filter != NULL && iter->m_query.m_filter (rhc->topic->filter_sample)) + ) + { + TRACE (("now matches")); + if (iter->m_entity.m_trigger++ == 0) + { + TRACE ((" (cond now triggers)")); + trigger = true; + } + } + } + else + { + TRACE (("no longer matches")); + if (--iter->m_entity.m_trigger == 0) + { + TRACE ((" (cond no longer triggers)")); + } + } + if (iter->m_entity.m_trigger) { + dds_entity_status_signal(&(iter->m_entity)); + } + + TRACE (("\n")); + iter = iter->m_rhc_next; + } + + return trigger; +} + + +/************************* + ****** READ/TAKE ****** + *************************/ + +int +dds_rhc_read( + struct rhc *rhc, + bool lock, + void ** values, + dds_sample_info_t *info_seq, + uint32_t max_samples, + uint32_t mask, + dds_instance_handle_t handle, + dds_readcond *cond) +{ + unsigned qminv = qmask_from_mask_n_cond(mask, cond); + return dds_rhc_read_w_qminv(rhc, lock, values, info_seq, max_samples, qminv, handle, cond); +} + +int +dds_rhc_take( + struct rhc *rhc, + bool lock, + void ** values, + dds_sample_info_t *info_seq, + uint32_t max_samples, + uint32_t mask, + dds_instance_handle_t handle, + dds_readcond *cond) +{ + unsigned qminv = qmask_from_mask_n_cond(mask, cond); + return dds_rhc_take_w_qminv(rhc, lock, values, info_seq, max_samples, qminv, handle, cond); +} + +int dds_rhc_takecdr +( + struct rhc *rhc, bool lock, struct serdata ** values, dds_sample_info_t *info_seq, uint32_t max_samples, + unsigned sample_states, unsigned view_states, unsigned instance_states, dds_instance_handle_t handle) +{ + unsigned qminv = qmask_from_dcpsquery (sample_states, view_states, instance_states); + return dds_rhc_takecdr_w_qminv (rhc, lock, values, info_seq, max_samples, qminv, handle, NULL); +} + +/************************* + ****** CHECK ****** + *************************/ + +#ifndef NDEBUG +#define CHECK_MAX_CONDS 64 +static int rhc_check_counts_locked (struct rhc *rhc, bool check_conds) +{ + unsigned n_instances = 0, n_nonempty_instances = 0; + unsigned n_not_alive_disposed = 0, n_not_alive_no_writers = 0, n_new = 0; + unsigned n_vsamples = 0, n_vread = 0; + unsigned n_invsamples = 0, n_invread = 0; + unsigned cond_match_count[CHECK_MAX_CONDS]; + struct rhc_instance *inst; + struct ut_hhIter iter; + uint32_t i; + + for (i = 0; i < CHECK_MAX_CONDS; i++) + cond_match_count[i] = 0; + + for (inst = ut_hhIterFirst (rhc->instances, &iter); inst; inst = ut_hhIterNext (&iter)) + { + n_instances++; + if (!INST_IS_EMPTY (inst)) + { + /* samples present (or an invalid sample is) */ + unsigned n_vsamples_in_instance = 0, n_read_vsamples_in_instance = 0; + bool a_sample_free = true; + + n_nonempty_instances++; + if (inst->isdisposed) + { + n_not_alive_disposed++; + } + else if (inst->wrcount == 0) + n_not_alive_no_writers++; + if (inst->isnew) + { + n_new++; + } + + if (inst->latest) + { + struct rhc_sample *sample = inst->latest->next, * const end = sample; + do { + if (sample == &inst->a_sample) + { + assert (a_sample_free); + a_sample_free = false; + } + n_vsamples++; + n_vsamples_in_instance++; + if (sample->isread) + { + n_vread++; + n_read_vsamples_in_instance++; + } + sample = sample->next; + } while (sample != end); + } + + if (inst->inv_exists) + { + n_invsamples++; + n_invread += inst->inv_isread; + } + + assert (n_read_vsamples_in_instance == inst->nvread); + assert (n_vsamples_in_instance == inst->nvsamples); + assert (a_sample_free == inst->a_sample_free); + + { + dds_readcond * rciter = rhc->conds; + for (i = 0; i < (rhc->nconds < CHECK_MAX_CONDS ? rhc->nconds : CHECK_MAX_CONDS); i++) + { + if (dds_entity_kind(rciter->m_entity.m_hdl) == DDS_KIND_COND_READ) + { + cond_match_count[i] += rhc_get_cond_trigger (inst, rciter); + } + rciter = rciter->m_rhc_next; + } + } + } + } + + assert (rhc->n_instances == n_instances); + assert (rhc->n_nonempty_instances == n_nonempty_instances); + assert (rhc->n_not_alive_disposed == n_not_alive_disposed); + assert (rhc->n_not_alive_no_writers == n_not_alive_no_writers); + assert (rhc->n_new == n_new); + assert (rhc->n_vsamples == n_vsamples); + assert (rhc->n_vread == n_vread); + assert (rhc->n_invsamples == n_invsamples); + assert (rhc->n_invread == n_invread); + + if (check_conds) + { + dds_readcond * rciter = rhc->conds; + for (i = 0; i < (rhc->nconds < CHECK_MAX_CONDS ? rhc->nconds : CHECK_MAX_CONDS); i++) + { + if (dds_entity_kind(rciter->m_entity.m_hdl) == DDS_KIND_COND_READ) + { + assert (cond_match_count[i] == rciter->m_entity.m_trigger); + } + rciter = rciter->m_rhc_next; + } + } + + if (rhc->n_nonempty_instances == 0) + { + assert (rhc->nonempty_instances == NULL); + } + else + { + struct rhc_instance *prev, *end; + assert (rhc->nonempty_instances != NULL); + prev = rhc->nonempty_instances->prev; + end = rhc->nonempty_instances; + inst = rhc->nonempty_instances; + n_nonempty_instances = 0; + do { + assert (!INST_IS_EMPTY (inst)); + assert (prev->next == inst); + assert (inst->prev == prev); + prev = inst; + inst = inst->next; + n_nonempty_instances++; + } while (inst != end); + assert (rhc->n_nonempty_instances == n_nonempty_instances); + } + + return 1; +} +#undef CHECK_MAX_CONDS +#endif diff --git a/src/core/ddsc/src/dds_stream.c b/src/core/ddsc/src/dds_stream.c new file mode 100644 index 0000000..bbe749b --- /dev/null +++ b/src/core/ddsc/src/dds_stream.c @@ -0,0 +1,1670 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include "ddsi/q_bswap.h" +#include "ddsi/q_config.h" +#include "dds__stream.h" +#include "dds__key.h" +#include "dds__alloc.h" +#include "os/os.h" + +/* +#define OP_DEBUG_READ 1 +#define OP_DEBUG_WRITE 1 +#define OP_DEBUG_KEY 1 +*/ + +#if defined OP_DEBUG_WRITE || defined OP_DEBUG_READ || defined OP_DEBUG_KEY +static const char * stream_op_type[11] = +{ + NULL, "1Byte", "2Byte", "4Byte", "8Byte", "String", + "BString", "Sequence", "Array", "Union", "Struct" +}; +#endif + +#if OS_ENDIANNESS == OS_LITTLE_ENDIAN +#define DDS_ENDIAN true +#else +#define DDS_ENDIAN false +#endif + +const uint32_t dds_op_size[5] = { 0, 1u, 2u, 4u, 8u }; + +static void dds_stream_write + (dds_stream_t * os, const char * data, const uint32_t * ops); +static void dds_stream_read + (dds_stream_t * is, char * data, const uint32_t * ops); + +#define DDS_SWAP16(v) \ + (((v) >> 8) | ((v) << 8)) +#define DDS_SWAP32(v) \ + (((v) >> 24) | \ + (((v) & 0x00ff0000) >> 8) | \ + (((v) & 0x0000ff00) << 8) | \ + ((v) << 24)) +#define DDS_SWAP64(v) \ + (((v)) >> 56) | \ + ((((v)) & 0x00ff000000000000) >> 40) | \ + ((((v)) & 0x0000ff0000000000) >> 24) | \ + ((((v)) & 0x000000ff00000000) >> 8) | \ + ((((v)) & 0x00000000ff000000) << 8) | \ + ((((v)) & 0x0000000000ff0000) << 24) | \ + ((((v)) & 0x000000000000ff00) << 40) | \ + (((v)) << 56) + +#define DDS_CDR_ALIGN2(s) ((s)->m_index = ((s)->m_index + 1U) & ~1U) +#define DDS_CDR_ALIGN4(s) ((s)->m_index = ((s)->m_index + 3U) & ~3U) +#define DDS_CDR_ALIGN8(s) ((s)->m_index = ((s)->m_index + 7U) & ~7U) +#define DDS_CDR_ALIGNTO(s,n) ((s)->m_index = ((s)->m_index + (n-1)) & ~(n-1)) +#define DDS_CDR_ALIGNED(s,n) ((n) && ((s)->m_index % (n)) == 0) + +#define DDS_CDR_ADDRESS(c, type) ((type*) &((c)->m_buffer.p8[(c)->m_index])) +#define DDS_CDR_RESET(c) \ + (c)->m_index = 0ul; \ + (c)->m_failed = false; +#define DDS_CDR_RESIZE(c,l) if (((c)->m_size < ((l) + (c)->m_index))) dds_stream_grow (c, l) +#define DDS_CDR_REINIT(c,s) \ + DDS_CDR_RESET(c); \ + (c)->m_endian = DDS_ENDIAN; \ + if ((c)->m_size < (s)) dds_stream_grow (c, s) + +#ifndef NDEBUG +#define DDS_IS_OK(s,n) (!((s)->m_failed = ((s)->m_index + (n)) > (s)->m_size)) +#else +#define DDS_IS_OK(s,n) (true) +#endif + +#define DDS_IS_GET1(s) (s)->m_buffer.p8[(s)->m_index++] + +#define DDS_IS_GET2(s,v) \ + (v) = *DDS_CDR_ADDRESS ((s), uint16_t); \ + if ((s)->m_endian != DDS_ENDIAN) (v) = DDS_SWAP16 ((v)); \ + (s)->m_index += 2 + +#define DDS_IS_GET4(s,v,t) \ + (v) = *DDS_CDR_ADDRESS ((s), t); \ + if ((s)->m_endian != DDS_ENDIAN) (v) = (t)(DDS_SWAP32 ((uint32_t) (v))); \ + (s)->m_index += 4; + +#define DDS_IS_GET8(s,v,t) \ + (v) = *DDS_CDR_ADDRESS ((s), t); \ + if ((s)->m_endian != DDS_ENDIAN) (v) = (t)(DDS_SWAP64 ((uint64_t) (v))); \ + (s)->m_index += 8 + +#define DDS_IS_GET_BYTES(s,b,l) \ + memcpy (b, DDS_CDR_ADDRESS ((s), void), l); \ + (s)->m_index += (l) + +#define DDS_OS_PUT1(s,v) \ + DDS_CDR_RESIZE (s, 1u); \ + *DDS_CDR_ADDRESS (s, uint8_t) = v; \ + (s)->m_index += 1 + +#define DDS_OS_PUT2(s,v) \ + DDS_CDR_ALIGN2 (s); \ + DDS_CDR_RESIZE (s, 2u); \ + *DDS_CDR_ADDRESS (s, uint16_t) = ((s)->m_endian == DDS_ENDIAN) ? \ + v : DDS_SWAP16 ((uint16_t) (v)); \ + (s)->m_index += 2 + +#define DDS_OS_PUT4(s,v,t) \ + DDS_CDR_ALIGN4 (s); \ + DDS_CDR_RESIZE (s, 4u); \ + *DDS_CDR_ADDRESS (s, t) = ((s)->m_endian == DDS_ENDIAN) ? \ + v : DDS_SWAP32 ((uint32_t) (v)); \ + (s)->m_index += 4 + +#define DDS_OS_PUT8(s,v,t) \ + DDS_CDR_ALIGN8 (s); \ + DDS_CDR_RESIZE (s, 8u); \ + *DDS_CDR_ADDRESS (s, t) = ((s)->m_endian == DDS_ENDIAN) ? \ + v : DDS_SWAP64 ((uint64_t) (v)); \ + (s)->m_index += 8 + +#define DDS_OS_PUT_BYTES(s,b,l) \ + DDS_CDR_RESIZE (s, l); \ + memcpy (DDS_CDR_ADDRESS (s, void), b, l); \ + (s)->m_index += (l) + +bool dds_stream_endian (void) +{ + return DDS_ENDIAN; +} + +size_t dds_stream_check_optimize (_In_ const dds_topic_descriptor_t * desc) +{ + dds_stream_t os; + void * sample = dds_alloc (desc->m_size); + uint8_t * ptr1; + uint8_t * ptr2; + size_t size = desc->m_size; + uint8_t val = 1; + + dds_stream_init (&os, size); + ptr1 = (uint8_t*) sample; + ptr2 = os.m_buffer.p8; + while (size--) + { + *ptr1++ = val; + *ptr2++ = val++; + } + + dds_stream_write (&os, sample, desc->m_ops); + size = (memcmp (sample, os.m_buffer.p8, desc->m_size) == 0) ? os.m_index : 0; + + dds_sample_free_contents (sample, desc->m_ops); + dds_free (sample); + dds_stream_fini (&os); + dds_log_info ("Marshalling for type: %s is%s optimised\n", desc->m_typename, size ? "" : " not"); + return size; +} + +dds_stream_t * dds_stream_create (size_t size) +{ + dds_stream_t * stream = (dds_stream_t*) dds_alloc (sizeof (*stream)); + dds_stream_init (stream, size); + return stream; +} + +void dds_stream_delete (dds_stream_t * st) +{ + dds_stream_fini (st); + dds_free (st); +} + +void dds_stream_fini (dds_stream_t * st) +{ + if (st->m_size) + { + dds_free (st->m_buffer.p8); + } +} + +void dds_stream_init (dds_stream_t * st, size_t size) +{ + memset (st, 0, sizeof (*st)); + DDS_CDR_REINIT (st, size); +} + +void dds_stream_reset (dds_stream_t * st) +{ + DDS_CDR_RESET (st); +} + +void dds_stream_grow (dds_stream_t * st, size_t size) +{ + size_t needed = size + st->m_index; + + /* Reallocate on 4k boundry */ + + size_t newSize = (needed & ~0xfff) + 0x1000; + uint8_t * old = st->m_buffer.p8; + + st->m_buffer.p8 = dds_realloc (old, newSize); + memset (st->m_buffer.p8 + st->m_size, 0, newSize - st->m_size); + st->m_size = (int32_t) newSize; +} + +bool dds_stream_read_bool (dds_stream_t * is) +{ + return (dds_stream_read_uint8 (is) != 0); +} + +uint8_t dds_stream_read_uint8 (dds_stream_t * is) +{ + return DDS_IS_OK (is, 1) ? DDS_IS_GET1 (is) : 0; +} + +uint16_t dds_stream_read_uint16 (dds_stream_t * is) +{ + uint16_t val = 0; + DDS_CDR_ALIGN2 (is); + if (DDS_IS_OK (is, 2)) + { + DDS_IS_GET2 (is, val); + } + return val; +} + +uint32_t dds_stream_read_uint32 (dds_stream_t * is) +{ + uint32_t val = 0; + DDS_CDR_ALIGN4 (is); + if (DDS_IS_OK (is, 4)) + { + DDS_IS_GET4 (is, val, uint32_t); + } + return val; +} + +uint64_t dds_stream_read_uint64 (dds_stream_t * is) +{ + uint64_t val = 0; + DDS_CDR_ALIGN8 (is); + if (DDS_IS_OK (is, 8)) + { + DDS_IS_GET8 (is, val, uint64_t); + } + return val; +} + +float dds_stream_read_float (dds_stream_t * is) +{ + float val = 0.0; + DDS_CDR_ALIGN4 (is); + if (DDS_IS_OK (is, 4)) + { + DDS_IS_GET4 (is, val, float); + } + return val; +} + +double dds_stream_read_double (dds_stream_t * is) +{ + double val = 0.0; + DDS_CDR_ALIGN8 (is); + if (DDS_IS_OK (is, 8)) + { + DDS_IS_GET8 (is, val, double); + } + return val; +} + +char * dds_stream_reuse_string + (dds_stream_t * is, char * str, const uint32_t bound) +{ + uint32_t length; + void * src; + + DDS_CDR_ALIGN4 (is); + if (DDS_IS_OK (is, 4)) + { + DDS_IS_GET4 (is, length, uint32_t); + if (DDS_IS_OK (is, length)) + { + src = DDS_CDR_ADDRESS (is, void); + if (bound) + { + memcpy (str, src, length > bound ? bound : length); + } + else + { + if ((str == NULL) || (strlen (str) < length)) + { + str = dds_realloc (str, length); + } + memcpy (str, src, length); + } + is->m_index += length; + } + } + + return str; +} + +char * dds_stream_read_string (dds_stream_t * is) +{ + return dds_stream_reuse_string (is, NULL, 0); +} + +void dds_stream_swap (void * buff, uint32_t size, uint32_t num) +{ + assert (size == 2 || size == 4 || size == 8); + + switch (size) + { + case 2: + { + uint16_t * ptr = (uint16_t*) buff; + while (num--) + { + *ptr = DDS_SWAP16 (*ptr); + ptr++; + } + break; + } + case 4: + { + uint32_t * ptr = (uint32_t*) buff; + while (num--) + { + *ptr = DDS_SWAP32 (*ptr); + ptr++; + } + break; + } + default: + { + uint64_t * ptr = (uint64_t*) buff; + while (num--) + { + *ptr = DDS_SWAP64 (*ptr); + ptr++; + } + break; + } + } +} + +static void dds_stream_read_fixed_buffer + (dds_stream_t * is, void * buff, uint32_t len, const uint32_t size, const bool swap) +{ + if (size && len) + { + DDS_CDR_ALIGNTO (is, size); + DDS_IS_GET_BYTES (is, buff, len * size); + if (swap && (size > 1)) + { + dds_stream_swap (buff, size, len); + } + } +} + +void dds_stream_read_buffer (dds_stream_t * is, uint8_t * buffer, uint32_t len) +{ + if (DDS_IS_OK (is, len)) + { + DDS_IS_GET_BYTES (is, buffer, len); + } +} + +void dds_stream_read_sample (dds_stream_t * is, void * data, const struct sertopic * topic) +{ + const struct dds_topic_descriptor * desc = (const struct dds_topic_descriptor *) topic->type; + + { + /* Check if can copy directly from stream buffer */ + + if (topic->opt_size && DDS_IS_OK (is, desc->m_size) && (is->m_endian == DDS_ENDIAN)) + { + DDS_IS_GET_BYTES (is, data, desc->m_size); + } + else + { + dds_stream_read (is, data, desc->m_ops); + } + } +} + +void dds_stream_write_bool (dds_stream_t * os, bool val) +{ + dds_stream_write_uint8 (os, val ? 1 : 0); +} + +void dds_stream_write_uint8 (dds_stream_t * os, uint8_t val) +{ + DDS_OS_PUT1 (os, val); +} + +void dds_stream_write_uint16 (dds_stream_t * os, uint16_t val) +{ + DDS_OS_PUT2 (os, val); +} + +void dds_stream_write_uint32 (dds_stream_t * os, uint32_t val) +{ + DDS_OS_PUT4 (os, val, uint32_t); +} + +void dds_stream_write_uint64 (dds_stream_t * os, uint64_t val) +{ + DDS_OS_PUT8 (os, val, uint64_t); +} + +void dds_stream_write_float (dds_stream_t * os, float val) +{ + DDS_OS_PUT4 (os, val, float); +} + +void dds_stream_write_double (dds_stream_t * os, double val) +{ + DDS_OS_PUT8 (os, val, double); +} + +void dds_stream_write_string (dds_stream_t * os, const char * val) +{ + uint32_t size = 1; + + if (val) + { + size += (uint32_t)strlen (val); /* Type casting is done for the warning of conversion from 'size_t' to 'uint32_t', which may cause possible loss of data */ + } + + DDS_OS_PUT4 (os, size, uint32_t); + + if (val) + { + DDS_OS_PUT_BYTES (os, (uint8_t*) val, size); + } + else + { + DDS_OS_PUT1 (os, 0U); + } +} + +void dds_stream_write_buffer (dds_stream_t * os, uint32_t len, uint8_t * buffer) +{ + DDS_OS_PUT_BYTES (os, buffer, len); +} + +static void dds_stream_write +( + dds_stream_t * os, + const char * data, + const uint32_t * ops +) +{ + uint32_t align; + uint32_t op; + uint32_t type; + uint32_t subtype; + uint32_t num; + const char * addr; + + while ((op = *ops) != DDS_OP_RTS) + { + switch (DDS_OP_MASK & op) + { + case DDS_OP_ADR: + { + type = DDS_OP_TYPE (op); +#ifdef OP_DEBUG_WRITE + TRACE (("W-ADR: %s offset %d\n", stream_op_type[type], ops[1])); +#endif + addr = data + ops[1]; + ops += 2; + switch (type) + { + case DDS_OP_VAL_1BY: + { + DDS_OS_PUT1 (os, *(uint8_t*) addr); + break; + } + case DDS_OP_VAL_2BY: + { + DDS_OS_PUT2 (os, *(uint16_t*) addr); + break; + } + case DDS_OP_VAL_4BY: + { + DDS_OS_PUT4 (os, *(uint32_t*) addr, uint32_t); + break; + } + case DDS_OP_VAL_8BY: + { + DDS_OS_PUT8 (os, *(uint64_t*) addr, uint64_t); + break; + } + case DDS_OP_VAL_STR: + { +#ifdef OP_DEBUG_WRITE + TRACE (("W-STR: %s\n", *((char**) addr))); +#endif + dds_stream_write_string (os, *((char**) addr)); + break; + } + case DDS_OP_VAL_SEQ: + { + dds_sequence_t * seq = (dds_sequence_t*) addr; + subtype = DDS_OP_SUBTYPE (op); + num = seq->_length; + +#ifdef OP_DEBUG_WRITE + TRACE (("W-SEQ: %s <%d>\n", stream_op_type[subtype], num)); +#endif + DDS_OS_PUT4 (os, num, uint32_t); + if (num || (subtype > DDS_OP_VAL_STR)) + { + switch (subtype) + { + case DDS_OP_VAL_1BY: + case DDS_OP_VAL_2BY: + case DDS_OP_VAL_4BY: + { + num = num * dds_op_size[subtype]; + DDS_OS_PUT_BYTES (os, seq->_buffer, num); + break; + } + case DDS_OP_VAL_8BY: + { + DDS_CDR_ALIGN8 (os); + DDS_OS_PUT_BYTES (os, seq->_buffer, num * 8u); + break; + } + case DDS_OP_VAL_STR: + { + char ** ptr = (char**) seq->_buffer; + while (num--) + { +#ifdef OP_DEBUG_WRITE + TRACE (("W-SEQ STR: %s\n", *ptr)); +#endif + dds_stream_write_string (os, *ptr); + ptr++; + } + break; + } + case DDS_OP_VAL_BST: + { + char * ptr = (char*) seq->_buffer; + align = *ops++; + while (num--) + { +#ifdef OP_DEBUG_WRITE + TRACE (("W-SEQ BST[%d]: %s\n", align, ptr)); +#endif + dds_stream_write_string (os, ptr); + ptr += align; + } + break; + } + default: + { + const uint32_t elem_size = *ops++; + const uint32_t * jsr_ops = ops + DDS_OP_ADR_JSR (*ops) - 3; + const uint32_t jmp = DDS_OP_ADR_JMP (*ops); + char * ptr = (char*) seq->_buffer; + while (num--) + { + dds_stream_write (os, ptr, jsr_ops); + ptr += elem_size; + } + ops += jmp ? (jmp - 3) : 1; + break; + } + } + } + break; + } + case DDS_OP_VAL_ARR: + { + subtype = DDS_OP_SUBTYPE (op); + num = *ops++; + +#ifdef OP_DEBUG_WRITE + TRACE (("W-ARR: %s [%d]\n", stream_op_type[subtype], num)); +#endif + switch (subtype) + { + case DDS_OP_VAL_1BY: + case DDS_OP_VAL_2BY: + case DDS_OP_VAL_4BY: + case DDS_OP_VAL_8BY: + { + align = dds_op_size[subtype]; + DDS_CDR_ALIGNTO (os, align); + DDS_OS_PUT_BYTES (os, addr, num * align); + break; + } + case DDS_OP_VAL_STR: + { + char ** ptr = (char**) addr; + while (num--) + { + dds_stream_write_string (os, *ptr); + ptr++; + } + break; + } + case DDS_OP_VAL_BST: + { + char * ptr = (char*) addr; + align = ops[1]; + while (num--) + { + dds_stream_write_string (os, ptr); + ptr += align; + } + ops += 2; + break; + } + default: + { + const uint32_t * jsr_ops = ops + DDS_OP_ADR_JSR (*ops) - 3; + const uint32_t jmp = DDS_OP_ADR_JMP (*ops); + const uint32_t elem_size = ops[1]; + + while (num--) + { + dds_stream_write (os, addr, jsr_ops); + addr += elem_size; + } + ops += jmp ? (jmp - 3) : 2; + break; + } + } + break; + } + case DDS_OP_VAL_UNI: + { + const bool has_default = op & DDS_OP_FLAG_DEF; + subtype = DDS_OP_SUBTYPE (op); + num = ops[0]; + const uint32_t * jeq_op = ops + DDS_OP_ADR_JSR (ops[1]) - 2; + uint32_t disc = 0; + + assert (subtype <= DDS_OP_VAL_4BY); + + /* Write discriminant */ + + switch (subtype) + { + case DDS_OP_VAL_1BY: + { + uint8_t d8 = *((uint8_t*) addr); + DDS_OS_PUT1 (os, d8); + disc = d8; + break; + } + case DDS_OP_VAL_2BY: + { + uint16_t d16 = *((uint16_t*) addr); + DDS_OS_PUT2 (os, d16); + disc = d16; + break; + } + case DDS_OP_VAL_4BY: + { + disc = *((uint32_t*) addr); + DDS_OS_PUT4 (os, disc, uint32_t); + break; + } + default: assert (0); + } +#ifdef OP_DEBUG_WRITE + TRACE (("W-UNI: switch %s case %d/%d\n", stream_op_type[subtype], disc, num)); +#endif + + /* Write case matching discriminant */ + + while (num--) + { + assert ((DDS_OP_MASK & jeq_op[0]) == DDS_OP_JEQ); + + /* Select matching or default case */ + + if ((jeq_op[1] == disc) || (has_default && (num == 0))) + { + subtype = DDS_JEQ_TYPE (jeq_op[0]); + addr = data + jeq_op[2]; + +#ifdef OP_DEBUG_WRITE + TRACE (("W-UNI: case type %s\n", stream_op_type[subtype])); +#endif + switch (subtype) + { + case DDS_OP_VAL_1BY: + case DDS_OP_VAL_2BY: + case DDS_OP_VAL_4BY: + case DDS_OP_VAL_8BY: + { + align = dds_op_size[subtype]; + DDS_CDR_ALIGNTO (os, align); + DDS_OS_PUT_BYTES (os, addr, align); + break; + } + case DDS_OP_VAL_STR: + { + dds_stream_write_string (os, *(char**) addr); + break; + } + case DDS_OP_VAL_BST: + { + dds_stream_write_string (os, (char*) addr); + break; + } + default: + { + dds_stream_write (os, addr, jeq_op + DDS_OP_ADR_JSR (jeq_op[0])); + break; + } + } + break; + } + jeq_op += 3; + } + + /* Jump to next instruction */ + + ops += DDS_OP_ADR_JMP (ops[1]) - 2; + break; + } + case DDS_OP_VAL_BST: + { +#ifdef OP_DEBUG_WRITE + TRACE (("W-BST: %s\n", (char*) addr)); +#endif + dds_stream_write_string (os, (char*) addr); + ops++; + break; + } + default: assert (0); + } + break; + } + case DDS_OP_JSR: /* Implies nested type */ + { +#ifdef OP_DEBUG_WRITE + TRACE (("W-JSR: %d\n", DDS_OP_JUMP (op))); +#endif + dds_stream_write (os, data, ops + DDS_OP_JUMP (op)); + ops++; + break; + } + default: assert (0); + } + } +#ifdef OP_DEBUG_WRITE + TRACE (("W-RTS:\n")); +#endif +} + +static void dds_stream_read (dds_stream_t * is, char * data, const uint32_t * ops) +{ + uint32_t align; + uint32_t op; + uint32_t type; + uint32_t subtype; + uint32_t num; + char * addr; + + while ((op = *ops) != DDS_OP_RTS) + { + switch (DDS_OP_MASK & op) + { + case DDS_OP_ADR: + { + type = DDS_OP_TYPE (op); +#ifdef OP_DEBUG_READ + TRACE (("R-ADR: %s offset %d\n", stream_op_type[type], ops[1])); +#endif + addr = data + ops[1]; + ops += 2; + switch (type) + { + case DDS_OP_VAL_1BY: + { + *(uint8_t*) addr = dds_stream_read_uint8 (is); + break; + } + case DDS_OP_VAL_2BY: + { + *(uint16_t*) addr = dds_stream_read_uint16 (is); + break; + } + case DDS_OP_VAL_4BY: + { + *(uint32_t*) addr = dds_stream_read_uint32 (is); + break; + } + case DDS_OP_VAL_8BY: + { + *(uint64_t*) addr = dds_stream_read_uint64 (is); + break; + } + case DDS_OP_VAL_STR: + { +#ifdef OP_DEBUG_READ + TRACE (("R-STR: @ %p\n", addr)); +#endif + *(char**) addr = dds_stream_reuse_string (is, *((char**) addr), 0); + break; + } + case DDS_OP_VAL_SEQ: + { + dds_sequence_t * seq = (dds_sequence_t*) addr; + subtype = DDS_OP_SUBTYPE (op); + num = dds_stream_read_uint32 (is); + +#ifdef OP_DEBUG_READ + TRACE (("R-SEQ: %s <%d>\n", stream_op_type[subtype], num)); +#endif + /* Maintain max sequence length (may not have been set by caller) */ + + if (seq->_length > seq->_maximum) + { + seq->_maximum = seq->_length; + } + + switch (subtype) + { + case DDS_OP_VAL_1BY: + case DDS_OP_VAL_2BY: + case DDS_OP_VAL_4BY: + case DDS_OP_VAL_8BY: + { + align = dds_op_size[subtype]; + + /* Reuse sequence buffer if big enough */ + + if (num > seq->_length) + { + if (seq->_release && seq->_length) + { + seq->_buffer = dds_realloc_zero (seq->_buffer, num * align); + } + else + { + seq->_buffer = dds_alloc (num * align); + } + seq->_release = true; + seq->_maximum = num; + } + seq->_length = num; + dds_stream_read_fixed_buffer (is, seq->_buffer, seq->_length, align, is->m_endian != DDS_ENDIAN); + break; + } + case DDS_OP_VAL_STR: + { + char ** ptr; + + /* Reuse sequence buffer if big enough */ + + if (num > seq->_maximum) + { + if (seq->_release && seq->_maximum) + { + seq->_buffer = dds_realloc_zero (seq->_buffer, num * sizeof (char*)); + } + else + { + seq->_buffer = dds_alloc (num * sizeof (char*)); + } + seq->_release = true; + seq->_maximum = num; + } + seq->_length = num; + + ptr = (char**) seq->_buffer; + while (num--) + { + *ptr = dds_stream_reuse_string (is, *ptr, 0); + ptr++; + } + break; + } + case DDS_OP_VAL_BST: + { + char * ptr; + align = *ops++; + + /* Reuse sequence buffer if big enough */ + + if (num > seq->_maximum) + { + if (seq->_release && seq->_maximum) + { + seq->_buffer = dds_realloc_zero (seq->_buffer, num * align); + } + else + { + seq->_buffer = dds_alloc (num * align); + } + seq->_release = true; + seq->_maximum = num; + } + seq->_length = num; + + ptr = (char*) seq->_buffer; + while (num--) + { + dds_stream_reuse_string (is, ptr, align); + ptr += align; + } + break; + } + default: + { + const uint32_t elem_size = *ops++; + const uint32_t * jsr_ops = ops + DDS_OP_ADR_JSR (*ops) - 3; + const uint32_t jmp = DDS_OP_ADR_JMP (*ops); + uint32_t i; + char * ptr; + + /* Reuse sequence buffer if big enough */ + + if (num > seq->_maximum) + { + if (seq->_release && seq->_maximum) + { + if (seq->_buffer) + { + i = seq->_length; + ptr = (char*) seq->_buffer; + while (i--) + { + dds_sample_free_contents (ptr, jsr_ops); + ptr += elem_size; + } + } + seq->_buffer = dds_realloc_zero (seq->_buffer, num * elem_size); + } + else + { + seq->_buffer = dds_alloc (num * elem_size); + } + seq->_release = true; + seq->_maximum = num; + } + seq->_length = num; + + ptr = (char*) seq->_buffer; + while (num--) + { + dds_stream_read (is, ptr, jsr_ops); + ptr += elem_size; + } + ops += jmp ? (jmp - 3) : 1; + break; + } + } + break; + } + case DDS_OP_VAL_ARR: + { + subtype = DDS_OP_SUBTYPE (op); + num = *ops++; + +#ifdef OP_DEBUG_READ + TRACE (("R-ARR: %s [%d]\n", stream_op_type[subtype], num)); +#endif + switch (subtype) + { + case DDS_OP_VAL_1BY: + case DDS_OP_VAL_2BY: + case DDS_OP_VAL_4BY: + case DDS_OP_VAL_8BY: + { + align = dds_op_size[subtype]; + if (DDS_IS_OK (is, num * align)) + { + dds_stream_read_fixed_buffer (is, addr, num, align, is->m_endian != DDS_ENDIAN); + } + break; + } + case DDS_OP_VAL_STR: + { + char ** ptr = (char**) addr; + while (num--) + { + *ptr = dds_stream_reuse_string (is, *ptr, 0); + ptr++; + } + break; + } + case DDS_OP_VAL_BST: + { + char * ptr = (char*) addr; + align = ops[1]; + while (num--) + { + dds_stream_reuse_string (is, ptr, align); + ptr += align; + } + ops += 2; + break; + } + default: + { + const uint32_t * jsr_ops = ops + DDS_OP_ADR_JSR (*ops) - 3; + const uint32_t jmp = DDS_OP_ADR_JMP (*ops); + const uint32_t elem_size = ops[1]; + + while (num--) + { + dds_stream_read (is, addr, jsr_ops); + addr += elem_size; + } + ops += jmp ? (jmp - 3) : 2; + break; + } + } + break; + } + case DDS_OP_VAL_UNI: + { + const bool has_default = op & DDS_OP_FLAG_DEF; + subtype = DDS_OP_SUBTYPE (op); + num = ops[0]; + const uint32_t * jeq_op = ops + DDS_OP_ADR_JSR (ops[1]) - 2; + uint32_t disc = 0; + + assert (subtype <= DDS_OP_VAL_4BY); + + /* Read discriminant */ + + switch (subtype) + { + case DDS_OP_VAL_1BY: + { + uint8_t d8 = dds_stream_read_uint8 (is); + *(uint8_t*) addr = d8; + disc = d8; + break; + } + case DDS_OP_VAL_2BY: + { + uint16_t d16 = dds_stream_read_uint16 (is); + *(uint16_t*) addr = d16; + disc = d16; + break; + } + case DDS_OP_VAL_4BY: + { + disc = dds_stream_read_uint32 (is); + *(uint32_t*) addr = disc; + break; + } + default: assert (0); + } + +#ifdef OP_DEBUG_READ + TRACE (("R-UNI: switch %s case %d/%d\n", stream_op_type[subtype], disc, num)); +#endif + + /* Read case matching discriminant */ + + while (num--) + { + assert ((DDS_OP_MASK & jeq_op[0]) == DDS_OP_JEQ); + if ((jeq_op[1] == disc) || (has_default && (num == 0))) + { + subtype = DDS_JEQ_TYPE (jeq_op[0]); + addr = data + jeq_op[2]; + +#ifdef OP_DEBUG_READ + TRACE (("R-UNI: case type %s\n", stream_op_type[subtype])); +#endif + switch (subtype) + { + case DDS_OP_VAL_1BY: + { + *(uint8_t*) addr = dds_stream_read_uint8 (is); + break; + } + case DDS_OP_VAL_2BY: + { + *(uint16_t*) addr = dds_stream_read_uint16 (is); + break; + } + case DDS_OP_VAL_4BY: + { + *(uint32_t*) addr = dds_stream_read_uint32 (is); + break; + } + case DDS_OP_VAL_8BY: + { + *(uint64_t*) addr = dds_stream_read_uint64 (is); + break; + } + case DDS_OP_VAL_STR: + { + *(char**) addr = dds_stream_reuse_string (is, *((char**) addr), 0); + break; + } + default: + { + dds_stream_read (is, addr, jeq_op + DDS_OP_ADR_JSR (jeq_op[0])); + break; + } + } + break; + } + jeq_op += 3; + } + + /* Jump to next instruction */ + + ops += DDS_OP_ADR_JMP (ops[1]) - 2; + break; + } + case DDS_OP_VAL_BST: + { +#ifdef OP_DEBUG_READ + TRACE (("R-BST: @ %p\n", addr)); +#endif + dds_stream_reuse_string (is, (char*) addr, *ops); + ops++; + break; + } + default: assert (0); + } + break; + } + case DDS_OP_JSR: /* Implies nested type */ + { +#ifdef OP_DEBUG_READ + TRACE (("R-JSR: %d\n", DDS_OP_JUMP (op))); +#endif + dds_stream_read (is, data, ops + DDS_OP_JUMP (op)); + ops++; + break; + } + default: assert (0); + } + } +#ifdef OP_DEBUG_READ + TRACE (("R-RTS:\n")); +#endif +} + +void dds_stream_write_sample (dds_stream_t * os, const void * data, const struct sertopic * topic) +{ + const struct dds_topic_descriptor * desc = (const struct dds_topic_descriptor *) topic->type; + + if (topic->opt_size && DDS_CDR_ALIGNED (os, desc->m_align)) + { + DDS_OS_PUT_BYTES (os, data, desc->m_size); + } + else + { + dds_stream_write (os, data, desc->m_ops); + } +} + +void dds_stream_from_serstate (_Out_ dds_stream_t * s, _In_ const serstate_t st) +{ + s->m_failed = false; + s->m_buffer.p8 = (uint8_t*) st->data; + s->m_size = st->size + offsetof (struct serdata, data); + s->m_index = offsetof (struct serdata, data); + s->m_endian = (st->data->v.bswap) ? (! DDS_ENDIAN) : DDS_ENDIAN; +} + +void dds_stream_add_to_serstate (_Inout_ dds_stream_t * s, _Inout_ serstate_t st) +{ + /* DDSI requires 4 byte alignment */ + + DDS_CDR_ALIGN4 (s); + + /* Reset data pointer as stream may have reallocated */ + + st->data = s->m_buffer.pv; + st->pos += (s->m_index - offsetof (struct serdata, data)); + st->size = (s->m_size - offsetof(struct serdata, data)); +} + +void dds_stream_write_key +( + dds_stream_t * os, + const char * sample, + const dds_topic_descriptor_t * desc +) +{ + uint32_t i; + const char * src; + const uint32_t * op; + + for (i = 0; i < desc->m_nkeys; i++) + { + op = desc->m_ops + desc->m_keys[i].m_index; + src = sample + op[1]; + assert ((*op & DDS_OP_FLAG_KEY) && ((DDS_OP_MASK & *op) == DDS_OP_ADR)); + switch (DDS_OP_TYPE (*op)) + { + case DDS_OP_VAL_1BY: + DDS_OS_PUT1 (os, *((uint8_t*) src)); + break; + case DDS_OP_VAL_2BY: + DDS_OS_PUT2 (os, *((uint16_t*) src)); + break; + case DDS_OP_VAL_4BY: + DDS_OS_PUT4 (os, *((uint32_t*) src), uint32_t); + break; + case DDS_OP_VAL_8BY: + DDS_OS_PUT8 (os, *((uint64_t*) src), uint64_t); + break; + case DDS_OP_VAL_STR: + src = *(char**) src; /* drop through */ + case DDS_OP_VAL_BST: + dds_stream_write_string (os, src); + break; + case DDS_OP_VAL_ARR: + { + uint32_t subtype = DDS_OP_SUBTYPE (*op); + assert (subtype <= DDS_OP_VAL_8BY); + uint32_t align = dds_op_size[subtype]; + DDS_CDR_ALIGNTO (os, align); + DDS_OS_PUT_BYTES (os, src, op[2] * align); + break; + } + default: assert (0); + } + } +} + +/* + dds_stream_get_keyhash: Extract key values from a stream and generate + keyhash used for instance identification. Non key fields are skipped. + Key hash data is big endian CDR encoded with no padding. Returns length + of key hash. Input stream may contain full sample of just key data. +*/ + +static uint32_t dds_stream_get_keyhash +( + dds_stream_t * is, + char * dst, + const uint32_t * ops, + const bool just_key +) +{ + uint32_t align; + uint32_t op; + uint32_t type; + uint32_t subtype; + uint32_t num; + uint32_t len; + bool is_key; + bool have_data; + const char * origin = dst; + + while ((op = *ops) != DDS_OP_RTS) + { + switch (DDS_OP_MASK & op) + { + case DDS_OP_ADR: + { + type = DDS_OP_TYPE (op); + is_key = (op & DDS_OP_FLAG_KEY) && (dst != NULL); + have_data = is_key || !just_key; + ops += 2; + if (type <= DDS_OP_VAL_8BY) + { + if (have_data) + { + align = dds_op_size[type]; + DDS_CDR_ALIGNTO (is, align); + + /* Quick skip for basic types that are not keys */ + + if (! is_key) + { + is->m_index += align; + break; + } + } + else + { + break; + } + } +#ifdef OP_DEBUG_KEY + if (is_key) + { + TRACE (("K-ADR: %s\n", stream_op_type[type])); + } +#endif + switch (type) + { + case DDS_OP_VAL_1BY: + { + *dst++ = DDS_IS_GET1 (is); + break; + } + case DDS_OP_VAL_2BY: + { + uint16_t u16 = *DDS_CDR_ADDRESS (is, uint16_t); + if (is->m_endian) + { + u16 = DDS_SWAP16 (u16); + } + memcpy (dst, &u16, sizeof (u16)); + is->m_index += 2; + dst += 2; + break; + } + case DDS_OP_VAL_4BY: + { + uint32_t u32 = *DDS_CDR_ADDRESS (is, uint32_t); + if (is->m_endian) + { + u32 = DDS_SWAP32 (u32); + } + memcpy (dst, &u32, sizeof (u32)); + is->m_index += 4; + dst += 4; + break; + } + case DDS_OP_VAL_8BY: + { + uint64_t u64 = *DDS_CDR_ADDRESS (is, uint64_t); + if (is->m_endian) + { + u64 = DDS_SWAP64 (u64); + } + memcpy (dst, &u64, sizeof (u64)); + is->m_index += 8; + dst += 8; + break; + } + case DDS_OP_VAL_STR: + case DDS_OP_VAL_BST: + { + if (have_data) + { + len = dds_stream_read_uint32 (is); + if (is_key) + { + uint32_t be32 = toBE4u (len); + memcpy (dst, &be32, 4); + dst += 4; + memcpy (dst, DDS_CDR_ADDRESS (is, void), len); + dst += len; +#ifdef OP_DEBUG_KEY + TRACE (("K-ADR: String/BString (%d)\n", len)); +#endif + } + is->m_index += len; + } + if (type == DDS_OP_VAL_BST) + { + ops++; + } + break; + } + case DDS_OP_VAL_SEQ: + { + assert (! is_key); + subtype = DDS_OP_SUBTYPE (op); + num = have_data ? dds_stream_read_uint32 (is) : 0; + + if (num || (subtype > DDS_OP_VAL_STR)) + { + switch (subtype) + { + case DDS_OP_VAL_1BY: + case DDS_OP_VAL_2BY: + case DDS_OP_VAL_4BY: + case DDS_OP_VAL_8BY: + { + align = dds_op_size[subtype]; + DDS_CDR_ALIGNTO (is, align); + is->m_index += align * num; + break; + } + case DDS_OP_VAL_STR: + case DDS_OP_VAL_BST: + { + while (num--) + { + len = dds_stream_read_uint32 (is); + is->m_index += len; + } + if (subtype == DDS_OP_VAL_BST) + { + ops++; + } + break; + } + default: + { + const uint32_t * jsr_ops = ops + DDS_OP_ADR_JSR (ops[1]) - 2; + const uint32_t jmp = DDS_OP_ADR_JMP (ops[1]); + while (num--) + { + dds_stream_get_keyhash (is, NULL, jsr_ops, just_key); + } + ops += jmp ? (jmp - 2) : 2; + break; + } + } + } + break; + } + case DDS_OP_VAL_ARR: + { + subtype = DDS_OP_SUBTYPE (op); + assert (! is_key || subtype <= DDS_OP_VAL_8BY); + num = have_data ? *ops : 0; + ops++; + +#ifdef OP_DEBUG_KEY + if (is_key) + { + TRACE (("K-ADR: %s[%d]\n", stream_op_type[subtype], num)); + } +#endif + switch (subtype) + { + case DDS_OP_VAL_1BY: + case DDS_OP_VAL_2BY: + case DDS_OP_VAL_4BY: + case DDS_OP_VAL_8BY: + { + if (num) + { + align = dds_op_size[subtype]; + if (is_key) + { + dds_stream_read_fixed_buffer (is, dst, num, align, is->m_endian); + dst += num * align; + } + is->m_index += num * align; + } + break; + } + case DDS_OP_VAL_STR: + case DDS_OP_VAL_BST: + { + while (num--) + { + len = dds_stream_read_uint32 (is); + is->m_index += len; + } + break; + } + default: + { + const uint32_t * jsr_ops = ops + DDS_OP_ADR_JSR (*ops) - 3; + const uint32_t jmp = DDS_OP_ADR_JMP (*ops); + while (num--) + { + dds_stream_get_keyhash (is, NULL, jsr_ops, just_key); + } + ops += jmp ? (jmp - 3) : 2; + break; + } + } + break; + } + case DDS_OP_VAL_UNI: + { + const bool has_default = op & DDS_OP_FLAG_DEF; + subtype = DDS_OP_SUBTYPE (op); + num = ops[0]; + const uint32_t * jeq_op = ops + DDS_OP_ADR_JSR (ops[1]) - 2; + uint32_t disc = 0; + + assert (subtype <= DDS_OP_VAL_4BY); + assert (! is_key); + +#ifdef OP_DEBUG_KEY + TRACE (("K-UNI: switch %s cases %d\n", stream_op_type[subtype], num)); +#endif + /* Read discriminant */ + + if (have_data) + { + switch (subtype) + { + case DDS_OP_VAL_1BY: + { + disc = dds_stream_read_uint8 (is); + break; + } + case DDS_OP_VAL_2BY: + { + disc = dds_stream_read_uint16 (is); + break; + } + case DDS_OP_VAL_4BY: + { + disc = dds_stream_read_uint32 (is); + break; + } + default: assert (0); + } + + /* Skip union case */ + + while (num--) + { + assert ((DDS_OP_MASK & jeq_op[0]) == DDS_OP_JEQ); + if ((jeq_op[1] == disc) || (has_default && (num == 0))) + { + subtype = DDS_JEQ_TYPE (jeq_op[0]); + + switch (subtype) + { + case DDS_OP_VAL_1BY: + case DDS_OP_VAL_2BY: + case DDS_OP_VAL_4BY: + case DDS_OP_VAL_8BY: + { + align = dds_op_size[subtype]; + DDS_CDR_ALIGNTO (is, align); + is->m_index += align; + break; + } + case DDS_OP_VAL_STR: + case DDS_OP_VAL_BST: + { + len = dds_stream_read_uint32 (is); + is->m_index += len; + break; + } + default: + { + dds_stream_get_keyhash (is, NULL, jeq_op + DDS_OP_ADR_JSR (jeq_op[0]), just_key); + break; + } + } + break; + } + jeq_op += 3; + } + } + + /* Jump to next instruction */ + + ops += DDS_OP_ADR_JMP (ops[1]) - 2; + break; + } + default: assert (0); + } + break; + } + case DDS_OP_JSR: /* Implies nested type */ + { + dst += dds_stream_get_keyhash (is, dst, ops + DDS_OP_JUMP (op), just_key); + ops++; + break; + } + default: assert (0); + } + } + return (uint32_t) (dst - origin); +} + +void dds_stream_read_keyhash +( + dds_stream_t * is, + dds_key_hash_t * kh, + const dds_topic_descriptor_t * desc, + const bool just_key +) +{ + char * dst; + + assert (desc->m_keys); + + /* Select key buffer to use */ + + kh->m_flags = DDS_KEY_SET | DDS_KEY_HASH_SET; + if (desc->m_flagset & DDS_TOPIC_FIXED_KEY) + { + memset (kh->m_hash, 0, 16); + kh->m_flags |= DDS_KEY_IS_HASH; + dst = kh->m_hash; + } + else + { + if (is->m_size > kh->m_key_buff_size) + { + kh->m_key_buff = dds_realloc (kh->m_key_buff, is->m_size); + kh->m_key_buff_size = (uint32_t) is->m_size; + } + dst = kh->m_key_buff; + } + kh->m_key_len = dds_stream_get_keyhash (is, dst, desc->m_ops, just_key); + + if (kh->m_flags & DDS_KEY_IS_HASH) + { + assert (kh->m_key_len <= 16); + kh->m_key_len = 16; + } + else + { + /* Hash is md5 of key */ + + dds_key_md5 (kh); + } +} + +void dds_stream_read_key +( + dds_stream_t * is, + char * sample, + const dds_topic_descriptor_t * desc +) +{ + uint32_t i; + char * dst; + const uint32_t * op; + + for (i = 0; i < desc->m_nkeys; i++) + { + op = desc->m_ops + desc->m_keys[i].m_index; + dst = sample + op[1]; + assert ((*op & DDS_OP_FLAG_KEY) && ((DDS_OP_MASK & *op) == DDS_OP_ADR)); + switch (DDS_OP_TYPE (*op)) + { + case DDS_OP_VAL_1BY: + *((uint8_t*) dst) = dds_stream_read_uint8 (is); + break; + case DDS_OP_VAL_2BY: + *((uint16_t*) dst) = dds_stream_read_uint16 (is); + break; + case DDS_OP_VAL_4BY: + *((uint32_t*) dst) = dds_stream_read_uint32 (is); + break; + case DDS_OP_VAL_8BY: + *((uint64_t*) dst) = dds_stream_read_uint64 (is); + break; + case DDS_OP_VAL_STR: + *((char**) dst) = dds_stream_reuse_string (is, *((char**) dst), 0); + break; + case DDS_OP_VAL_BST: + dds_stream_reuse_string (is, dst, op[2]); + break; + case DDS_OP_VAL_ARR: + { + uint32_t subtype = DDS_OP_SUBTYPE (*op); + assert (subtype <= DDS_OP_VAL_8BY); + dds_stream_read_fixed_buffer (is, dst, op[2], dds_op_size[subtype], is->m_endian != DDS_ENDIAN); + break; + } + default: assert (0); + } + } +} diff --git a/src/core/ddsc/src/dds_subscriber.c b/src/core/ddsc/src/dds_subscriber.c new file mode 100644 index 0000000..8dbce87 --- /dev/null +++ b/src/core/ddsc/src/dds_subscriber.c @@ -0,0 +1,223 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include "dds__listener.h" +#include "dds__qos.h" +#include "dds__err.h" +#include "ddsi/q_entity.h" +#include "dds__report.h" +#include "ddsc/ddsc_project.h" + +#define DDS_SUBSCRIBER_STATUS_MASK \ + DDS_DATA_ON_READERS_STATUS + +static dds_return_t +dds_subscriber_instance_hdl( + dds_entity *e, + dds_instance_handle_t *i) +{ + assert(e); + assert(i); + /* TODO: Get/generate proper handle. */ + return DDS_ERRNO(DDS_RETCODE_UNSUPPORTED, "Generating subscriber instance handle is not supported"); +} + +static dds_return_t +dds__subscriber_qos_validate( + _In_ const dds_qos_t *qos, + _In_ bool enabled) +{ + dds_return_t ret = DDS_RETCODE_OK; + + assert(qos); + + if((qos->present & QP_GROUP_DATA) && !validate_octetseq(&qos->group_data)) { + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Group data policy is inconsistent and caused an error"); + } + if((qos->present & QP_PARTITION) && !validate_stringseq(&qos->partition)) { + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Partition policy is inconsistent and caused an error"); + } + if((qos->present & QP_PRESENTATION) && validate_presentation_qospolicy(&qos->presentation)) { + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Presentation policy is inconsistent and caused an error"); + } + if((qos->present & QP_PRISMTECH_ENTITY_FACTORY) && !validate_entityfactory_qospolicy(&qos->entity_factory)) { + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Prismtech entity factory policy is inconsistent and caused an error"); + } + if(ret == DDS_RETCODE_OK && enabled && (qos->present & QP_PRESENTATION)) { + /* TODO: Improve/check immutable check. */ + ret = DDS_ERRNO(DDS_RETCODE_IMMUTABLE_POLICY, "Presentation QoS policy is immutable"); + } + + return ret; +} + +static dds_return_t +dds_subscriber_qos_set( + dds_entity *e, + const dds_qos_t *qos, + bool enabled) +{ + dds_return_t ret = dds__subscriber_qos_validate(qos, enabled); + if (ret == DDS_RETCODE_OK) { + if (enabled) { + /* TODO: CHAM-95: DDSI does not support changing QoS policies. */ + ret = DDS_ERRNO(DDS_RETCODE_UNSUPPORTED, DDSC_PROJECT_NAME" does not support changing QoS policies yet"); + } + } + return ret; +} + +static dds_return_t +dds_subscriber_status_validate( + uint32_t mask) +{ + return (mask & ~(DDS_SUBSCRIBER_STATUS_MASK)) ? + DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Invalid status mask") : + DDS_RETCODE_OK; +} + +/* + Set boolean on readers that indicates state of DATA_ON_READERS + status on parent subscriber +*/ +static dds_return_t +dds_subscriber_status_propagate( + dds_entity *sub, + uint32_t mask, + bool set) +{ + if (mask & DDS_DATA_ON_READERS_STATUS) { + dds_entity *iter = sub->m_children; + while (iter) { + os_mutexLock (&iter->m_mutex); + ((dds_reader*) iter)->m_data_on_readers = set; + os_mutexUnlock (&iter->m_mutex); + iter = iter->m_next; + } + } + return DDS_RETCODE_OK; +} + +_Requires_exclusive_lock_held_(participant) +_Check_return_ dds_entity_t +dds__create_subscriber_l( + _Inout_ dds_entity *participant, /* entity-lock must be held */ + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener) +{ + dds_subscriber * sub; + dds_entity_t subscriber; + dds_return_t ret; + dds_qos_t * new_qos; + + /* Validate qos */ + if (qos) { + if ((ret = dds__subscriber_qos_validate(qos, false)) != DDS_RETCODE_OK) { + goto err_param; + } + new_qos = dds_qos_create(); + /* Only returns failure when one of the qos args is NULL, which + * is not the case here. */ + (void)dds_qos_copy(new_qos, qos); + } else { + new_qos = NULL; + } + + /* Create subscriber */ + sub = dds_alloc(sizeof(*sub)); + subscriber = dds_entity_init(&sub->m_entity, participant, DDS_KIND_SUBSCRIBER, new_qos, listener, DDS_SUBSCRIBER_STATUS_MASK); + sub->m_entity.m_deriver.set_qos = dds_subscriber_qos_set; + sub->m_entity.m_deriver.validate_status = dds_subscriber_status_validate; + sub->m_entity.m_deriver.propagate_status = dds_subscriber_status_propagate; + sub->m_entity.m_deriver.get_instance_hdl = dds_subscriber_instance_hdl; + + return subscriber; + + /* Error handling */ +err_param: + return ret; +} + +_Pre_satisfies_((participant & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) +_Must_inspect_result_ dds_entity_t +dds_create_subscriber( + _In_ dds_entity_t participant, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener) +{ + dds_entity * par; + dds_entity_t hdl; + dds__retcode_t errnr; + + DDS_REPORT_STACK(); + + errnr = dds_entity_lock(participant, DDS_KIND_PARTICIPANT, &par); + if (errnr != DDS_RETCODE_OK) { + hdl = DDS_ERRNO(errnr, "Error occurred on locking participant"); + return hdl; + } + + hdl = dds__create_subscriber_l(par, qos, listener); + dds_entity_unlock(par); + + DDS_REPORT_FLUSH(hdl <= 0); + return hdl; +} + +_Pre_satisfies_((subscriber & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER) +dds_return_t +dds_notify_readers( + _In_ dds_entity_t subscriber) +{ + dds_entity *iter; + dds_entity *sub; + dds__retcode_t errnr; + dds_return_t ret; + + DDS_REPORT_STACK(); + + errnr = dds_entity_lock(subscriber, DDS_KIND_SUBSCRIBER, &sub); + if (errnr == DDS_RETCODE_OK) { + errnr = DDS_RETCODE_UNSUPPORTED; + ret = DDS_ERRNO(errnr, "Unsupported operation"); + iter = sub->m_children; + while (iter) { + os_mutexLock(&iter->m_mutex); + // TODO: check if reader has data available, call listener + os_mutexUnlock(&iter->m_mutex); + iter = iter->m_next; + } + dds_entity_unlock(sub); + } else { + ret = DDS_ERRNO(errnr, "Error occurred on locking subscriber"); + } + + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +dds_return_t +dds_subscriber_begin_coherent( + _In_ dds_entity_t e) +{ + /* TODO: CHAM-124 Currently unsupported. */ + return DDS_ERRNO(DDS_RETCODE_UNSUPPORTED, "Using coherency to get a coherent data set is not currently being supported"); +} + +dds_return_t +dds_subscriber_end_coherent( + _In_ dds_entity_t e) +{ + /* TODO: CHAM-124 Currently unsupported. */ + return DDS_ERRNO(DDS_RETCODE_UNSUPPORTED, "Using coherency to get a coherent data set is not currently being supported"); +} + diff --git a/src/core/ddsc/src/dds_time.c b/src/core/ddsc/src/dds_time.c new file mode 100644 index 0000000..2834a9f --- /dev/null +++ b/src/core/ddsc/src/dds_time.c @@ -0,0 +1,41 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include "os/os.h" + +dds_time_t dds_time (void) +{ + os_time time; + + /* Get the current time */ + time = os_timeGet (); + + /* convert os_time to dds_time_t */ + dds_time_t dds_time = time.tv_nsec + (time.tv_sec * DDS_NSECS_IN_SEC); + + return dds_time; +} + +void dds_sleepfor (dds_duration_t n) +{ + os_time interval = { (os_timeSec) (n / DDS_NSECS_IN_SEC), (uint32_t) (n % DDS_NSECS_IN_SEC) }; + os_nanoSleep (interval); +} + +void dds_sleepuntil (dds_time_t n) +{ + dds_time_t interval = n - dds_time (); + if (interval > 0) + { + dds_sleepfor (interval); + } +} diff --git a/src/core/ddsc/src/dds_tkmap.c b/src/core/ddsc/src/dds_tkmap.c new file mode 100644 index 0000000..12d92c3 --- /dev/null +++ b/src/core/ddsc/src/dds_tkmap.c @@ -0,0 +1,393 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include "ddsi/q_thread.h" +#include "ddsi/q_unused.h" +#include "ddsi/q_gc.h" +#include "ddsi/q_globals.h" +#include "ddsi/q_config.h" +#include "ddsi/sysdeps.h" +#include "dds__tkmap.h" +#include "dds__iid.h" +#include "util/ut_hopscotch.h" +#include "dds__stream.h" +#include "os/os.h" +#include "q__osplser.h" + +#define REFC_DELETE 0x80000000 +#define REFC_MASK 0x0fffffff + +struct tkmap +{ + struct ut_chh * m_hh; + os_mutex m_lock; + os_cond m_cond; +}; + +static void gc_buckets_impl (struct gcreq *gcreq) +{ + os_free (gcreq->arg); + gcreq_free (gcreq); +} + +static void gc_buckets (void *a) +{ + struct gcreq *gcreq = gcreq_new (gv.gcreq_queue, gc_buckets_impl); + gcreq->arg = a; + gcreq_enqueue (gcreq); +} + +static void gc_tkmap_instance_impl (struct gcreq *gcreq) +{ + struct tkmap_instance *tk = gcreq->arg; + ddsi_serdata_unref (tk->m_sample); + dds_free (tk); + gcreq_free (gcreq); +} + +static void gc_tkmap_instance (struct tkmap_instance *tk) +{ + struct gcreq *gcreq = gcreq_new (gv.gcreq_queue, gc_tkmap_instance_impl); + gcreq->arg = tk; + gcreq_enqueue (gcreq); +} + +/* Fixed seed and length */ + +#define DDS_MH3_LEN 16 +#define DDS_MH3_SEED 0 + +#define DDS_MH3_ROTL32(x,r) (((x) << (r)) | ((x) >> (32 - (r)))) + +/* Really + http://code.google.com/p/smhasher/source/browse/trunk/MurmurHash3.cpp, + MurmurHash3_x86_32 +*/ + +static uint32_t dds_mh3 (const void * key) +{ + const uint8_t *data = (const uint8_t *) key; + const intptr_t nblocks = (intptr_t) (DDS_MH3_LEN / 4); + const uint32_t c1 = 0xcc9e2d51; + const uint32_t c2 = 0x1b873593; + + uint32_t h1 = DDS_MH3_SEED; + + const uint32_t *blocks = (const uint32_t *) (data + nblocks * 4); + register intptr_t i; + + for (i = -nblocks; i; i++) + { + uint32_t k1 = blocks[i]; + + k1 *= c1; + k1 = DDS_MH3_ROTL32 (k1, 15); + k1 *= c2; + + h1 ^= k1; + h1 = DDS_MH3_ROTL32 (h1, 13); + h1 = h1 * 5+0xe6546b64; + } + + /* finalization */ + + h1 ^= DDS_MH3_LEN; + h1 ^= h1 >> 16; + h1 *= 0x85ebca6b; + h1 ^= h1 >> 13; + h1 *= 0xc2b2ae35; + h1 ^= h1 >> 16; + + return h1; +} + +static uint32_t dds_tk_hash (const struct tkmap_instance * inst) +{ + volatile struct serdata * sd = (volatile struct serdata *) inst->m_sample; + + if (! sd->v.hash_valid) + { + const uint32_t * k = (const uint32_t *) sd->v.keyhash.m_hash; + const uint32_t hash0 = sd->v.st->topic ? sd->v.st->topic->hash : 0; + sd->v.hash = ((sd->v.keyhash.m_flags & DDS_KEY_IS_HASH) ? dds_mh3 (k) : (*k)) ^ hash0; + sd->v.hash_valid = 1; + } + + return sd->v.hash; +} + +static uint32_t dds_tk_hash_void (const void * inst) +{ + return dds_tk_hash (inst); +} + +static int dds_tk_equals (const struct tkmap_instance *a, const struct tkmap_instance *b) +{ + return serdata_cmp (a->m_sample, b->m_sample) == 0; +} + +static int dds_tk_equals_void (const void *a, const void *b) +{ + return dds_tk_equals (a, b); +} + +struct tkmap * dds_tkmap_new (void) +{ + struct tkmap *tkmap = dds_alloc (sizeof (*tkmap)); + tkmap->m_hh = ut_chhNew (1, dds_tk_hash_void, dds_tk_equals_void, gc_buckets); + os_mutexInit (&tkmap->m_lock); + os_condInit (&tkmap->m_cond, &tkmap->m_lock); + return tkmap; +} + +static void free_tkmap_instance (void *vtk, UNUSED_ARG(void *f_arg)) +{ + struct tkmap_instance *tk = vtk; + ddsi_serdata_unref (tk->m_sample); + os_free (tk); +} + +void dds_tkmap_free (_Inout_ _Post_invalid_ struct tkmap * map) +{ + ut_chhEnumUnsafe (map->m_hh, free_tkmap_instance, NULL); + ut_chhFree (map->m_hh); + os_condDestroy (&map->m_cond); + os_mutexDestroy (&map->m_lock); + dds_free (map); +} + +uint64_t dds_tkmap_lookup (_In_ struct tkmap * map, _In_ const struct serdata * sd) +{ + struct tkmap_instance dummy; + struct tkmap_instance * tk; + dummy.m_sample = (struct serdata *) sd; + tk = ut_chhLookup (map->m_hh, &dummy); + return (tk) ? tk->m_iid : DDS_HANDLE_NIL; +} + +typedef struct +{ + uint64_t m_iid; + void * m_sample; + bool m_ret; +} +tkmap_get_key_arg; + +static void dds_tkmap_get_key_fn (void * vtk, void * varg) +{ + struct tkmap_instance * tk = vtk; + tkmap_get_key_arg * arg = (tkmap_get_key_arg*) varg; + if (tk->m_iid == arg->m_iid) + { + deserialize_into (arg->m_sample, tk->m_sample); + arg->m_ret = true; + } +} + +_Check_return_ +bool dds_tkmap_get_key (_In_ struct tkmap * map, _In_ uint64_t iid, _Out_ void * sample) +{ + tkmap_get_key_arg arg = { iid, sample, false }; + os_mutexLock (&map->m_lock); + ut_chhEnumUnsafe (map->m_hh, dds_tkmap_get_key_fn, &arg); + os_mutexUnlock (&map->m_lock); + return arg.m_ret; +} + +typedef struct +{ + uint64_t m_iid; + struct tkmap_instance * m_inst; +} +tkmap_get_inst_arg; + +static void dds_tkmap_get_inst_fn (void * vtk, void * varg) +{ + struct tkmap_instance * tk = vtk; + tkmap_get_inst_arg * arg = (tkmap_get_inst_arg*) varg; + if (tk->m_iid == arg->m_iid) + { + arg->m_inst = tk; + } +} + +_Check_return_ +struct tkmap_instance * dds_tkmap_find_by_id (_In_ struct tkmap * map, _In_ uint64_t iid) +{ + tkmap_get_inst_arg arg = { iid, NULL }; + ut_chhEnumUnsafe (map->m_hh, dds_tkmap_get_inst_fn, &arg); + return arg.m_inst; +} + +/* Debug keyhash generation for debug and coverage builds */ + +#ifdef NDEBUG +#if VL_BUILD_LCOV +#define DDS_DEBUG_KEYHASH 1 +#else +#define DDS_DEBUG_KEYHASH 0 +#endif +#else +#define DDS_DEBUG_KEYHASH 1 +#endif + +_Check_return_ +struct tkmap_instance * dds_tkmap_find( + _In_opt_ const struct dds_topic * topic, + _In_ struct serdata * sd, + _In_ const bool rd, + _In_ const bool create) +{ + struct tkmap_instance dummy; + struct tkmap_instance * tk; + struct tkmap * map = gv.m_tkmap; + + dummy.m_sample = sd; + + /* Generate key hash if required and not provided */ + + if (topic && topic->m_descriptor->m_nkeys) + { + if ((sd->v.keyhash.m_flags & DDS_KEY_HASH_SET) == 0) + { + dds_stream_t is; + dds_stream_from_serstate (&is, sd->v.st); + dds_stream_read_keyhash (&is, &sd->v.keyhash, topic->m_descriptor, sd->v.st->kind == STK_KEY); + } + else + { + if (topic->m_descriptor->m_flagset & DDS_TOPIC_FIXED_KEY) + { + sd->v.keyhash.m_flags |= DDS_KEY_IS_HASH; + } + +#if DDS_DEBUG_KEYHASH + + { + dds_stream_t is; + dds_key_hash_t kh; + + /* Check that we generate same keyhash as provided */ + + memset (&kh, 0, sizeof (kh)); + dds_stream_from_serstate (&is, sd->v.st); + dds_stream_read_keyhash (&is, &kh, topic->m_descriptor, sd->v.st->kind == STK_KEY); + assert (memcmp (kh.m_hash, sd->v.keyhash.m_hash, 16) == 0); + if (kh.m_key_buff_size) + { + dds_free (kh.m_key_buff); + } + } +#endif + } + } + +retry: + if ((tk = ut_chhLookup(map->m_hh, &dummy)) != NULL) + { + uint32_t new; + new = os_atomic_inc32_nv(&tk->m_refc); + if (new & REFC_DELETE) + { + /* for the unlikely case of spinning 2^31 times across all threads ... */ + os_atomic_dec32(&tk->m_refc); + + /* simplest action would be to just spin, but that can potentially take a long time; + we can block until someone signals some entry is removed from the map if we take + some lock & wait for some condition */ + os_mutexLock(&map->m_lock); + while ((tk = ut_chhLookup(map->m_hh, &dummy)) != NULL && (os_atomic_ld32(&tk->m_refc) & REFC_DELETE)) + os_condWait(&map->m_cond, &map->m_lock); + os_mutexUnlock(&map->m_lock); + goto retry; + } + } + else if (create) + { + if ((tk = dds_alloc (sizeof (*tk))) == NULL) + return NULL; + + tk->m_sample = ddsi_serdata_ref (sd); + tk->m_map = map; + os_atomic_st32 (&tk->m_refc, 1); + tk->m_iid = dds_iid_gen (); + if (!ut_chhAdd (map->m_hh, tk)) + { + /* Lost a race from another thread, retry */ + ddsi_serdata_unref (tk->m_sample); + dds_free (tk); + goto retry; + } + } + + if (tk && rd) + { + TRACE (("tk=%p iid=%"PRIx64"", &tk, tk->m_iid)); + } + return tk; +} + +_Check_return_ +struct tkmap_instance * dds_tkmap_lookup_instance_ref (_In_ struct serdata * sd) +{ + dds_topic * topic = sd->v.st->topic ? sd->v.st->topic->status_cb_entity : NULL; + + assert (vtime_awake_p (lookup_thread_state ()->vtime)); + +#if 0 + /* Topic might have been deleted -- FIXME: no way the topic may be deleted when there're still users out there */ + if (topic == NULL) + { + return NULL; + } +#endif + + return dds_tkmap_find (topic, sd, true, true); +} + +void dds_tkmap_instance_ref (_In_ struct tkmap_instance *tk) +{ + os_atomic_inc32 (&tk->m_refc); +} + +void dds_tkmap_instance_unref (_In_ struct tkmap_instance * tk) +{ + uint32_t old, new; + assert (vtime_awake_p(lookup_thread_state()->vtime)); + do { + old = os_atomic_ld32(&tk->m_refc); + if (old == 1) + new = REFC_DELETE; + else + { + assert(!(old & REFC_DELETE)); + new = old - 1; + } + } while (!os_atomic_cas32(&tk->m_refc, old, new)); + if (new == REFC_DELETE) + { + struct tkmap *map = tk->m_map; + + /* Remove from hash table */ + (void)ut_chhRemove(map->m_hh, tk); + + /* Signal any threads blocked in their retry loops in lookup */ + os_mutexLock(&map->m_lock); + os_condBroadcast(&map->m_cond); + os_mutexUnlock(&map->m_lock); + + /* Schedule freeing of memory until after all those who may have found a pointer have + progressed to where they no longer hold that pointer */ + gc_tkmap_instance(tk); + } +} diff --git a/src/core/ddsc/src/dds_topic.c b/src/core/ddsc/src/dds_topic.c new file mode 100644 index 0000000..8f3aabe --- /dev/null +++ b/src/core/ddsc/src/dds_topic.c @@ -0,0 +1,631 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include "dds__topic.h" +#include "dds__listener.h" +#include "dds__qos.h" +#include "dds__stream.h" +#include "dds__init.h" +#include "dds__domain.h" +#include "dds__err.h" +#include "ddsi/q_entity.h" +#include "ddsi/q_thread.h" +#include "q__osplser.h" +#include "ddsi/q_ddsi_discovery.h" +#include "os/os_atomics.h" +#include "dds__report.h" + + +#define DDS_TOPIC_STATUS_MASK \ + DDS_INCONSISTENT_TOPIC_STATUS + +const ut_avlTreedef_t dds_topictree_def = UT_AVL_TREEDEF_INITIALIZER_INDKEY +( + offsetof (struct sertopic, avlnode), + offsetof (struct sertopic, name_typename), + (int (*) (const void *, const void *)) strcmp, + 0 +); + +/* builtin-topic handles */ +const dds_entity_t DDS_BUILTIN_TOPIC_DCPSPARTICIPANT = (DDS_KIND_INTERNAL + 1); +const dds_entity_t DDS_BUILTIN_TOPIC_CMPARTICIPANT = (DDS_KIND_INTERNAL + 2); +const dds_entity_t DDS_BUILTIN_TOPIC_DCPSTYPE = (DDS_KIND_INTERNAL + 3); +const dds_entity_t DDS_BUILTIN_TOPIC_DCPSTOPIC = (DDS_KIND_INTERNAL + 4); +const dds_entity_t DDS_BUILTIN_TOPIC_DCPSPUBLICATION = (DDS_KIND_INTERNAL + 5); +const dds_entity_t DDS_BUILTIN_TOPIC_CMPUBLISHER = (DDS_KIND_INTERNAL + 6); +const dds_entity_t DDS_BUILTIN_TOPIC_DCPSSUBSCRIPTION = (DDS_KIND_INTERNAL + 7); +const dds_entity_t DDS_BUILTIN_TOPIC_CMSUBSCRIBER = (DDS_KIND_INTERNAL + 8); +const dds_entity_t DDS_BUILTIN_TOPIC_CMDATAWRITER = (DDS_KIND_INTERNAL + 9); +const dds_entity_t DDS_BUILTIN_TOPIC_CMDATAREADER = (DDS_KIND_INTERNAL + 10); + +static bool +is_valid_name( + _In_ const char *name) +{ + bool valid = false; + /* DDS Spec: + * | TOPICNAME - A topic name is an identifier for a topic, and is defined as any series of characters + * | 'a', ..., 'z', + * | 'A', ..., 'Z', + * | '0', ..., '9', + * | '-' but may not start with a digit. + * It is considered that '-' is an error in the spec and should say '_'. So, that's what we'll check for. + */ + assert(name); + if ((name[0] != '\0') && (!isdigit((unsigned char)name[0]))) { + while (isalnum((unsigned char)*name) || (*name == '_')) { + name++; + } + if (*name == '\0') { + valid = true; + } + } + + return valid; +} + + +static dds_return_t +dds_topic_status_validate( + uint32_t mask) +{ + return (mask & ~(DDS_TOPIC_STATUS_MASK)) ? + DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument mask is invalid") : + DDS_RETCODE_OK; +} + +/* + Topic status change callback handler. Supports INCONSISTENT_TOPIC + status (only defined status on a topic). +*/ + +static void +dds_topic_status_cb( + struct dds_topic *cb_t) +{ + dds_topic *topic; + dds__retcode_t rc; + void *metrics = NULL; + + DDS_REPORT_STACK(); + + if (dds_topic_lock(((dds_entity*)cb_t)->m_hdl, &topic) != DDS_RETCODE_OK) { + /* There's a deletion or closing going on. */ + DDS_REPORT_FLUSH(false); + return; + } + assert(topic == cb_t); + + /* Reset the status for possible Listener call. + * When a listener is not called, the status will be set (again). */ + + /* Update status metrics. */ + topic->m_inconsistent_topic_status.total_count++; + topic->m_inconsistent_topic_status.total_count_change++; + + + /* The topic needs to be unlocked when propagating the (possible) listener + * call because the application should be able to call this topic within + * the callback function. */ + dds_topic_unlock(topic); + + /* Is anybody interested within the entity hierarchy through listeners? */ + rc = dds_entity_listener_propagation((dds_entity*)topic, + (dds_entity*)topic, + DDS_INCONSISTENT_TOPIC_STATUS, + (void*)&(topic->m_inconsistent_topic_status), + true); + + if (rc == DDS_RETCODE_OK) { + /* Event was eaten by a listener. */ + if (dds_topic_lock(((dds_entity*)cb_t)->m_hdl, &topic) == DDS_RETCODE_OK) { + /* Reset the change counts of the metrics. */ + topic->m_inconsistent_topic_status.total_count_change = 0; + dds_topic_unlock(topic); + } + } else if (rc == DDS_RETCODE_NO_DATA) { + /* Nobody was interested through a listener (NO_DATA == NO_CALL): set the status. */ + dds_entity_status_set((dds_entity*)topic, DDS_INCONSISTENT_TOPIC_STATUS); + /* Notify possible interested observers. */ + dds_entity_status_signal((dds_entity*)topic); + rc = DDS_RETCODE_OK; + } else if (rc == DDS_RETCODE_ALREADY_DELETED) { + /* An entity up the hierarchy is being deleted. */ + rc = DDS_RETCODE_OK; + } else { + /* Something went wrong up the hierarchy. */ + } + + DDS_REPORT_FLUSH(rc != DDS_RETCODE_OK); +} + +sertopic_t +dds_topic_lookup( + dds_domain *domain, + const char *name) +{ + sertopic_t st = NULL; + ut_avlIter_t iter; + + assert (domain); + assert (name); + + os_mutexLock (&dds_global.m_mutex); + st = ut_avlIterFirst (&dds_topictree_def, &domain->m_topics, &iter); + while (st) { + if (strcmp (st->name, name) == 0) { + break; + } + st = ut_avlIterNext (&iter); + } + os_mutexUnlock (&dds_global.m_mutex); + return st; +} + +void +dds_topic_free( + dds_domainid_t domainid, + struct sertopic *st) +{ + dds_domain *domain; + + assert (st); + + os_mutexLock (&dds_global.m_mutex); + domain = (dds_domain*) ut_avlLookup (&dds_domaintree_def, &dds_global.m_domains, &domainid); + if (domain != NULL) { + ut_avlDelete (&dds_topictree_def, &domain->m_topics, st); + } + os_mutexUnlock (&dds_global.m_mutex); + st->status_cb_entity = NULL; + sertopic_free (st); +} + +static void +dds_topic_add( + dds_domainid_t id, + sertopic_t st) +{ + dds_domain * dom; + os_mutexLock (&dds_global.m_mutex); + dom = dds_domain_find_locked (id); + assert (dom); + ut_avlInsert (&dds_topictree_def, &dom->m_topics, st); + os_mutexUnlock (&dds_global.m_mutex); +} + +_Pre_satisfies_((participant & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) +DDS_EXPORT dds_entity_t +dds_find_topic( + _In_ dds_entity_t participant, + _In_z_ const char *name) +{ + dds_entity_t tp; + dds_entity *p = NULL; + sertopic_t st; + dds__retcode_t rc; + + DDS_REPORT_STACK(); + + if (name) { + rc = dds_entity_lock(participant, DDS_KIND_PARTICIPANT, &p); + if (rc == DDS_RETCODE_OK) { + st = dds_topic_lookup (p->m_domain, name); + if (st) { + dds_entity_add_ref (&st->status_cb_entity->m_entity); + tp = st->status_cb_entity->m_entity.m_hdl; + } else { + tp = DDS_ERRNO(DDS_RETCODE_PRECONDITION_NOT_MET, "Topic is not being created yet"); + } + dds_entity_unlock(p); + } else { + tp = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + } else { + tp = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument name is not valid"); + } + DDS_REPORT_FLUSH(tp <= 0); + return tp; +} + +static dds_return_t +dds_topic_delete( + dds_entity *e) +{ + dds_topic_free(e->m_domainid, ((dds_topic*) e)->m_stopic); + return DDS_RETCODE_OK; +} + +static dds_return_t +dds_topic_qos_validate( + const dds_qos_t *qos, + bool enabled) +{ + dds_return_t ret = DDS_RETCODE_OK; + assert(qos); + + + /* Check consistency. */ + if (!dds_qos_validate_common(qos)) { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument QoS is not valid"); + } + if ((qos->present & QP_GROUP_DATA) && !validate_octetseq (&qos->group_data)) { + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Group data QoS policy is inconsistent and caused an error"); + } + if ((qos->present & QP_DURABILITY_SERVICE) && (validate_durability_service_qospolicy(&qos->durability_service) != 0)) { + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Durability service QoS policy is inconsistent and caused an error"); + } + if ((qos->present & QP_LIFESPAN) && (validate_duration(&qos->lifespan.duration) != 0)) { + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Lifespan QoS policy is inconsistent and caused an error"); + } + if (qos->present & QP_HISTORY && (qos->present & QP_RESOURCE_LIMITS) && (validate_history_and_resource_limits(&qos->history, &qos->resource_limits) != 0)) { + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Lifespan QoS policy is inconsistent and caused an error"); + } + if(ret == DDS_RETCODE_OK && enabled){ + ret = dds_qos_validate_mutable_common(qos); + } + return ret; +} + + +static dds_return_t +dds_topic_qos_set( + dds_entity *e, + const dds_qos_t *qos, + bool enabled) +{ + dds_return_t ret = dds_topic_qos_validate(qos, enabled); + if (ret == DDS_RETCODE_OK) { + if (enabled) { + /* TODO: CHAM-95: DDSI does not support changing QoS policies. */ + ret = DDS_ERRNO(DDS_RETCODE_UNSUPPORTED, "Changing the topic QoS is not supported."); + } + } + return ret; +} + +_Pre_satisfies_((participant & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) +DDS_EXPORT dds_entity_t +dds_create_topic( + _In_ dds_entity_t participant, + _In_ const dds_topic_descriptor_t *desc, + _In_z_ const char *name, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener) +{ + static uint32_t next_topicid = 0; + + char *key = NULL; + sertopic_t st; + const char *typename; + dds__retcode_t rc; + dds_entity *par; + dds_topic *top; + dds_qos_t *new_qos = NULL; + nn_plist_t plist; + dds_entity_t hdl; + struct participant *ddsi_pp; + struct thread_state1 *const thr = lookup_thread_state (); + const bool asleep = !vtime_awake_p (thr->vtime); + uint32_t index; + + DDS_REPORT_STACK(); + + if (desc == NULL){ + hdl = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Topic description is NULL"); + goto bad_param_err; + } + + if (name == NULL) { + hdl = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Topic name is NULL"); + goto bad_param_err; + } + + if (!is_valid_name(name)) { + hdl = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Topic name contains characters that are not allowed."); + goto bad_param_err; + } + + rc = dds_entity_lock(participant, DDS_KIND_PARTICIPANT, &par); + if (rc != DDS_RETCODE_OK) { + hdl = DDS_ERRNO(rc, "Error occurred on locking entity"); + goto lock_err; + } + + /* Validate qos */ + if (qos) { + hdl = dds_topic_qos_validate (qos, false); + if (hdl != DDS_RETCODE_OK) { + goto qos_err; + } + } + + /* Check if topic already exists with same name */ + if (dds_topic_lookup (par->m_domain, name)) { + hdl = DDS_ERRNO(DDS_RETCODE_PRECONDITION_NOT_MET, "Precondition not met"); + goto qos_err; + } + + typename = desc->m_typename; + key = (char*) dds_alloc (strlen (name) + strlen (typename) + 2); + strcpy (key, name); + strcat (key, "/"); + strcat (key, typename); + + if (qos) { + new_qos = dds_qos_create(); + /* Only returns failure when one of the qos args is NULL, which + * is not the case here. */ + (void)dds_qos_copy(new_qos, qos); + } + + /* Create topic */ + top = dds_alloc (sizeof (*top)); + top->m_descriptor = desc; + hdl = dds_entity_init (&top->m_entity, par, DDS_KIND_TOPIC, new_qos, listener, DDS_TOPIC_STATUS_MASK); + top->m_entity.m_deriver.delete = dds_topic_delete; + top->m_entity.m_deriver.set_qos = dds_topic_qos_set; + top->m_entity.m_deriver.validate_status = dds_topic_status_validate; + + st = dds_alloc (sizeof (*st)); + st->type = (void*) desc; + os_atomic_st32 (&st->refcount, 1); + st->status_cb = dds_topic_status_cb; + st->status_cb_entity = top; + st->name_typename = key; + st->name = dds_alloc (strlen (name) + 1); + strcpy (st->name, name); + st->typename = dds_alloc (strlen (typename) + 1); + strcpy (st->typename, typename); + st->nkeys = desc->m_nkeys; + st->keys = desc->m_keys; + st->id = next_topicid++; + +#ifdef VXWORKS_RTP + st->hash = (st->id * UINT64_C (12844332200329132887UL)) >> 32; +#else + st->hash = (st->id * UINT64_C (12844332200329132887)) >> 32; +#endif + + /* Check if topic cannot be optimised (memcpy marshal) */ + + if ((desc->m_flagset & DDS_TOPIC_NO_OPTIMIZE) == 0) { + st->opt_size = dds_stream_check_optimize (desc); + } + top->m_stopic = st; + + /* Add topic to extent */ + dds_topic_add (par->m_domainid, st); + + nn_plist_init_empty (&plist); + if (new_qos) { + dds_qos_merge (&plist.qos, new_qos); + } + + /* Set Topic meta data (for SEDP publication) */ + plist.qos.topic_name = dds_string_dup (st->name); + plist.qos.type_name = dds_string_dup (st->typename); + plist.qos.present |= (QP_TOPIC_NAME | QP_TYPE_NAME); + if (desc->m_meta) { + plist.type_description = dds_string_dup (desc->m_meta); + plist.present |= PP_PRISMTECH_TYPE_DESCRIPTION; + } + if (desc->m_nkeys) { + plist.qos.present |= QP_PRISMTECH_SUBSCRIPTION_KEYS; + plist.qos.subscription_keys.use_key_list = 1; + plist.qos.subscription_keys.key_list.n = desc->m_nkeys; + plist.qos.subscription_keys.key_list.strs = dds_alloc (desc->m_nkeys * sizeof (char*)); + for (index = 0; index < desc->m_nkeys; index++) { + plist.qos.subscription_keys.key_list.strs[index] = dds_string_dup (desc->m_keys[index].m_name); + } + } + + /* Publish Topic */ + if (asleep) { + thread_state_awake (thr); + } + ddsi_pp = ephash_lookup_participant_guid (&par->m_guid); + assert (ddsi_pp); + sedp_write_topic (ddsi_pp, &plist); + if (asleep) { + thread_state_asleep (thr); + } + nn_plist_fini (&plist); + +qos_err: + dds_entity_unlock(par); +lock_err: +bad_param_err: + DDS_REPORT_FLUSH(hdl <= 0); + return hdl; +} + +static bool +dds_topic_chaining_filter( + const void *sample, + void *ctx) +{ + dds_topic_filter_fn realf = (dds_topic_filter_fn)ctx; + return realf (sample); +} + +static void +dds_topic_mod_filter( + dds_entity_t topic, + dds_topic_intern_filter_fn *filter, + void **ctx, + bool set) +{ + dds_topic *t; + if (dds_topic_lock(topic, &t) == DDS_RETCODE_OK) { + if (set) { + t->m_stopic->filter_fn = *filter; + t->m_stopic->filter_ctx = *ctx; + + /* Create sample for read filtering */ + + if (t->m_stopic->filter_sample == NULL) { + t->m_stopic->filter_sample = dds_alloc (t->m_descriptor->m_size); + } + } else { + *filter = t->m_stopic->filter_fn; + *ctx = t->m_stopic->filter_ctx; + } + dds_topic_unlock(t); + } +} + +_Pre_satisfies_((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC) +void +dds_topic_set_filter( + dds_entity_t topic, + dds_topic_filter_fn filter) +{ + dds_topic_intern_filter_fn chaining = dds_topic_chaining_filter; + void *realf = (void *)filter; + dds_topic_mod_filter (topic, &chaining, &realf, true); +} + +_Pre_satisfies_((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC) +dds_topic_filter_fn +dds_topic_get_filter( + dds_entity_t topic) +{ + dds_topic_intern_filter_fn filter; + void *ctx; + dds_topic_mod_filter (topic, &filter, &ctx, false); + return + (filter == dds_topic_chaining_filter) ? (dds_topic_filter_fn)ctx : NULL; +} + +void +dds_topic_set_filter_with_ctx( + dds_entity_t topic, + dds_topic_intern_filter_fn filter, + void *ctx) +{ + dds_topic_mod_filter (topic, &filter, &ctx, true); +} + +dds_topic_intern_filter_fn +dds_topic_get_filter_with_ctx( + dds_entity_t topic) +{ + dds_topic_intern_filter_fn filter; + void *ctx; + dds_topic_mod_filter (topic, &filter, &ctx, false); + return (filter == dds_topic_chaining_filter) ? NULL : filter; +} + +_Pre_satisfies_((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC) +DDS_EXPORT dds_return_t +dds_get_name( + _In_ dds_entity_t topic, + _Out_writes_z_(size) char *name, + _In_ size_t size) +{ + dds_topic *t; + dds_return_t ret; + dds__retcode_t rc; + + DDS_REPORT_STACK(); + + if(size <= 0){ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument size is smaller than 0"); + goto fail; + } + if(name == NULL){ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument name is NULL"); + goto fail; + } + name[0] = '\0'; + rc = dds_topic_lock(topic, &t); + if (rc == DDS_RETCODE_OK) { + (void)snprintf(name, size, "%s", t->m_stopic->name); + dds_topic_unlock(t); + ret = DDS_RETCODE_OK; + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking topic"); + goto fail; + } +fail: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC) +DDS_EXPORT dds_return_t +dds_get_type_name( + _In_ dds_entity_t topic, + _Out_writes_z_(size) char *name, + _In_ size_t size) +{ + dds_topic *t; + dds__retcode_t rc; + dds_return_t ret; + + DDS_REPORT_STACK(); + + if(size <= 0){ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument size is smaller than 0"); + goto fail; + } + if(name == NULL){ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument name is NULL"); + goto fail; + } + name[0] = '\0'; + rc = dds_topic_lock(topic, &t); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking topic"); + goto fail; + } + (void)snprintf(name, size, "%s", t->m_stopic->typename); + dds_topic_unlock(t); + ret = DDS_RETCODE_OK; +fail: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} +_Pre_satisfies_((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC) +dds_return_t +dds_get_inconsistent_topic_status( + _In_ dds_entity_t topic, + _Out_opt_ dds_inconsistent_topic_status_t *status) +{ + dds__retcode_t rc; + dds_topic *t; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + rc = dds_topic_lock(topic, &t); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking topic"); + goto fail; + } + /* status = NULL, application do not need the status, but reset the counter & triggered bit */ + if (status) { + *status = t->m_inconsistent_topic_status; + } + if (((dds_entity*)t)->m_status_enable & DDS_INCONSISTENT_TOPIC_STATUS) { + t->m_inconsistent_topic_status.total_count_change = 0; + dds_entity_status_reset(t, DDS_INCONSISTENT_TOPIC_STATUS); + } + dds_topic_unlock(t); +fail: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} diff --git a/src/core/ddsc/src/dds_waitset.c b/src/core/ddsc/src/dds_waitset.c new file mode 100644 index 0000000..7c73827 --- /dev/null +++ b/src/core/ddsc/src/dds_waitset.c @@ -0,0 +1,512 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include "os/os.h" +#include "dds__entity.h" +#include "dds__querycond.h" +#include "dds__readcond.h" +#include "dds__rhc.h" +#include "dds__err.h" +#include "dds__report.h" + + +#define dds_waitset_lock(hdl, obj) dds_entity_lock(hdl, DDS_KIND_WAITSET, (dds_entity**)obj) +#define dds_waitset_unlock(obj) dds_entity_unlock((dds_entity*)obj); + + +static void +dds_waitset_swap( + _Inout_ dds_attachment **dst, + _In_ dds_attachment **src, + _In_opt_ dds_attachment *prev, + _In_ dds_attachment *idx) +{ + /* Remove from source. */ + if (prev == NULL) { + *src = idx->next; + } else { + prev->next = idx->next; + } + + /* Add to destination. */ + idx->next = *dst; + *dst = idx; +} + +static void +dds_waitset_signal_entity( + _In_ dds_waitset *ws) +{ + dds_entity *e = (dds_entity*)ws; + /* When signaling any observers of us through the entity, + * we need to be unlocked. We still have claimed the related + * handle, so possible deletions will be delayed until we + * release it. */ + os_mutexUnlock(&(e->m_mutex)); + dds_entity_status_signal(e); + os_mutexLock(&(e->m_mutex)); +} + +static dds_return_t +dds_waitset_wait_impl( + _In_ dds_entity_t waitset, + _Out_writes_to_opt_(nxs, return < 0 ? 0 : return) dds_attach_t *xs, + _In_ size_t nxs, + _In_ dds_time_t abstimeout, + _In_ dds_time_t tnow) +{ + dds_waitset *ws; + dds_return_t ret; + dds__retcode_t rc; + dds_attachment *idx; + dds_attachment *next; + dds_attachment *prev; + + if ((xs == NULL) && (nxs != 0)){ + return DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "A size was given, but no array."); + } + if ((xs != NULL) && (nxs == 0)){ + return DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Array is given with an invalid size"); + } + + /* Locking the waitset here will delay a possible deletion until it is + * unlocked. Even when the related mutex is unlocked by a conditioned wait. */ + rc = dds_waitset_lock(waitset, &ws); + if (rc == DDS_RETCODE_OK) { + /* Check if any of any previous triggered entities has changed there status + * and thus it trigger value could be false now. */ + idx = ws->triggered; + prev = NULL; + while (idx != NULL) { + next = idx->next; + if (idx->entity->m_trigger == 0) { + /* Move observed entity to triggered list. */ + dds_waitset_swap(&(ws->observed), &(ws->triggered), prev, idx); + } else { + prev = idx; + } + idx = next; + } + /* Check if any of the entities have been triggered. */ + idx = ws->observed; + prev = NULL; + while (idx != NULL) { + next = idx->next; + if (idx->entity->m_trigger > 0) { + /* Move observed entity to triggered list. */ + dds_waitset_swap(&(ws->triggered), &(ws->observed), prev, idx); + } else { + prev = idx; + } + idx = next; + } + + /* Only wait/keep waiting when whe have something to observer and there aren't any triggers yet. */ + rc = DDS_RETCODE_OK; + while ((ws->observed != NULL) && (ws->triggered == NULL) && (rc == DDS_RETCODE_OK)) { + if (abstimeout == DDS_NEVER) { + os_condWait(&ws->m_entity.m_cond, &ws->m_entity.m_mutex); + } else if (abstimeout <= tnow) { + rc = DDS_RETCODE_TIMEOUT; + } else { + dds_duration_t dt = abstimeout - tnow; + os_time to; + if ((dt / (dds_duration_t)DDS_NSECS_IN_SEC) >= (dds_duration_t)OS_TIME_INFINITE_SEC) { + to.tv_sec = OS_TIME_INFINITE_SEC; + to.tv_nsec = DDS_NSECS_IN_SEC - 1; + } else { + to.tv_sec = (os_timeSec) (dt / DDS_NSECS_IN_SEC); + to.tv_nsec = (uint32_t) (dt % DDS_NSECS_IN_SEC); + } + (void)os_condTimedWait(&ws->m_entity.m_cond, &ws->m_entity.m_mutex, &to); + tnow = dds_time(); + } + } + + /* Get number of triggered entities + * - set attach array when needed + * - swap them back to observed */ + if (rc == DDS_RETCODE_OK) { + ret = 0; + idx = ws->triggered; + while (idx != NULL) { + if ((uint32_t)ret < (uint32_t)nxs) { + xs[ret] = idx->arg; + } + ret++; + + next = idx->next; + /* The idx is always the first in triggered, so no prev. */ + dds_waitset_swap(&(ws->observed), &(ws->triggered), NULL, idx); + idx = next; + } + } else if (rc == DDS_RETCODE_TIMEOUT) { + ret = 0; + } else { + ret = DDS_ERRNO(rc, "Internal error"); + } + + dds_waitset_unlock(ws); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking waitset"); + } + + return ret; +} + +static void +dds_waitset_close_list( + _In_ dds_attachment **list, + _In_ dds_entity_t waitset) +{ + dds_attachment *idx = *list; + dds_attachment *next; + while (idx != NULL) { + next = idx->next; + (void)dds_entity_observer_unregister(idx->entity->m_hdl, waitset); + os_free(idx); + idx = next; + } + *list = NULL; +} + +static bool +dds_waitset_remove_from_list( + _In_ dds_attachment **list, + _In_ dds_entity_t observed) +{ + dds_attachment *idx = *list; + dds_attachment *prev = NULL; + + while (idx != NULL) { + if (idx->entity->m_hdl == observed) { + if (prev == NULL) { + *list = idx->next; + } else { + prev->next = idx->next; + } + os_free(idx); + + /* We're done. */ + return true; + } + prev = idx; + idx = idx->next; + } + return false; +} + +dds_return_t +dds_waitset_close( + struct dds_entity *e) +{ + dds_waitset *ws = (dds_waitset*)e; + + dds_waitset_close_list(&(ws->observed), e->m_hdl); + dds_waitset_close_list(&(ws->triggered), e->m_hdl); + + /* Trigger waitset to wake up. */ + os_condBroadcast(&e->m_cond); + + return DDS_RETCODE_OK; +} + +_Pre_satisfies_((participant & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT) +DDS_EXPORT _Must_inspect_result_ dds_entity_t +dds_create_waitset( + _In_ dds_entity_t participant) +{ + dds_entity_t hdl; + dds_entity *par; + dds__retcode_t rc; + + DDS_REPORT_STACK(); + + rc = dds_entity_lock(participant, DDS_KIND_PARTICIPANT, &par); + if (rc == DDS_RETCODE_OK) { + dds_waitset *waitset = dds_alloc(sizeof(*waitset)); + hdl = dds_entity_init(&waitset->m_entity, par, DDS_KIND_WAITSET, NULL, NULL, 0); + waitset->m_entity.m_deriver.close = dds_waitset_close; + waitset->observed = NULL; + waitset->triggered = NULL; + dds_entity_unlock(par); + } else { + hdl = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + DDS_REPORT_FLUSH(hdl <= 0); + return hdl; +} + + +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +DDS_EXPORT dds_return_t +dds_waitset_get_entities( + _In_ dds_entity_t waitset, + _Out_writes_to_(size, return < 0 ? 0 : return) dds_entity_t *entities, + _In_ size_t size) +{ + dds_return_t ret = 0; + dds__retcode_t rc; + dds_waitset *ws; + + DDS_REPORT_STACK(); + + rc = dds_waitset_lock(waitset, &ws); + if (rc == DDS_RETCODE_OK) { + dds_attachment* iter; + + iter = ws->observed; + while (iter) { + if (((size_t)ret < size) && (entities != NULL)) { + entities[ret] = iter->entity->m_hdl; + } + ret++; + iter = iter->next; + } + + iter = ws->triggered; + while (iter) { + if (((size_t)ret < size) && (entities != NULL)) { + entities[ret] = iter->entity->m_hdl; + } + ret++; + iter = iter->next; + } + dds_waitset_unlock(ws); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking waitset"); + } + DDS_REPORT_FLUSH(ret < 0); + return ret; +} + + +static void +dds_waitset_move( + _In_ dds_attachment **src, + _Inout_ dds_attachment **dst, + _In_ dds_entity_t entity) +{ + dds_attachment *idx = *src; + dds_attachment *prev = NULL; + while (idx != NULL) { + if (idx->entity->m_hdl == entity) { + /* Swap idx from src to dst. */ + dds_waitset_swap(dst, src, prev, idx); + + /* We're done. */ + return; + } + prev = idx; + idx = idx->next; + } +} + +static void +dds_waitset_remove( + dds_waitset *ws, + dds_entity_t observed) +{ + if (!dds_waitset_remove_from_list(&(ws->observed), observed)) { + (void)dds_waitset_remove_from_list(&(ws->triggered), observed); + } +} + +/* This is called when the observed entity signals a status change. */ +void +dds_waitset_observer( + dds_entity_t observer, + dds_entity_t observed, + uint32_t status) +{ + dds_waitset *ws; + if (dds_waitset_lock(observer, &ws) == DDS_RETCODE_OK) { + if (status & DDS_DELETING_STATUS) { + /* Remove this observed entity, which is being deleted, from the waitset. */ + dds_waitset_remove(ws, observed); + /* Our registration to this observed entity will be removed automatically. */ + } else if (status != 0) { + /* Move observed entity to triggered list. */ + dds_waitset_move(&(ws->observed), &(ws->triggered), observed); + } else { + /* Remove observed entity from triggered list (which it possibly resides in). */ + dds_waitset_move(&(ws->triggered), &(ws->observed), observed); + } + /* Trigger waitset to wake up. */ + os_condBroadcast(&ws->m_entity.m_cond); + dds_waitset_unlock(ws); + } +} + +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +DDS_EXPORT dds_return_t +dds_waitset_attach( + _In_ dds_entity_t waitset, + _In_ dds_entity_t entity, + _In_ dds_attach_t x) +{ + dds_entity *e = NULL; + dds_waitset *ws; + dds__retcode_t rc; + dds_return_t ret; + + DDS_REPORT_STACK(); + + rc = dds_waitset_lock(waitset, &ws); + if (rc == DDS_RETCODE_OK) { + if (waitset != entity) { + rc = dds_entity_lock(entity, DDS_KIND_DONTCARE, &e); + if (rc != DDS_RETCODE_OK) { + e = NULL; + } + } else { + e = (dds_entity*)ws; + } + + /* This will fail if given entity is already attached (or deleted). */ + if (rc == DDS_RETCODE_OK) { + rc = dds_entity_observer_register_nl(e, waitset, dds_waitset_observer); + } + + if (rc == DDS_RETCODE_OK) { + dds_attachment *a = os_malloc(sizeof(dds_attachment)); + a->arg = x; + a->entity = e; + if (e->m_trigger > 0) { + a->next = ws->triggered; + ws->triggered = a; + } else { + a->next = ws->observed; + ws->observed = a; + } + ret = DDS_RETCODE_OK; + } else if (rc != DDS_RETCODE_PRECONDITION_NOT_MET) { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Entity is not valid"); + } else { + ret = DDS_ERRNO(rc, "Entity is already attached."); + } + if ((e != NULL) && (waitset != entity)) { + dds_entity_unlock(e); + } + dds_waitset_unlock(ws); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking waitset"); + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK ); + return ret; +} + +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +DDS_EXPORT dds_return_t +dds_waitset_detach( + _In_ dds_entity_t waitset, + _In_ dds_entity_t entity) +{ + dds_waitset *ws; + dds__retcode_t rc; + dds_return_t ret; + + DDS_REPORT_STACK(); + + rc = dds_waitset_lock(waitset, &ws); + if (rc == DDS_RETCODE_OK) { + /* Possibly fails when entity was not attached. */ + if (waitset == entity) { + rc = dds_entity_observer_unregister_nl((dds_entity*)ws, waitset); + } else { + rc = dds_entity_observer_unregister(entity, waitset); + } + if (rc == DDS_RETCODE_OK) { + dds_waitset_remove(ws, entity); + ret = DDS_RETCODE_OK; + } else if (rc != DDS_RETCODE_PRECONDITION_NOT_MET) { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "The given entity to detach is invalid."); + } else { + ret = DDS_ERRNO(rc, "The given entity to detach was not attached previously."); + } + dds_waitset_unlock(ws); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking waitset"); + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +dds_return_t +dds_waitset_wait_until( + _In_ dds_entity_t waitset, + _Out_writes_to_opt_(nxs, return < 0 ? 0 : return) dds_attach_t *xs, + _In_ size_t nxs, + _In_ dds_time_t abstimeout) +{ + dds_return_t ret; + DDS_REPORT_STACK(); + ret = dds_waitset_wait_impl(waitset, xs, nxs, abstimeout, dds_time()); + DDS_REPORT_FLUSH(ret <0 ); + return ret; +} + +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +dds_return_t +dds_waitset_wait( + _In_ dds_entity_t waitset, + _Out_writes_to_opt_(nxs, return < 0 ? 0 : return) dds_attach_t *xs, + _In_ size_t nxs, + _In_ dds_duration_t reltimeout) +{ + dds_entity_t ret; + DDS_REPORT_STACK(); + + if (reltimeout >= 0) { + dds_time_t tnow = dds_time(); + dds_time_t abstimeout = (DDS_INFINITY - reltimeout <= tnow) ? DDS_NEVER : (tnow + reltimeout); + ret = dds_waitset_wait_impl(waitset, xs, nxs, abstimeout, tnow); + } else{ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Negative timeout"); + } + DDS_REPORT_FLUSH(ret <0 ); + return ret; +} + +_Pre_satisfies_((waitset & DDS_ENTITY_KIND_MASK) == DDS_KIND_WAITSET) +dds_return_t +dds_waitset_set_trigger( + _In_ dds_entity_t waitset, + _In_ bool trigger) +{ + dds_waitset *ws; + dds__retcode_t rc; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + /* Locking the waitset here will delay a possible deletion until it is + * unlocked. Even when the related mutex is unlocked when we want to send + * a signal. */ + rc = dds_waitset_lock(waitset, &ws); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking waitset"); + goto fail; + } + if (trigger) { + dds_entity_status_set(ws, DDS_WAITSET_TRIGGER_STATUS); + } else { + dds_entity_status_reset(ws, DDS_WAITSET_TRIGGER_STATUS); + } + dds_waitset_signal_entity(ws); + dds_waitset_unlock(ws); +fail: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + diff --git a/src/core/ddsc/src/dds_write.c b/src/core/ddsc/src/dds_write.c new file mode 100644 index 0000000..c9a1bc0 --- /dev/null +++ b/src/core/ddsc/src/dds_write.c @@ -0,0 +1,406 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include "dds__writer.h" +#include "dds__write.h" +#include "dds__tkmap.h" +#include "ddsi/q_error.h" +#include "ddsi/q_thread.h" +#include "q__osplser.h" +#include "dds__err.h" +#include "ddsi/q_transmit.h" +#include "ddsi/q_ephash.h" +#include "ddsi/q_config.h" +#include "ddsi/q_entity.h" +#include "dds__report.h" +#include "ddsi/q_radmin.h" +#include + + +#if OS_ATOMIC64_SUPPORT +typedef os_atomic_uint64_t fake_seq_t; +uint64_t fake_seq_next (fake_seq_t *x) { return os_atomic_inc64_nv (x); } +#else /* HACK */ +typedef os_atomic_uint32_t fake_seq_t; +uint64_t fake_seq_next (fake_seq_t *x) { return os_atomic_inc32_nv (x); } +#endif + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +dds_return_t +dds_write( + _In_ dds_entity_t writer, + _In_ const void *data) +{ + dds_return_t ret; + dds__retcode_t rc; + dds_writer *wr; + + DDS_REPORT_STACK(); + + if (data != NULL) { + rc = dds_writer_lock(writer, &wr); + if (rc == DDS_RETCODE_OK) { + ret = dds_write_impl(wr, data, dds_time(), 0); + dds_writer_unlock(wr); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking entity"); + } + } else { + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "No data buffer provided"); + } + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +int +dds_writecdr( + dds_entity_t writer, + const void *cdr, + size_t size) +{ + dds_return_t ret; + dds__retcode_t rc; + dds_writer *wr; + if (cdr != NULL) { + rc = dds_writer_lock(writer, &wr); + if (rc == DDS_RETCODE_OK) { + ret = dds_writecdr_impl (wr, cdr, size, dds_time (), 0); + dds_writer_unlock(wr); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking writer"); + } + } else{ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Given cdr has NULL value"); + } + return ret; +} + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +dds_return_t +dds_write_ts( + _In_ dds_entity_t writer, + _In_ const void *data, + _In_ dds_time_t timestamp) +{ + dds_return_t ret; + dds__retcode_t rc; + dds_writer *wr; + + DDS_REPORT_STACK(); + + if(data == NULL){ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument data has NULL value"); + goto err; + } + if(timestamp < 0){ + ret = DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Argument timestamp has negative value"); + goto err; + } + rc = dds_writer_lock(writer, &wr); + if (rc == DDS_RETCODE_OK) { + ret = dds_write_impl(wr, data, timestamp, 0); + dds_writer_unlock(wr); + } else { + ret = DDS_ERRNO(rc, "Error occurred on locking writer"); + } +err: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +static void +init_sampleinfo( + _Out_ struct nn_rsample_info *sampleinfo, + _In_ struct writer *wr, + _In_ int64_t seq, + _In_ serdata_t payload) +{ + memset(sampleinfo, 0, sizeof(*sampleinfo)); + sampleinfo->bswap = 0; + sampleinfo->complex_qos = 0; + sampleinfo->hashash = 0; + sampleinfo->seq = seq; + sampleinfo->reception_timestamp = payload->v.msginfo.timestamp; + sampleinfo->statusinfo = payload->v.msginfo.statusinfo; + sampleinfo->pwr_info.iid = 1; + sampleinfo->pwr_info.auto_dispose = 0; + sampleinfo->pwr_info.guid = wr->e.guid; + sampleinfo->pwr_info.ownership_strength = 0; +} + +static int +deliver_locally( + _In_ struct writer *wr, + _In_ int64_t seq, + _In_ serdata_t payload, + _In_ struct tkmap_instance *tk) +{ + dds_return_t ret = DDS_RETCODE_OK; + os_mutexLock (&wr->rdary.rdary_lock); + if (wr->rdary.fastpath_ok) { + struct reader ** const rdary = wr->rdary.rdary; + if (rdary[0]) { + struct nn_rsample_info sampleinfo; + unsigned i; + init_sampleinfo(&sampleinfo, wr, seq, payload); + for (i = 0; rdary[i]; i++) { + bool stored; + TRACE (("reader %x:%x:%x:%x\n", PGUID (rdary[i]->e.guid))); + dds_duration_t max_block_ms = nn_from_ddsi_duration(wr->xqos->reliability.max_blocking_time) / DDS_NSECS_IN_MSEC; + do { + stored = (ddsi_plugin.rhc_store_fn) (rdary[i]->rhc, &sampleinfo, payload, tk); + if (!stored) { + if (max_block_ms <= 0) { + ret = DDS_ERRNO(DDS_RETCODE_TIMEOUT, "The writer could not deliver data on time, probably due to a local reader resources being full."); + } else { + dds_sleepfor(DDS_MSECS(DDS_HEADBANG_TIMEOUT_MS)); + } + /* Decreasing the block time after the sleep, let's us possibly + * wait a bit too long. But that's preferable compared to waiting + * a bit too short. */ + max_block_ms -= DDS_HEADBANG_TIMEOUT_MS; + } + } while ((!stored) && (ret == DDS_RETCODE_OK)); + } + } + os_mutexUnlock (&wr->rdary.rdary_lock); + } else { + /* When deleting, pwr is no longer accessible via the hash + tables, and consequently, a reader may be deleted without + it being possible to remove it from rdary. The primary + reason rdary exists is to avoid locking the proxy writer + but this is less of an issue when we are deleting it, so + we fall back to using the GUIDs so that we can deliver all + samples we received from it. As writer being deleted any + reliable samples that are rejected are simply discarded. */ + ut_avlIter_t it; + struct pwr_rd_match *m; + struct nn_rsample_info sampleinfo; + os_mutexUnlock (&wr->rdary.rdary_lock); + init_sampleinfo(&sampleinfo, wr, seq, payload); + os_mutexLock (&wr->e.lock); + for (m = ut_avlIterFirst (&wr_local_readers_treedef, &wr->local_readers, &it); m != NULL; m = ut_avlIterNext (&it)) { + struct reader *rd; + if ((rd = ephash_lookup_reader_guid (&m->rd_guid)) != NULL) { + TRACE (("reader-via-guid %x:%x:%x:%x\n", PGUID (rd->e.guid))); + /* Copied the return value ignore from DDSI deliver_user_data() function. */ + (void)(ddsi_plugin.rhc_store_fn) (rd->rhc, &sampleinfo, payload, tk); + } + } + os_mutexUnlock (&wr->e.lock); + } + return ret; +} + +int +dds_write_impl( + _In_ dds_writer *wr, + _In_ const void * data, + _In_ dds_time_t tstamp, + _In_ dds_write_action action) +{ + static fake_seq_t fake_seq; + dds_return_t ret = DDS_RETCODE_OK; + int w_rc; + + assert (wr); + assert (dds_entity_kind(((dds_entity*)wr)->m_hdl) == DDS_KIND_WRITER); + + struct thread_state1 * const thr = lookup_thread_state (); + const bool asleep = !vtime_awake_p (thr->vtime); + const bool writekey = action & DDS_WR_KEY_BIT; + dds_writer * writer = (dds_writer*) wr; + struct writer * ddsi_wr = writer->m_wr; + struct tkmap_instance * tk; + serdata_t d; + + if (data == NULL) { + return DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "No data buffer provided"); + } + + /* Check for topic filter */ + if (ddsi_wr->topic->filter_fn && ! writekey) { + if (!(ddsi_wr->topic->filter_fn) (data, ddsi_wr->topic->filter_ctx)) { + goto filtered; + } + } + + if (asleep) { + thread_state_awake (thr); + } + + /* Serialize and write data or key */ + if (writekey) { + d = serialize_key (gv.serpool, ddsi_wr->topic, data); + } else { + d = serialize (gv.serpool, ddsi_wr->topic, data); + } + + /* Set if disposing or unregistering */ + d->v.msginfo.statusinfo = ((action & DDS_WR_DISPOSE_BIT ) ? NN_STATUSINFO_DISPOSE : 0) | + ((action & DDS_WR_UNREGISTER_BIT) ? NN_STATUSINFO_UNREGISTER : 0) ; + d->v.msginfo.timestamp.v = tstamp; + os_mutexLock (&writer->m_call_lock); + ddsi_serdata_ref(d); + tk = (ddsi_plugin.rhc_lookup_fn) (d); + w_rc = write_sample_gc (writer->m_xp, ddsi_wr, d, tk); + + if (w_rc >= 0) { + /* Flush out write unless configured to batch */ + if (! config.whc_batch){ + nn_xpack_send (writer->m_xp, false); + } + ret = DDS_RETCODE_OK; + } else if (w_rc == ERR_TIMEOUT) { + ret = DDS_ERRNO(DDS_RETCODE_TIMEOUT, "The writer could not deliver data on time, probably due to a reader resources being full."); + } else if (w_rc == ERR_INVALID_DATA) { + ret = DDS_ERRNO(DDS_RETCODE_ERROR, "Invalid data provided"); + } else { + ret = DDS_ERRNO(DDS_RETCODE_ERROR, "Internal error"); + } + os_mutexUnlock (&writer->m_call_lock); + + if (ret == DDS_RETCODE_OK) { + ret = deliver_locally (ddsi_wr, fake_seq_next(&fake_seq), d, tk); + } + ddsi_serdata_unref(d); + (ddsi_plugin.rhc_unref_fn) (tk); + + if (asleep) { + thread_state_asleep (thr); + } + +filtered: + return ret; +} + +int +dds_writecdr_impl( + _In_ dds_writer *wr, + _In_ const void *cdr, + _In_ size_t sz, + _In_ dds_time_t tstamp, + _In_ dds_write_action action) +{ + static fake_seq_t fake_seq; + int ret = DDS_RETCODE_OK; + int w_rc; + + assert (wr); + assert (cdr); + + struct thread_state1 * const thr = lookup_thread_state (); + const bool asleep = !vtime_awake_p (thr->vtime); + const bool writekey = action & DDS_WR_KEY_BIT; + struct writer * ddsi_wr = wr->m_wr; + struct tkmap_instance * tk; + serdata_t d; + + /* Check for topic filter */ + if (ddsi_wr->topic->filter_fn && ! writekey) { + abort(); + } + + if (asleep) { + thread_state_awake (thr); + } + + /* Serialize and write data or key */ + if (writekey) { + abort(); + //d = serialize_key (gv.serpool, ddsi_wr->topic, data); + } else { + serstate_t st = ddsi_serstate_new (gv.serpool, ddsi_wr->topic); + if (ddsi_wr->topic->nkeys) { + abort(); + //dds_key_gen ((const dds_topic_descriptor_t*) tp->type, &st->data->v.keyhash, (char*) sample); + } + ddsi_serstate_append_blob(st, 1, sz, cdr); + d = ddsi_serstate_fix(st); + } + + /* Set if disposing or unregistering */ + d->v.msginfo.statusinfo = ((action & DDS_WR_DISPOSE_BIT ) ? NN_STATUSINFO_DISPOSE : 0) | + ((action & DDS_WR_UNREGISTER_BIT) ? NN_STATUSINFO_UNREGISTER : 0) ; + d->v.msginfo.timestamp.v = tstamp; + os_mutexLock (&wr->m_call_lock); + ddsi_serdata_ref(d); + tk = (ddsi_plugin.rhc_lookup_fn) (d); + w_rc = write_sample_gc (wr->m_xp, ddsi_wr, d, tk); + + if (w_rc >= 0) { + /* Flush out write unless configured to batch */ + if (! config.whc_batch) { + nn_xpack_send (wr->m_xp, false); + } + ret = DDS_RETCODE_OK; + } else if (w_rc == ERR_TIMEOUT) { + ret = DDS_ERRNO(DDS_RETCODE_TIMEOUT, "The writer could not deliver data on time, probably due to a reader resources being full."); + } else if (w_rc == ERR_INVALID_DATA) { + ret = DDS_ERRNO(DDS_RETCODE_ERROR, "Invalid data provided"); + } else { + ret = DDS_ERRNO(DDS_RETCODE_ERROR, "Internal error"); + } + os_mutexUnlock (&wr->m_call_lock); + + if (ret == DDS_RETCODE_OK) { + ret = deliver_locally (ddsi_wr, fake_seq_next(&fake_seq), d, tk); + } + ddsi_serdata_unref(d); + (ddsi_plugin.rhc_unref_fn) (tk); + + if (asleep) { + thread_state_asleep (thr); + } + + return ret; +} + +void +dds_write_set_batch( + bool enable) +{ + config.whc_batch = enable ? 1 : 0; +} + +_Pre_satisfies_((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER) +void +dds_write_flush( + dds_entity_t writer) +{ + dds_return_t ret = DDS_RETCODE_OK; + dds__retcode_t rc; + DDS_REPORT_STACK(); + + struct thread_state1 * const thr = lookup_thread_state (); + const bool asleep = !vtime_awake_p (thr->vtime); + dds_writer *wr; + + if (asleep) { + thread_state_awake (thr); + } + rc = dds_writer_lock(writer, &wr); + if (rc == DDS_RETCODE_OK) { + nn_xpack_send (wr->m_xp, true); + dds_writer_unlock(wr); + ret = DDS_RETCODE_OK; + } else{ + ret = DDS_ERRNO(rc, "Error occurred on locking writer"); + } + + if (asleep) { + thread_state_asleep (thr); + } + DDS_REPORT_FLUSH(ret < 0); + return ; +} diff --git a/src/core/ddsc/src/dds_writer.c b/src/core/ddsc/src/dds_writer.c new file mode 100644 index 0000000..5869cbd --- /dev/null +++ b/src/core/ddsc/src/dds_writer.c @@ -0,0 +1,611 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include "ddsc/dds.h" +#include "ddsi/q_config.h" +#include "ddsi/q_entity.h" +#include "ddsi/q_thread.h" +#include "q__osplser.h" +#include "dds__writer.h" +#include "dds__listener.h" +#include "dds__qos.h" +#include "dds__err.h" +#include "dds__init.h" +#include "dds__tkmap.h" +#include "dds__report.h" +#include "ddsc/ddsc_project.h" + +#define DDS_WRITER_STATUS_MASK \ + DDS_LIVELINESS_LOST_STATUS |\ + DDS_OFFERED_DEADLINE_MISSED_STATUS |\ + DDS_OFFERED_INCOMPATIBLE_QOS_STATUS |\ + DDS_PUBLICATION_MATCHED_STATUS + +static dds_return_t +dds_writer_instance_hdl( + dds_entity *e, + dds_instance_handle_t *i) +{ + assert(e); + assert(i); + *i = (dds_instance_handle_t)writer_instance_id(&e->m_guid); + return DDS_RETCODE_OK; +} + +static dds_return_t +dds_writer_status_validate( + uint32_t mask) +{ + return (mask & ~(DDS_WRITER_STATUS_MASK)) ? + DDS_ERRNO(DDS_RETCODE_BAD_PARAMETER, "Invalid status mask") : + DDS_RETCODE_OK; +} + +/* + Handler function for all write related status callbacks. May trigger status + condition or call listener on writer. Each entity has a mask of + supported status types. According to DDS specification, if listener is called + then status conditions is not triggered. +*/ + +static void +dds_writer_status_cb( + void *entity, + const status_cb_data_t *data) +{ + dds_writer *wr; + dds__retcode_t rc; + void *metrics = NULL; + + /* When data is NULL, it means that the writer is deleted. */ + if (data == NULL) { + /* Release the initial claim that was done during the create. This + * will indicate that further API deletion is now possible. */ + ut_handle_release(((dds_entity*)entity)->m_hdl, ((dds_entity*)entity)->m_hdllink); + return; + } + + DDS_REPORT_STACK(); + + if (dds_writer_lock(((dds_entity*)entity)->m_hdl, &wr) != DDS_RETCODE_OK) { + /* There's a deletion or closing going on. */ + DDS_REPORT_FLUSH(false); + return; + } + assert(wr == entity); + + /* Reset the status for possible Listener call. + * When a listener is not called, the status will be set (again). */ + dds_entity_status_reset(entity, data->status); + + /* Update status metrics. */ + switch (data->status) { + case DDS_OFFERED_DEADLINE_MISSED_STATUS: { + wr->m_offered_deadline_missed_status.total_count++; + wr->m_offered_deadline_missed_status.total_count_change++; + wr->m_offered_deadline_missed_status.last_instance_handle = data->handle; + metrics = (void*)&(wr->m_offered_deadline_missed_status); + break; + } + case DDS_LIVELINESS_LOST_STATUS: { + wr->m_liveliness_lost_status.total_count++; + wr->m_liveliness_lost_status.total_count_change++; + metrics = (void*)&(wr->m_liveliness_lost_status); + break; + } + case DDS_OFFERED_INCOMPATIBLE_QOS_STATUS: { + wr->m_offered_incompatible_qos_status.total_count++; + wr->m_offered_incompatible_qos_status.total_count_change++; + wr->m_offered_incompatible_qos_status.last_policy_id = data->extra; + metrics = (void*)&(wr->m_offered_incompatible_qos_status); + break; + } + case DDS_PUBLICATION_MATCHED_STATUS: { + if (data->add) { + wr->m_publication_matched_status.total_count++; + wr->m_publication_matched_status.total_count_change++; + wr->m_publication_matched_status.current_count++; + wr->m_publication_matched_status.current_count_change++; + } else { + wr->m_publication_matched_status.current_count--; + wr->m_publication_matched_status.current_count_change--; + } + wr->m_publication_matched_status.last_subscription_handle = data->handle; + metrics = (void*)&(wr->m_publication_matched_status); + break; + } + default: assert (0); + } + + /* The writer needs to be unlocked when propagating the (possible) listener + * call because the application should be able to call this writer within + * the callback function. */ + dds_writer_unlock(wr); + + /* Is anybody interested within the entity hierarchy through listeners? */ + rc = dds_entity_listener_propagation(entity, entity, data->status, metrics, true); + + if (rc == DDS_RETCODE_OK) { + /* Event was eaten by a listener. */ + if (dds_writer_lock(((dds_entity*)entity)->m_hdl, &wr) == DDS_RETCODE_OK) { + assert(wr == entity); + + /* Reset the status. */ + dds_entity_status_reset(entity, data->status); + + /* Reset the change counts of the metrics. */ + switch (data->status) { + case DDS_OFFERED_DEADLINE_MISSED_STATUS: { + wr->m_offered_deadline_missed_status.total_count_change = 0; + break; + } + case DDS_LIVELINESS_LOST_STATUS: { + wr->m_liveliness_lost_status.total_count_change = 0; + break; + } + case DDS_OFFERED_INCOMPATIBLE_QOS_STATUS: { + wr->m_offered_incompatible_qos_status.total_count_change = 0; + break; + } + case DDS_PUBLICATION_MATCHED_STATUS: { + wr->m_publication_matched_status.total_count_change = 0; + wr->m_publication_matched_status.current_count_change = 0; + break; + } + default: assert (0); + } + dds_writer_unlock(wr); + } else { + /* There's a deletion or closing going on. */ + } + } else if (rc == DDS_RETCODE_NO_DATA) { + /* Nobody was interested through a listener (NO_DATA == NO_CALL): set the status. */ + dds_entity_status_set(entity, data->status); + /* Notify possible interested observers. */ + dds_entity_status_signal(entity); + rc = DDS_RETCODE_OK; + } else if (rc == DDS_RETCODE_ALREADY_DELETED) { + /* An entity up the hierarchy is being deleted. */ + rc = DDS_RETCODE_OK; + } else { + /* Something went wrong up the hierarchy. */ + } + + DDS_REPORT_FLUSH(rc != DDS_RETCODE_OK); +} + +static uint32_t +get_bandwidth_limit( + nn_transport_priority_qospolicy_t transport_priority) +{ +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + struct config_channel_listelem *channel = find_channel (transport_priority); + return channel->data_bandwidth_limit; +#else + return 0; +#endif +} + +static dds_return_t +dds_writer_close( + dds_entity *e) +{ + dds_return_t ret = DDS_RETCODE_OK; + dds_writer *wr = (dds_writer*)e; + struct thread_state1 * const thr = lookup_thread_state(); + const bool asleep = thr ? !vtime_awake_p(thr->vtime) : false; + + assert(e); + + if (asleep) { + thread_state_awake(thr); + } + if (thr) { + nn_xpack_send (wr->m_xp, false); + } + if (delete_writer (&e->m_guid) != 0) { + ret = DDS_ERRNO(DDS_RETCODE_ERROR, "Internal error"); + } + if (asleep) { + thread_state_asleep(thr); + } + return ret; +} + +static dds_return_t +dds_writer_delete( + dds_entity *e) +{ + dds_writer *wr = (dds_writer*)e; + struct thread_state1 * const thr = lookup_thread_state(); + const bool asleep = thr ? !vtime_awake_p(thr->vtime) : false; + dds_return_t ret; + + assert(e); + assert(thr); + + if (asleep) { + thread_state_awake(thr); + } + if (thr) { + nn_xpack_free(wr->m_xp); + } + if (asleep) { + thread_state_asleep(thr); + } + ret = dds_delete(wr->m_topic->m_entity.m_hdl); + if(ret == DDS_RETCODE_OK){ + ret = dds_delete_impl(e->m_parent->m_hdl, true); + if(dds_err_nr(ret) == DDS_RETCODE_ALREADY_DELETED){ + ret = DDS_RETCODE_OK; + } + } + os_mutexDestroy(&wr->m_call_lock); + return ret; +} + + +static dds_return_t +dds_writer_qos_validate( + const dds_qos_t *qos, + bool enabled) +{ + dds_return_t ret = DDS_RETCODE_OK; + bool consistent = true; + + assert(qos); + + /* Check consistency. */ + if(dds_qos_validate_common(qos) != true){ + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Provided inconsistent QoS policy"); + } + if((qos->present & QP_USER_DATA) && validate_octetseq(&qos->user_data) != true){ + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "User Data QoS policy is inconsistent and caused an error"); + } + if ((qos->present & QP_DURABILITY_SERVICE) && validate_durability_service_qospolicy(&qos->durability_service) != 0){ + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Durability service QoS policy is inconsistent and caused an error"); + } + if ((qos->present & QP_LIFESPAN) && validate_duration(&qos->lifespan.duration) != 0){ + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Lifespan QoS policy is inconsistent and caused an error"); + } + if ((qos->present & QP_HISTORY) && (qos->present & QP_RESOURCE_LIMITS) && (validate_history_and_resource_limits(&qos->history, &qos->resource_limits) != 0)){ + ret = DDS_ERRNO(DDS_RETCODE_INCONSISTENT_POLICY, "Resource limits QoS policy is inconsistent and caused an error"); + } + if(ret == DDS_RETCODE_OK && enabled) { + ret = dds_qos_validate_mutable_common(qos); + } + return ret; +} + +static dds_return_t +dds_writer_qos_set( + dds_entity *e, + const dds_qos_t *qos, + bool enabled) +{ + dds_return_t ret = dds_writer_qos_validate(qos, enabled); + if (ret == DDS_RETCODE_OK) { + /* + * TODO: CHAM-95: DDSI does not support changing QoS policies. + * + * Only Ownership is required for the minimum viable product. This seems + * to be the only QoS policy that DDSI supports changes on. + */ + if (qos->present & QP_OWNERSHIP_STRENGTH) { + dds_ownership_kind_t kind; + /* check for ownership before updating, ownership strength is applicable only if + * writer is exclusive */ + dds_qget_ownership (e->m_qos, &kind); + + if (kind == DDS_OWNERSHIP_EXCLUSIVE) { + struct thread_state1 * const thr = lookup_thread_state (); + const bool asleep = !vtime_awake_p (thr->vtime); + struct writer * ddsi_wr = ((dds_writer*)e)->m_wr; + + dds_qset_ownership_strength (e->m_qos, qos->ownership_strength.value); + + if (asleep) { + thread_state_awake (thr); + } + + os_mutexLock (&((dds_writer*)e)->m_call_lock); + if (qos->ownership_strength.value != ddsi_wr->xqos->ownership_strength.value) { + ddsi_wr->xqos->ownership_strength.value = qos->ownership_strength.value; + } + os_mutexUnlock (&((dds_writer*)e)->m_call_lock); + + if (asleep) { + thread_state_asleep (thr); + } + } else { + ret = DDS_ERRNO(DDS_RETCODE_ERROR, "Setting ownership strength doesn't make sense when the ownership is shared."); + } + } else { + if (enabled) { + ret = DDS_ERRNO(DDS_RETCODE_UNSUPPORTED, DDSC_PROJECT_NAME" does not support changing QoS policies yet"); + } + } + } + return ret; +} + + +_Pre_satisfies_(((participant_or_publisher & DDS_ENTITY_KIND_MASK) == DDS_KIND_PUBLISHER) || \ + ((participant_or_publisher & DDS_ENTITY_KIND_MASK) == DDS_KIND_PARTICIPANT)) +_Pre_satisfies_((topic & DDS_ENTITY_KIND_MASK) == DDS_KIND_TOPIC) +dds_entity_t +dds_create_writer( + _In_ dds_entity_t participant_or_publisher, + _In_ dds_entity_t topic, + _In_opt_ const dds_qos_t *qos, + _In_opt_ const dds_listener_t *listener) +{ + dds__retcode_t rc; + dds_qos_t * wqos; + dds_writer * wr; + dds_entity_t writer; + dds_entity * pub = NULL; + dds_entity * tp; + dds_entity_t publisher; + struct thread_state1 * const thr = lookup_thread_state(); + const bool asleep = !vtime_awake_p(thr->vtime); + ddsi_tran_conn_t conn = gv.data_conn_mc ? gv.data_conn_mc : gv.data_conn_uc; + dds_return_t ret; + + DDS_REPORT_STACK(); + + /* Try claiming a participant. If that's not working, then it could be a subscriber. */ + if(dds_entity_kind(participant_or_publisher) == DDS_KIND_PARTICIPANT){ + publisher = dds_create_publisher(participant_or_publisher, qos, NULL); + } else{ + publisher = participant_or_publisher; + } + rc = dds_entity_lock(publisher, DDS_KIND_PUBLISHER, &pub); + + if (rc != DDS_RETCODE_OK) { + writer = DDS_ERRNO(rc, "Error occurred on locking publisher"); + goto err_pub_lock; + } + + if (publisher != participant_or_publisher) { + pub->m_flags |= DDS_ENTITY_IMPLICIT; + } + + rc = dds_entity_lock(topic, DDS_KIND_TOPIC, &tp); + if (rc != DDS_RETCODE_OK) { + writer = DDS_ERRNO(rc, "Error occurred on locking topic"); + goto err_tp_lock; + } + assert(((dds_topic*)tp)->m_stopic); + assert(pub->m_domain == tp->m_domain); + + /* Merge Topic & Publisher qos */ + wqos = dds_qos_create(); + if (qos) { + /* Only returns failure when one of the qos args is NULL, which + * is not the case here. */ + (void)dds_qos_copy(wqos, qos); + } + + if (pub->m_qos) { + dds_qos_merge(wqos, pub->m_qos); + } + + if (tp->m_qos) { + /* merge topic qos data to writer qos */ + dds_qos_merge(wqos, tp->m_qos); + } + nn_xqos_mergein_missing(wqos, &gv.default_xqos_wr); + + ret = dds_writer_qos_validate(wqos, false); + if (ret != 0) { + dds_qos_delete(wqos); + writer = ret; + goto err_bad_qos; + } + + /* Create writer */ + wr = dds_alloc(sizeof (*wr)); + writer = dds_entity_init(&wr->m_entity, pub, DDS_KIND_WRITER, wqos, listener, DDS_WRITER_STATUS_MASK); + + wr->m_topic = (dds_topic*)tp; + dds_entity_add_ref_nolock(tp); + wr->m_xp = nn_xpack_new(conn, get_bandwidth_limit(wqos->transport_priority), config.xpack_send_async); + os_mutexInit (&wr->m_call_lock); + wr->m_entity.m_deriver.close = dds_writer_close; + wr->m_entity.m_deriver.delete = dds_writer_delete; + wr->m_entity.m_deriver.set_qos = dds_writer_qos_set; + wr->m_entity.m_deriver.validate_status = dds_writer_status_validate; + wr->m_entity.m_deriver.get_instance_hdl = dds_writer_instance_hdl; + + /* Extra claim of this writer to make sure that the delete waits until DDSI + * has deleted its writer as well. This can be known through the callback. */ + if (ut_handle_claim(wr->m_entity.m_hdl, wr->m_entity.m_hdllink, DDS_KIND_WRITER, NULL) != UT_HANDLE_OK) { + assert(0); + } + + os_mutexUnlock(&tp->m_mutex); + os_mutexUnlock(&pub->m_mutex); + + if (asleep) { + thread_state_awake(thr); + } + wr->m_wr = new_writer(&wr->m_entity.m_guid, NULL, &pub->m_participant->m_guid, ((dds_topic*)tp)->m_stopic, + wqos, dds_writer_status_cb, wr); + os_mutexLock(&pub->m_mutex); + os_mutexLock(&tp->m_mutex); + assert(wr->m_wr); + if (asleep) { + thread_state_asleep(thr); + } + dds_entity_unlock(tp); + dds_entity_unlock(pub); + DDS_REPORT_FLUSH(writer <= 0); + return writer; + +err_bad_qos: + dds_entity_unlock(tp); +err_tp_lock: + dds_entity_unlock(pub); + if((pub->m_flags & DDS_ENTITY_IMPLICIT) != 0){ + (void)dds_delete(publisher); + } +err_pub_lock: + DDS_REPORT_FLUSH(writer <= 0); + return writer; +} + + + +_Pre_satisfies_(((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER)) +dds_entity_t +dds_get_publisher( + _In_ dds_entity_t writer) +{ + dds_entity_t hdl = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + hdl = dds_valid_hdl(writer, DDS_KIND_WRITER); + if(hdl != DDS_RETCODE_OK){ + hdl = DDS_ERRNO(hdl, "Provided handle is not writer kind, so it is not valid"); + } else{ + hdl = dds_get_parent(writer); + } + DDS_REPORT_FLUSH(hdl <= 0); + return hdl; +} + +_Pre_satisfies_(((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER)) +dds_return_t +dds_get_publication_matched_status ( + _In_ dds_entity_t writer, + _Out_opt_ dds_publication_matched_status_t * status) +{ + dds__retcode_t rc; + dds_writer *wr; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + rc = dds_writer_lock(writer, &wr); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking writer"); + goto fail; + } + /* status = NULL, application do not need the status, but reset the counter & triggered bit */ + if (status) { + *status = wr->m_publication_matched_status; + } + if (((dds_entity*)wr)->m_status_enable & DDS_PUBLICATION_MATCHED_STATUS) { + wr->m_publication_matched_status.total_count_change = 0; + wr->m_publication_matched_status.current_count_change = 0; + dds_entity_status_reset(wr, DDS_PUBLICATION_MATCHED_STATUS); + } + dds_writer_unlock(wr); +fail: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_(((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER)) +dds_return_t +dds_get_liveliness_lost_status ( + _In_ dds_entity_t writer, + _Out_opt_ dds_liveliness_lost_status_t * status) +{ + dds__retcode_t rc; + dds_writer *wr; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + rc = dds_writer_lock(writer, &wr); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking writer"); + goto fail; + } + /* status = NULL, application do not need the status, but reset the counter & triggered bit */ + if (status) { + *status = wr->m_liveliness_lost_status; + } + if (((dds_entity*)wr)->m_status_enable & DDS_LIVELINESS_LOST_STATUS) { + wr->m_liveliness_lost_status.total_count_change = 0; + dds_entity_status_reset(wr, DDS_LIVELINESS_LOST_STATUS); + } + dds_writer_unlock(wr); +fail: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_(((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER)) +dds_return_t +dds_get_offered_deadline_missed_status( + _In_ dds_entity_t writer, + _Out_opt_ dds_offered_deadline_missed_status_t *status) +{ + dds__retcode_t rc; + dds_writer *wr; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + rc = dds_writer_lock(writer, &wr); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking writer"); + goto fail; + } + /* status = NULL, application do not need the status, but reset the counter & triggered bit */ + if (status) { + *status = wr->m_offered_deadline_missed_status; + } + if (((dds_entity*)wr)->m_status_enable & DDS_OFFERED_DEADLINE_MISSED_STATUS) { + wr->m_offered_deadline_missed_status.total_count_change = 0; + dds_entity_status_reset(wr, DDS_OFFERED_DEADLINE_MISSED_STATUS); + } + dds_writer_unlock(wr); +fail: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} + +_Pre_satisfies_(((writer & DDS_ENTITY_KIND_MASK) == DDS_KIND_WRITER)) +dds_return_t +dds_get_offered_incompatible_qos_status ( + _In_ dds_entity_t writer, + _Out_opt_ dds_offered_incompatible_qos_status_t * status) +{ + dds__retcode_t rc; + dds_writer *wr; + dds_return_t ret = DDS_RETCODE_OK; + + DDS_REPORT_STACK(); + + rc = dds_writer_lock(writer, &wr); + if (rc != DDS_RETCODE_OK) { + ret = DDS_ERRNO(rc, "Error occurred on locking writer"); + goto fail; + } + /* status = NULL, application do not need the status, but reset the counter & triggered bit */ + if (status) { + *status = wr->m_offered_incompatible_qos_status; + } + if (((dds_entity*)wr)->m_status_enable & DDS_OFFERED_INCOMPATIBLE_QOS_STATUS) { + wr->m_offered_incompatible_qos_status.total_count_change = 0; + dds_entity_status_reset(wr, DDS_OFFERED_INCOMPATIBLE_QOS_STATUS); + } + dds_writer_unlock(wr); +fail: + DDS_REPORT_FLUSH(ret != DDS_RETCODE_OK); + return ret; +} diff --git a/src/core/ddsc/src/q__osplser.h b/src/core/ddsc/src/q__osplser.h new file mode 100644 index 0000000..febda73 --- /dev/null +++ b/src/core/ddsc/src/q__osplser.h @@ -0,0 +1,39 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef DDS_OSPLSER_H +#define DDS_OSPLSER_H + +#include "ddsi/ddsi_ser.h" +#include "ddsi/q_xmsg.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +int serdata_cmp (const struct serdata * a, const struct serdata * b); +uint32_t serdata_hash (const struct serdata *a); + +serdata_t serialize (serstatepool_t pool, const struct sertopic * tp, const void * sample); +serdata_t serialize_key (serstatepool_t pool, const struct sertopic * tp, const void * sample); + +void deserialize_into (void *sample, const struct serdata *serdata); +void free_deserialized (const struct serdata *serdata, void *vx); + +void sertopic_free (struct sertopic * tp); +void serstate_set_key (serstate_t st, int justkey, const void *key); +void serstate_init (serstate_t st, const struct sertopic * topic); +void serstate_free (serstate_t st); + +#if defined (__cplusplus) +} +#endif +#endif diff --git a/src/core/ddsc/src/q_osplser.c b/src/core/ddsc/src/q_osplser.c new file mode 100644 index 0000000..e0ed6e7 --- /dev/null +++ b/src/core/ddsc/src/q_osplser.c @@ -0,0 +1,153 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include "os/os.h" +#include "dds__key.h" +#include "dds__tkmap.h" +#include "dds__stream.h" +#include "ddsi/q_bswap.h" +#include "q__osplser.h" + +serdata_t serialize (serstatepool_t pool, const struct sertopic * tp, const void * sample) +{ + dds_stream_t os; + serstate_t st = ddsi_serstate_new (pool, tp); + + if (tp->nkeys) + { + dds_key_gen ((const dds_topic_descriptor_t*) tp->type, &st->data->v.keyhash, (char*) sample); + } + dds_stream_from_serstate (&os, st); + dds_stream_write_sample (&os, sample, tp); + dds_stream_add_to_serstate (&os, st); + return st->data; +} + +int serdata_cmp (const struct serdata *a, const struct serdata *b) +{ + /* First compare on topic */ + + if (a->v.st->topic != b->v.st->topic) + { + return a->v.st->topic < b->v.st->topic ? -1 : 1; + } + + /* Samples with a keyless topic map to the default instance */ + + if + ( + (a->v.st->topic) && + (((dds_topic_descriptor_t*) a->v.st->topic->type)->m_keys == 0) + ) + { + return 0; + } + + /* Check key has been hashed */ + + assert (a->v.keyhash.m_flags & DDS_KEY_HASH_SET); + + /* Compare by hash */ + + return memcmp (a->v.keyhash.m_hash, b->v.keyhash.m_hash, 16); +} + +serdata_t serialize_key (serstatepool_t pool, const struct sertopic * tp, const void * sample) +{ + serdata_t sd; + if (tp->nkeys) + { + dds_stream_t os; + dds_topic_descriptor_t * desc = (dds_topic_descriptor_t*) tp->type; + serstate_t st = ddsi_serstate_new (pool, tp); + dds_key_gen (desc, &st->data->v.keyhash, (char*) sample); + dds_stream_from_serstate (&os, st); + dds_stream_write_key (&os, sample, desc); + dds_stream_add_to_serstate (&os, st); + sd = st->data; + } + else + { + sd = serialize (pool, tp, sample); + } + sd->v.st->kind = STK_KEY; + return sd; +} + +void deserialize_into (void * sample, const struct serdata * serdata) +{ + const serstate_t st = serdata->v.st; + dds_stream_t is; + + dds_stream_from_serstate (&is, st); + if (st->kind == STK_KEY) + { + dds_stream_read_key (&is, sample, (const dds_topic_descriptor_t*) st->topic->type); + } + else + { + dds_stream_read_sample (&is, sample, st->topic); + } +} + +void serstate_set_key (serstate_t st, int justkey, const void *key) +{ + st->kind = justkey ? STK_KEY : STK_DATA; + memcpy (&st->data->v.keyhash.m_hash, key, 16); + st->data->v.keyhash.m_flags = DDS_KEY_SET | DDS_KEY_HASH_SET | DDS_KEY_IS_HASH; + st->data->v.keyhash.m_key_len = 16; +} + +void serstate_init (serstate_t st, const struct sertopic * topic) +{ + st->pos = 0; + st->topic = topic; + st->kind = STK_DATA; + st->twrite.v = -1; + os_atomic_st32 (&st->refcount, 1); + + if (topic) + { + os_atomic_inc32 (&(((struct sertopic *) topic)->refcount)); + } + + st->data->hdr.identifier = topic ? + (PLATFORM_IS_LITTLE_ENDIAN ? CDR_LE : CDR_BE) : + (PLATFORM_IS_LITTLE_ENDIAN ? PL_CDR_LE : PL_CDR_BE); + + st->data->v.hash_valid = (topic == NULL || topic->nkeys) ? 0 : 1; + st->data->v.hash = 0; + st->data->v.bswap = false; + memset (st->data->v.keyhash.m_hash, 0, sizeof (st->data->v.keyhash.m_hash)); + st->data->v.keyhash.m_key_len = 0; + st->data->v.keyhash.m_flags = 0; +} + +void serstate_free (serstate_t st) +{ + dds_free (st->data->v.keyhash.m_key_buff); + dds_free (st->data); + dds_free (st); +} + +void sertopic_free (struct sertopic * tp) +{ + if (tp && (os_atomic_dec32_nv (&tp->refcount) == 0)) + { + dds_free (tp->name); + dds_free (tp->typename); + dds_free (tp->name_typename); + dds_sample_free (tp->filter_sample, (const struct dds_topic_descriptor *) tp->type, DDS_FREE_ALL); + dds_free (tp); + } +} diff --git a/src/core/ddsc/tests/CMakeLists.txt b/src/core/ddsc/tests/CMakeLists.txt new file mode 100644 index 0000000..d3822ac --- /dev/null +++ b/src/core/ddsc/tests/CMakeLists.txt @@ -0,0 +1,33 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +include(Criterion) + +idlc_generate(RoundTrip RoundTrip.idl) +idlc_generate(Space Space.idl) +idlc_generate(TypesArrayKey TypesArrayKey.idl) +add_criterion_executable(criterion_ddsc .) +target_include_directories(criterion_ddsc PRIVATE + "$") +target_link_libraries(criterion_ddsc RoundTrip Space TypesArrayKey ddsc OSAPI) + +# Setup environment for config-tests +set(Criterion_ddsc_config_simple_udp_file "${CMAKE_CURRENT_LIST_DIR}/config_simple_udp.xml") +set(Criterion_ddsc_config_simple_udp_uri "file://${Criterion_ddsc_config_simple_udp_file}") +set(Criterion_ddsc_config_simple_udp_max_participants "0") +set_tests_properties( + Criterion_ddsc_config_simple_udp + PROPERTIES + REQUIRED_FILES ${Criterion_ddsc_config_simple_udp_file} + ENVIRONMENT "${CMAKE_PROJECT_NAME_CAPS}_URI=${Criterion_ddsc_config_simple_udp_uri};MAX_PARTICIPANTS=${Criterion_ddsc_config_simple_udp_max_participants}" + +) +configure_file("config_env.h.in" "config_env.h") diff --git a/src/core/ddsc/tests/RoundTrip.idl b/src/core/ddsc/tests/RoundTrip.idl new file mode 100644 index 0000000..2e83792 --- /dev/null +++ b/src/core/ddsc/tests/RoundTrip.idl @@ -0,0 +1,26 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +module RoundTripModule +{ + struct DataType + { + sequence payload; + }; + #pragma keylist DataType + + struct Address + { + string ip; + long port; + }; + #pragma keylist Address ip port +}; diff --git a/src/core/ddsc/tests/Space.idl b/src/core/ddsc/tests/Space.idl new file mode 100644 index 0000000..3ebbc48 --- /dev/null +++ b/src/core/ddsc/tests/Space.idl @@ -0,0 +1,40 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +module Space { + struct Type1 { + long long_1; //@Key + long long_2; + long long_3; + }; +#pragma keylist Type1 long_1 + struct Type2 { + long long_1; //@Key + long long_2; + long long_3; + }; +#pragma keylist Type2 long_1 + + struct simpletypes { + long l; + long long ll; + unsigned short us; + unsigned long ul; + unsigned long long ull; + float f; + double d; + char c; + boolean b; + octet o; + string s; //@Key + }; +#pragma keylist simpletypes s +}; diff --git a/src/core/ddsc/tests/TypesArrayKey.idl b/src/core/ddsc/tests/TypesArrayKey.idl new file mode 100644 index 0000000..974de00 --- /dev/null +++ b/src/core/ddsc/tests/TypesArrayKey.idl @@ -0,0 +1,81 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +module TypesArrayKey { + struct long_arraytypekey { + long key[20]; //@Key + }; +#pragma keylist long_arraytypekey key + + typedef long long longlong; + struct longlong_arraytypekey { + longlong key[20]; //@Key + }; +#pragma keylist longlong_arraytypekey key + + typedef unsigned short unsignedshort; + struct unsignedshort_arraytypekey { + unsignedshort key[20]; //@Key + }; +#pragma keylist unsignedshort_arraytypekey key + + typedef unsigned long unsignedlong; + struct unsignedlong_arraytypekey { + unsignedlong key[20]; //@Key + }; +#pragma keylist unsignedlong_arraytypekey key + + typedef unsigned long long unsignedlonglong; + struct unsignedlonglong_arraytypekey { + unsignedlonglong key[20]; //@Key + }; +#pragma keylist unsignedlonglong_arraytypekey key + + struct float_arraytypekey { + float key[20]; //@Key + }; +#pragma keylist float_arraytypekey key + + struct double_arraytypekey { + double key[20]; //@Key + }; +#pragma keylist double_arraytypekey key + + struct char_arraytypekey { + char key[128]; //@Key + }; +#pragma keylist char_arraytypekey key + + struct boolean_arraytypekey { + boolean key[128]; //@Key + }; +#pragma keylist boolean_arraytypekey key + + struct octet_arraytypekey { + octet key[128]; //@Key + }; +#pragma keylist octet_arraytypekey key + + struct alltypeskey { + long l; + long long ll; + unsigned short us; + unsigned long ul; + unsigned long long ull; + float f; + double d; + char c; + boolean b; + octet o; + string s; //@Key + }; +#pragma keylist alltypeskey l ll us ul ull f d c b o s +}; diff --git a/src/core/ddsc/tests/basic.c b/src/core/ddsc/tests/basic.c new file mode 100644 index 0000000..cab9810 --- /dev/null +++ b/src/core/ddsc/tests/basic.c @@ -0,0 +1,28 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include +#include + +Test(ddsc_basic, test) +{ + dds_entity_t participant; + dds_return_t status; + + participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0); + + /* TODO: CHAM-108: Add some simple read/write test(s). */ + + status = dds_delete(participant); + cr_assert_eq(status, DDS_RETCODE_OK); +} diff --git a/src/core/ddsc/tests/builtin_topics.c b/src/core/ddsc/tests/builtin_topics.c new file mode 100755 index 0000000..54b560b --- /dev/null +++ b/src/core/ddsc/tests/builtin_topics.c @@ -0,0 +1,842 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "RoundTrip.h" +#include "Space.h" +#include "ddsc/dds.h" +#include "os/os.h" +#include "test-common.h" +#include +#include + +static dds_entity_t g_participant = 0; +static dds_entity_t g_subscriber = 0; +static dds_entity_t g_publisher = 0; +static dds_entity_t g_topic = 0; + +#define MAX_SAMPLES 1 + +static dds_sample_info_t g_info[MAX_SAMPLES]; + +static struct DDS_UserDataQosPolicy g_pol_userdata; +static struct DDS_TopicDataQosPolicy g_pol_topicdata; +static struct DDS_GroupDataQosPolicy g_pol_groupdata; +static struct DDS_DurabilityQosPolicy g_pol_durability; +static struct DDS_HistoryQosPolicy g_pol_history; +static struct DDS_ResourceLimitsQosPolicy g_pol_resource_limits; +static struct DDS_PresentationQosPolicy g_pol_presentation; +static struct DDS_LifespanQosPolicy g_pol_lifespan; +static struct DDS_DeadlineQosPolicy g_pol_deadline; +static struct DDS_LatencyBudgetQosPolicy g_pol_latency_budget; +static struct DDS_OwnershipQosPolicy g_pol_ownership; +static struct DDS_OwnershipStrengthQosPolicy g_pol_ownership_strength; +static struct DDS_LivelinessQosPolicy g_pol_liveliness; +static struct DDS_TimeBasedFilterQosPolicy g_pol_time_based_filter; +static struct DDS_PartitionQosPolicy g_pol_partition; +static struct DDS_ReliabilityQosPolicy g_pol_reliability; +static struct DDS_TransportPriorityQosPolicy g_pol_transport_priority; +static struct DDS_DestinationOrderQosPolicy g_pol_destination_order; +static struct DDS_WriterDataLifecycleQosPolicy g_pol_writer_data_lifecycle; +static struct DDS_ReaderDataLifecycleQosPolicy g_pol_reader_data_lifecycle; +static struct DDS_DurabilityServiceQosPolicy g_pol_durability_service; + +static const char* c_userdata = "user_key"; +static const char* c_topicdata = "topic_key"; +static const char* c_groupdata = "group_key"; +static const char* c_partitions[] = {"Partition1", "Partition2"}; + +static dds_qos_t *g_qos = NULL; + +static void +qos_init(void) +{ + g_qos = dds_qos_create(); + cr_assert_not_null(g_qos); + + g_pol_userdata.value._buffer = dds_alloc(strlen(c_userdata) + 1); + g_pol_userdata.value._length = (uint32_t)strlen(c_userdata) + 1; + g_pol_userdata.value._release = true; + g_pol_userdata.value._maximum = 0; + + g_pol_topicdata.value._buffer = dds_alloc(strlen(c_topicdata) + 1); + g_pol_topicdata.value._length = (uint32_t)strlen(c_topicdata) + 1; + g_pol_topicdata.value._release = true; + g_pol_topicdata.value._maximum = 0; + + g_pol_groupdata.value._buffer = dds_alloc(strlen(c_groupdata) + 1); + g_pol_groupdata.value._length = (uint32_t)strlen(c_groupdata) + 1; + g_pol_groupdata.value._release = true; + g_pol_groupdata.value._maximum = 0; + + g_pol_history.kind = DDS_KEEP_LAST_HISTORY_QOS; + g_pol_history.depth = 1; + + g_pol_resource_limits.max_samples = 1; + g_pol_resource_limits.max_instances = 1; + g_pol_resource_limits.max_samples_per_instance = 1; + + g_pol_presentation.access_scope = DDS_INSTANCE_PRESENTATION_QOS; + g_pol_presentation.coherent_access = true; + g_pol_presentation.ordered_access = true; + + g_pol_lifespan.duration.sec = 10000; + g_pol_lifespan.duration.nanosec = 11000; + + g_pol_deadline.period.sec = 20000; + g_pol_deadline.period.nanosec = 220000; + + g_pol_latency_budget.duration.sec = 30000; + g_pol_latency_budget.duration.nanosec = 33000; + + g_pol_ownership.kind = DDS_EXCLUSIVE_OWNERSHIP_QOS; + + g_pol_ownership_strength.value = 10; + + g_pol_liveliness.kind = DDS_AUTOMATIC_LIVELINESS_QOS; + g_pol_liveliness.lease_duration.sec = 40000; + g_pol_liveliness.lease_duration.nanosec = 44000; + + g_pol_time_based_filter.minimum_separation.sec = 50000; + g_pol_time_based_filter.minimum_separation.nanosec = 55000; + + g_pol_partition.name._buffer = (char**)c_partitions; + g_pol_partition.name._length = 2; + + g_pol_reliability.kind = DDS_RELIABLE_RELIABILITY_QOS; + g_pol_reliability.max_blocking_time.sec = 60000; + g_pol_reliability.max_blocking_time.nanosec = 66000; + + g_pol_transport_priority.value = 42; + + g_pol_destination_order.kind = DDS_BY_SOURCE_TIMESTAMP_DESTINATIONORDER_QOS; + + g_pol_writer_data_lifecycle.autodispose_unregistered_instances = true; + + g_pol_reader_data_lifecycle.autopurge_disposed_samples_delay.sec = 70000; + g_pol_reader_data_lifecycle.autopurge_disposed_samples_delay.nanosec= 77000; + g_pol_reader_data_lifecycle.autopurge_nowriter_samples_delay.sec = 80000; + g_pol_reader_data_lifecycle.autopurge_nowriter_samples_delay.nanosec = 88000; + + g_pol_durability_service.history_depth = 1; + g_pol_durability_service.history_kind = DDS_KEEP_LAST_HISTORY_QOS; + g_pol_durability_service.max_samples = 2; + g_pol_durability_service.max_instances = 3; + g_pol_durability_service.max_samples_per_instance = 4; + g_pol_durability_service.service_cleanup_delay.sec = 90000; + g_pol_durability_service.service_cleanup_delay.nanosec = 99000; +} + +static void +qos_fini(void) +{ + dds_qos_delete(g_qos); + dds_free(g_pol_userdata.value._buffer); + dds_free(g_pol_groupdata.value._buffer); + dds_free(g_pol_topicdata.value._buffer); +} + +static void +setup(void) +{ + qos_init(); + + g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_participant, 0, "Failed to create prerequisite g_participant"); + g_topic = dds_create_topic(g_participant, &RoundTripModule_DataType_desc, "RoundTrip", NULL, NULL); + cr_assert_gt(g_topic, 0, "Failed to create prerequisite g_topic"); + g_subscriber = dds_create_subscriber(g_participant, NULL, NULL); + cr_assert_gt(g_subscriber, 0, "Failed to create prerequisite g_subscriber"); + g_publisher = dds_create_publisher(g_participant, NULL, NULL); + cr_assert_gt(g_publisher, 0, "Failed to create prerequisite g_publisher"); +} + +static void +teardown(void) +{ + qos_fini(); + dds_delete(g_participant); +} +#define T_MILLISECOND 1000000ll +#define T_SECOND (1000 * T_MILLISECOND) + +int64_t from_ddsi_duration (DDS_Duration_t x) +{ + int64_t t; + t = x.sec * 10^9 + x.nanosec; + return t; +} + +static void +check_default_qos_of_builtin_entity(dds_entity_t entity) +{ + dds_return_t ret; + int64_t deadline; + int64_t liveliness_lease_duration; + int64_t minimum_separation; + int64_t max_blocking_time; + int64_t autopurge_nowriter_samples_delay; + int64_t autopurge_disposed_samples_delay; + + dds_durability_kind_t durability_kind; + dds_presentation_access_scope_kind_t presentation_access_scope_kind; + dds_ownership_kind_t ownership_kind; + dds_liveliness_kind_t liveliness_kind; + dds_reliability_kind_t reliability_kind; + dds_destination_order_kind_t destination_order_kind; + dds_history_kind_t history_kind; + + char **partitions; + uint32_t plen; + + dds_qos_t *qos = dds_qos_create(); + cr_assert_not_null(qos); + + ret = dds_get_qos(entity, qos); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to get QOS of builtin entity"); + + dds_qget_durability(qos, &durability_kind); + dds_qget_presentation(qos, &presentation_access_scope_kind, &g_pol_presentation.coherent_access, &g_pol_presentation.ordered_access); + dds_qget_deadline(qos, &deadline); + dds_qget_ownership(qos, &ownership_kind); + dds_qget_liveliness(qos, &liveliness_kind, &liveliness_lease_duration); + dds_qget_time_based_filter(qos, &minimum_separation); + dds_qget_reliability(qos, &reliability_kind, &max_blocking_time); + dds_qget_destination_order(qos, &destination_order_kind); + dds_qget_history(qos, &history_kind, &g_pol_history.depth); + dds_qget_resource_limits(qos, &g_pol_resource_limits.max_samples, &g_pol_resource_limits.max_instances, &g_pol_resource_limits.max_samples_per_instance); + dds_qget_reader_data_lifecycle(qos, &autopurge_nowriter_samples_delay, &autopurge_disposed_samples_delay); + dds_qget_partition(qos, &plen, &partitions); + // no getter for ENTITY_FACTORY + + if ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_SUBSCRIBER) { + cr_expect_eq(plen, 1); + if (plen > 0) { + cr_expect_str_eq(partitions[0], "__BUILT-IN PARTITION__"); + } + } else if ((entity & DDS_ENTITY_KIND_MASK) == DDS_KIND_READER) { + cr_expect_eq(durability_kind, DDS_DURABILITY_TRANSIENT_LOCAL); + cr_expect_eq(presentation_access_scope_kind, DDS_PRESENTATION_TOPIC); + cr_expect_eq(g_pol_presentation.coherent_access, false); + cr_expect_eq(g_pol_presentation.ordered_access, false); + cr_expect_eq(deadline, DDS_INFINITY); + cr_expect_eq(ownership_kind, DDS_OWNERSHIP_SHARED); + cr_expect_eq(liveliness_kind, DDS_LIVELINESS_AUTOMATIC); + cr_expect_eq(minimum_separation, 0); + cr_expect_eq(reliability_kind, DDS_RELIABILITY_RELIABLE); + cr_expect_eq(max_blocking_time, DDS_MSECS(100)); + cr_expect_eq(destination_order_kind, DDS_DESTINATIONORDER_BY_RECEPTION_TIMESTAMP); + cr_expect_eq(history_kind, DDS_HISTORY_KEEP_LAST); + cr_expect_eq(g_pol_history.depth, 1); + cr_expect_eq(g_pol_resource_limits.max_instances, DDS_LENGTH_UNLIMITED); + cr_expect_eq(g_pol_resource_limits.max_samples, DDS_LENGTH_UNLIMITED); + cr_expect_eq(g_pol_resource_limits.max_samples_per_instance, DDS_LENGTH_UNLIMITED); + cr_expect_eq(autopurge_nowriter_samples_delay, DDS_INFINITY); + cr_expect_eq(autopurge_disposed_samples_delay, DDS_INFINITY); + } else { + cr_assert_fail("Unsupported entity kind %s", entity_kind_str(entity)); + } + if (plen > 0) { + for (uint32_t i = 0; i < plen; i++) { + dds_free(partitions[i]); + } + dds_free(partitions); + } + dds_qos_delete(qos); +} + +static dds_entity_t builtin_topic_handles[10]; + +Test(ddsc_builtin_topics, types_allocation) +{ +#define TEST_ALLOC(type) do { \ + DDS_##type##BuiltinTopicData *data = DDS_##type##BuiltinTopicData__alloc(); \ + cr_expect_not_null(data, "Failed to allocate DDS_" #type "BuiltinTopicData"); \ + DDS_##type##BuiltinTopicData_free(data, DDS_FREE_ALL); \ + } while(0) + + TEST_ALLOC(Participant); + TEST_ALLOC(CMParticipant); + TEST_ALLOC(Type); + TEST_ALLOC(Topic); + TEST_ALLOC(Publication); + TEST_ALLOC(CMPublisher); + TEST_ALLOC(Subscription); + TEST_ALLOC(CMSubscriber); + TEST_ALLOC(CMDataWriter); + TEST_ALLOC(CMDataReader); +#undef TEST_ALLOC +} + +Test(ddsc_builtin_topics, availability_builtin_topics, .init = setup, .fini = teardown) +{ + dds_entity_t topic; + + topic = dds_find_topic(g_participant, "DCPSParticipant"); + cr_assert_gt(topic, 0); + dds_delete(topic); + topic = dds_find_topic(g_participant, "DCPSTopic"); + cr_assert_lt(topic, 0); + //TODO CHAM-347: dds_delete(topic); + topic = dds_find_topic(g_participant, "DCPSType"); + cr_assert_lt(topic, 0); + //TODO CHAM-347: dds_delete(topic); + topic = dds_find_topic(g_participant, "DCPSSubscription"); + cr_assert_lt(topic, 0); + //TODO CHAM-347: dds_delete(topic); + topic = dds_find_topic(g_participant, "DCSPPublication"); + cr_assert_lt(topic, 0); + //TODO CHAM-347: dds_delete(topic); +} + +Test(ddsc_builtin_topics, read_publication_data, .init = setup, .fini = teardown) +{ + dds_entity_t reader; +#if 0 /* disabled pending CHAM-347 */ + dds_return_t ret; + DDS_PublicationBuiltinTopicData *data; +#endif + void *samples[MAX_SAMPLES]; + + + reader = dds_create_reader(g_participant, DDS_BUILTIN_TOPIC_DCPSPUBLICATION, NULL, NULL); + cr_assert_gt(reader, 0, "Failed to create a data reader for DDS_BUILTIN_TOPIC_DCPSPUBLICATION."); + + samples[0] = DDS_PublicationBuiltinTopicData__alloc(); +#if 0 /* disabled pending CHAM-347 */ + ret = dds_read(reader, samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_gt(ret, 0, "Failed to read samples DCPSPublication"); + + data = (DDS_PublicationBuiltinTopicData *)samples; + cr_assert_str_eq(data->topic_name, "DCPSPublication"); +#endif + + DDS_PublicationBuiltinTopicData_free(samples[0], DDS_FREE_ALL); +} + +Test(ddsc_builtin_topics, create_reader) +{ + dds_entity_t participant; + dds_entity_t t1; + + /* Create a participant */ + participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "dds_participant_create"); + + /* + * The topics are created by the middleware as soon as a participant + * is created. + */ +#define TEST_FIND(p, t) do { \ + t1 = dds_find_topic(p, t); \ + cr_expect_gt(t1, 0, "dds_find_topic(\"" t "\") returned a valid handle"); \ + dds_delete(t1); \ + } while(0); + + /* A builtin-topic proxy is created 'on demand' and should not exist before a reader is created for it */ + TEST_FIND(participant, "DCPSParticipant"); + TEST_FIND(participant, "CMParticipant"); +#undef TEST_FIND + + /* + * TODO CHAM-347: Not all builtin topics are created at the start. + */ +#define TEST_NOTFOUND(p, t) do { \ + t1 = dds_find_topic(p, t); \ + cr_expect_lt(t1, 0, "dds_find_topic(\"" t "\") returned a valid handle"); \ + } while(0); + + /* A builtin-topic proxy is created 'on demand' and should not exist before a reader is created for it */ + TEST_NOTFOUND(participant, "DCPSType"); + TEST_NOTFOUND(participant, "DCPSTopic"); + TEST_NOTFOUND(participant, "DCPSPublication"); + TEST_NOTFOUND(participant, "CMPublisher"); + TEST_NOTFOUND(participant, "DCPSSubscription"); + TEST_NOTFOUND(participant, "CMSubscriber"); + TEST_NOTFOUND(participant, "CMDataWriter"); + TEST_NOTFOUND(participant, "CMDataReader"); +#undef TEST_NOTFOUND + + /* A reader is created by providing a special builtin-topic handle */ + { + dds_entity_t readers[10]; + dds_entity_t builtin_subscriber, s; + + builtin_topic_handles[0] = DDS_BUILTIN_TOPIC_DCPSPARTICIPANT; + builtin_topic_handles[1] = DDS_BUILTIN_TOPIC_CMPARTICIPANT; + builtin_topic_handles[2] = DDS_BUILTIN_TOPIC_DCPSTYPE; + builtin_topic_handles[3] = DDS_BUILTIN_TOPIC_DCPSTOPIC; + builtin_topic_handles[4] = DDS_BUILTIN_TOPIC_DCPSPUBLICATION; + builtin_topic_handles[5] = DDS_BUILTIN_TOPIC_CMPUBLISHER; + builtin_topic_handles[6] = DDS_BUILTIN_TOPIC_DCPSSUBSCRIPTION; + builtin_topic_handles[7] = DDS_BUILTIN_TOPIC_CMSUBSCRIBER; + builtin_topic_handles[8] = DDS_BUILTIN_TOPIC_CMDATAWRITER; + builtin_topic_handles[9] = DDS_BUILTIN_TOPIC_CMDATAREADER; + + + for (int i = 0; i < 10; i++) { + readers[i] = dds_create_reader(participant, builtin_topic_handles[i], NULL, NULL); + cr_expect_gt(readers[i], 0, "Failed to created reader for builtin topic handle %d", builtin_topic_handles[i]); + + if (i == 0) { + /* Check the parent of reader is a subscriber */ + builtin_subscriber = dds_get_parent(readers[i]); + cr_assert_gt(builtin_subscriber, 0, "Failed to get parent of first builtin-reader (%s)", dds_err_str(builtin_subscriber)); + cr_assert_eq(builtin_subscriber & DDS_ENTITY_KIND_MASK, DDS_KIND_SUBSCRIBER, "Parent is not a subscriber"); + } else { + /* Check the parent of reader equals parent of first reader */ + s = dds_get_parent(readers[i]); + cr_assert_gt(s, 0, "Failed to get parent of builtin-reader (%s)", dds_err_str(s)); + cr_assert_eq(s, builtin_subscriber, "Parent subscriber of reader(%d) doesn't equal builtin-subscriber", i); + //dds_delete(s); + } + } + } + +#define TEST_FOUND(p, t) do { \ + t1 = dds_find_topic(p, t); \ + cr_expect_gt(t1, 0, "dds_find_topic(\"" t "\") returned an invalid handle (%s)", dds_err_str(t1)); \ + if (t1 > 0) { \ + dds_delete(t1); \ + } \ + } while(0); + + /* Builtin-topics proxies should now be created */ + TEST_FOUND(participant, "DCPSParticipant"); + TEST_FOUND(participant, "CMParticipant"); + TEST_FOUND(participant, "DCPSType"); + TEST_FOUND(participant, "DCPSTopic"); + TEST_FOUND(participant, "DCPSPublication"); + TEST_FOUND(participant, "CMPublisher"); + TEST_FOUND(participant, "DCPSSubscription"); + TEST_FOUND(participant, "CMSubscriber"); + TEST_FOUND(participant, "CMDataWriter"); + TEST_FOUND(participant, "CMDataReader"); +#undef TEST_FOUND + + dds_delete(participant); +} + +Test(ddsc_builtin_topics, read_subscription_data, .init = setup, .fini = teardown) +{ + dds_entity_t reader; +#if 0 /* not supported yet */ + dds_return_t ret; + DDS_SubscriptionBuiltinTopicData *data; +#endif + void * samples[MAX_SAMPLES]; + + reader = dds_create_reader(g_participant, DDS_BUILTIN_TOPIC_DCPSSUBSCRIPTION, NULL, NULL); + cr_assert_gt(reader, 0, "Failed to create a data reader for DDS_BUILTIN_TOPIC_DCPSSUBSCRIPTION."); + + samples[0] = DDS_SubscriptionBuiltinTopicData__alloc(); + +#if 0 /* not supported yet */ + ret = dds_read(reader, samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_gt(ret, 0, "Failed to read samples DCPSSubscription"); + + data = (DDS_SubscriptionBuiltinTopicData *)samples; + cr_assert_str_eq(data->topic_name, "DCPSSubscription"); +#endif + + DDS_SubscriptionBuiltinTopicData_free(samples[0], DDS_FREE_ALL); +} + +Test(ddsc_builtin_topics, read_participant_data, .init = setup, .fini = teardown) +{ + dds_entity_t reader; + dds_return_t ret; + void * samples[MAX_SAMPLES]; + + reader = dds_create_reader(g_participant, DDS_BUILTIN_TOPIC_DCPSPARTICIPANT, NULL, NULL); + cr_assert_gt(reader, 0, "Failed to create a data reader for DDS_BUILTIN_TOPIC_DCPSPARTICIPANT."); + + samples[0] = DDS_ParticipantBuiltinTopicData__alloc(); + + ret = dds_read(reader, samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_gt(ret, 0, "Failed to read samples DCPSParticipant"); + + { + DDS_ParticipantBuiltinTopicData *data = (DDS_ParticipantBuiltinTopicData*)samples[0]; + cr_log_info("Participant.key: %x.%x.%x\n", data->key[0], data->key[1], data->key[2]); + cr_log_info("Participant.userdata: %s\n", data->user_data.value._buffer); + } + + DDS_ParticipantBuiltinTopicData_free(samples[0], DDS_FREE_ALL); +} + +Test(ddsc_builtin_topics, read_cmparticipant_data, .init = setup, .fini = teardown) +{ + dds_entity_t reader; + dds_return_t ret; + void * samples[MAX_SAMPLES]; + + reader = dds_create_reader(g_participant, DDS_BUILTIN_TOPIC_CMPARTICIPANT, NULL, NULL); + cr_assert_gt(reader, 0, "Failed to create a data reader for DDS_BUILTIN_TOPIC_DCPSPARTICIPANT."); + + samples[0] = DDS_CMParticipantBuiltinTopicData__alloc(); + + ret = dds_read(reader, samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_gt(ret, 0, "Failed to read samples CMParticipant"); + + { + DDS_CMParticipantBuiltinTopicData *data = (DDS_CMParticipantBuiltinTopicData*)samples[0]; + cr_log_info("CMParticipant.key: %x.%x.%x\n", data->key[0], data->key[1], data->key[2]); + cr_log_info("CMParticipant.product: %s\n", data->product.value); + } + + DDS_CMParticipantBuiltinTopicData_free(samples[0], DDS_FREE_ALL); +} + +Test(ddsc_builtin_topics, read_topic_data, .init = setup, .fini = teardown) +{ + dds_entity_t reader; +#if 0 /* disabled pending CHAM-347 */ + dds_return_t ret; + DDS_TopicBuiltinTopicData *data; +#endif + void * samples[MAX_SAMPLES]; + + + reader = dds_create_reader(g_participant, DDS_BUILTIN_TOPIC_DCPSTOPIC, NULL, NULL); + cr_assert_gt(reader, 0, "Failed to create a data reader for DDS_BUILTIN_TOPIC_DCPSTOPIC."); + + samples[0] = DDS_TopicBuiltinTopicData__alloc(); +#if 0 /* disabled pending CHAM-347 */ + ret = dds_read(reader, samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_gt(ret, 0, "Failed to read samples DCPSTopic"); + + data = (DDS_TopicBuiltinTopicData *)samples; + cr_assert_str_eq(data->name, "DCPSSubscription"); +#endif + DDS_ParticipantBuiltinTopicData_free(samples[0], DDS_FREE_ALL); +} + +Test(ddsc_builtin_topics, read_type_data, .init = setup, .fini = teardown) +{ + dds_entity_t reader; +#if 0 /* disabled pending CHAM-347 */ + dds_return_t ret; + DDS_TypeBuiltinTopicData *data; +#endif + void * samples[MAX_SAMPLES]; + + reader = dds_create_reader(g_participant, DDS_BUILTIN_TOPIC_DCPSTYPE, NULL, NULL); + cr_assert_gt(reader, 0, "Failed to create a data reader for DDS_BUILTIN_TOPIC_DCPSTYPE."); + + samples[0] = DDS_TypeBuiltinTopicData__alloc(); +#if 0 /* disabled pending CHAM-347 */ + ret = dds_read(reader, samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_gt(ret, 0, "Failed to read samples DCPSType"); + + data = (DDS_TypeBuiltinTopicData *)samples; + cr_assert_str_eq(data->name, "DCPSType"); +#endif + DDS_TypeBuiltinTopicData_free(samples[0], DDS_FREE_ALL); +} + +Test(ddsc_builtin_topics, same_subscriber, .init = setup, .fini = teardown) +{ + dds_entity_t subscription_rdr; + dds_entity_t subscription_subscriber; + + dds_entity_t publication_rdr; + dds_entity_t publication_subscriber; + + dds_entity_t participant_rdr; + dds_entity_t participant_subscriber; + + dds_entity_t topic_rdr; + dds_entity_t topic_subscriber; + + dds_entity_t type_rdr; + dds_entity_t type_subscriber; + + subscription_rdr = dds_create_reader(g_participant, DDS_BUILTIN_TOPIC_DCPSSUBSCRIPTION, NULL, NULL); + cr_assert_gt(subscription_rdr, 0, "Failed to create a data reader for DDS_BUILTIN_TOPIC_DCPSSUBSCRIPTION."); + subscription_subscriber = dds_get_parent(subscription_rdr); + cr_assert_gt(subscription_subscriber, 0, "Could not find builtin subscriber for DSCPSSubscription-reader."); + + publication_rdr = dds_create_reader(g_participant, DDS_BUILTIN_TOPIC_DCPSPUBLICATION, NULL, NULL); + cr_assert_gt(publication_rdr, 0, "Failed to create a data reader for DDS_BUILTIN_TOPIC_DCPSPUBLICATION."); + publication_subscriber = dds_get_parent(publication_rdr); + cr_assert_gt(publication_subscriber, 0, "Could not find builtin subscriber for DSCPSPublication-reader."); + + cr_assert_eq(subscription_subscriber, publication_subscriber); + + participant_rdr = dds_create_reader(g_participant, DDS_BUILTIN_TOPIC_DCPSPARTICIPANT, NULL, NULL); + cr_assert_gt(participant_rdr, 0, "Failed to create a data reader for DDS_BUILTIN_TOPIC_DCPSPARTICIPANT."); + participant_subscriber = dds_get_parent(participant_rdr); + cr_assert_gt(participant_subscriber, 0, "Could not find builtin subscriber for DSCPSParticipant-reader."); + + cr_assert_eq(publication_subscriber, participant_subscriber); + + topic_rdr = dds_create_reader(g_participant, DDS_BUILTIN_TOPIC_DCPSTOPIC, NULL, NULL); + cr_assert_gt(topic_rdr, 0, "Failed to create a data reader for DDS_BUILTIN_TOPIC_DCPSTOPIC."); + topic_subscriber = dds_get_parent(topic_rdr); + cr_assert_gt(topic_subscriber, 0, "Could not find builtin subscriber for DSCPSTopic-reader."); + + cr_assert_eq(participant_subscriber, topic_subscriber); + + type_rdr = dds_create_reader(g_participant, DDS_BUILTIN_TOPIC_DCPSTYPE, NULL, NULL); + cr_assert_gt(type_rdr, 0, "Failed to create a data reader for DDS_BUILTIN_TOPIC_DCPSTYPE."); + type_subscriber = dds_get_parent(type_rdr); + cr_assert_gt(type_subscriber, 0, "Could not find builtin subscriber for DSCPSType-reader."); + + cr_assert_eq(topic_subscriber, type_subscriber); +} + +Test(ddsc_builtin_topics, builtin_qos, .init = setup, .fini = teardown) +{ + dds_entity_t dds_sub_rdr; + dds_entity_t dds_sub_subscriber; + + dds_sub_rdr = dds_create_reader(g_participant, DDS_BUILTIN_TOPIC_DCPSSUBSCRIPTION, NULL, NULL); + cr_assert_gt(dds_sub_rdr, 0, "Failed to create a data reader for DDS_BUILTIN_TOPIC_DCPSSUBSCRIPTION."); + check_default_qos_of_builtin_entity(dds_sub_rdr); + + dds_sub_subscriber = dds_get_parent(dds_sub_rdr); + cr_assert_gt(dds_sub_subscriber, 0, "Could not find builtin subscriber for DSCPSSubscription-reader."); + check_default_qos_of_builtin_entity(dds_sub_subscriber); +} + +Test(ddsc_builtin_topics, datareader_qos, .init = setup, .fini = teardown) +{ + dds_entity_t rdr; + dds_entity_t subscription_rdr; + void * subscription_samples[MAX_SAMPLES]; +#if 0 /* disabled pending CHAM-347 */ + dds_return_t ret; + DDS_SubscriptionBuiltinTopicData *subscription_data; +#endif + + // Set some qos' which differ from the default + dds_qset_durability(g_qos, (dds_durability_kind_t)g_pol_durability.kind); + dds_qset_deadline(g_qos, from_ddsi_duration(g_pol_deadline.period)); + dds_qset_latency_budget(g_qos, from_ddsi_duration(g_pol_latency_budget.duration)); + dds_qset_liveliness(g_qos, (dds_liveliness_kind_t)g_pol_liveliness.kind, from_ddsi_duration(g_pol_liveliness.lease_duration)); + dds_qset_reliability(g_qos, (dds_reliability_kind_t)g_pol_reliability.kind, from_ddsi_duration(g_pol_reliability.max_blocking_time)); + dds_qset_ownership(g_qos, (dds_ownership_kind_t)g_pol_ownership.kind); + dds_qset_destination_order(g_qos, (dds_destination_order_kind_t)g_pol_destination_order.kind); + dds_qset_userdata(g_qos, g_pol_userdata.value._buffer, g_pol_userdata.value._length); + dds_qset_time_based_filter(g_qos, from_ddsi_duration(g_pol_time_based_filter.minimum_separation)); + dds_qset_presentation(g_qos, g_pol_presentation.access_scope, g_pol_presentation.coherent_access, g_pol_presentation.ordered_access); + dds_qset_partition(g_qos, g_pol_partition.name._length, c_partitions); + dds_qset_topicdata(g_qos, g_pol_topicdata.value._buffer, g_pol_topicdata.value._length); + dds_qset_groupdata(g_qos, g_pol_groupdata.value._buffer, g_pol_groupdata.value._length); + + rdr = dds_create_reader(g_subscriber, g_topic, g_qos, NULL); + + subscription_samples[0] = DDS_SubscriptionBuiltinTopicData__alloc(); + + subscription_rdr = dds_create_reader(g_participant, DDS_BUILTIN_TOPIC_DCPSSUBSCRIPTION, NULL, NULL); + cr_assert_gt(subscription_rdr, 0, "Failed to retrieve built-in datareader for DCPSSubscription"); +#if 0 /* disabled pending CHAM-347 */ + ret = dds_read(subscription_rdr, subscription_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_gt(ret, 0, "Failed to read Subscription data"); + + // Check the QOS settings of the 'remote' qos' + subscription_data = (DDS_SubscriptionBuiltinTopicData *)subscription_samples[0]; + + cr_assert_str_eq(subscription_data->topic_name, "RoundTrip"); + cr_assert_str_eq(subscription_data->type_name, "RoundTripModule::DataType"); + cr_assert_eq(subscription_data->durability.kind, g_pol_durability.kind); + cr_assert_eq(subscription_data->deadline.period.sec, g_pol_deadline.period.sec); + cr_assert_eq(subscription_data->deadline.period.nanosec, g_pol_deadline.period.nanosec); + cr_assert_eq(subscription_data->latency_budget.duration.sec, g_pol_latency_budget.duration.sec); + cr_assert_eq(subscription_data->latency_budget.duration.nanosec, g_pol_latency_budget.duration.nanosec); + cr_assert_eq(subscription_data->liveliness.kind, g_pol_liveliness.kind); + cr_assert_eq(subscription_data->liveliness.lease_duration.sec, g_pol_liveliness.lease_duration.sec); + cr_assert_eq(subscription_data->liveliness.lease_duration.nanosec, g_pol_liveliness.lease_duration.nanosec); + cr_assert_eq(subscription_data->reliability.kind, g_pol_reliability.kind); + cr_assert_eq(subscription_data->reliability.max_blocking_time.sec, g_pol_reliability.max_blocking_time.sec); + cr_assert_eq(subscription_data->reliability.max_blocking_time.nanosec, g_pol_reliability.max_blocking_time.nanosec); + cr_assert_eq(subscription_data->ownership.kind, g_pol_ownership.kind); + cr_assert_eq(subscription_data->destination_order.kind, g_pol_destination_order.kind); + cr_assert_eq(subscription_data->user_data.value._buffer, g_pol_userdata.value._buffer); + cr_assert_eq(subscription_data->user_data.value._length, g_pol_userdata.value._length); + cr_assert_eq(subscription_data->time_based_filter.minimum_separation.sec, g_pol_time_based_filter.minimum_separation.sec); + cr_assert_eq(subscription_data->time_based_filter.minimum_separation.nanosec, g_pol_time_based_filter.minimum_separation.nanosec); + cr_assert_eq(subscription_data->presentation.access_scope, g_pol_presentation.access_scope); + cr_assert_eq(subscription_data->presentation.coherent_access, g_pol_presentation.coherent_access); + cr_assert_eq(subscription_data->presentation.ordered_access, g_pol_presentation.ordered_access); + + cr_assert_eq(subscription_data->partition.name._length, g_pol_partition.name._length); + for (uint32_t i = 0; i < subscription_data->partition.name._length; ++i) + { + cr_assert_str_eq(subscription_data->partition.name._buffer[i], c_partitions[i]); + } + + cr_assert_str_eq(subscription_data->topic_data.value._buffer, g_pol_topicdata.value._buffer); + cr_assert_eq(subscription_data->topic_data.value._length, g_pol_topicdata.value._length); + cr_assert_str_eq(subscription_data->group_data.value._buffer, g_pol_groupdata.value._buffer); + cr_assert_eq(subscription_data->group_data.value._length, g_pol_groupdata.value._length); +#endif + DDS_SubscriptionBuiltinTopicData_free(subscription_samples[0], DDS_FREE_ALL); +} + +Test(ddsc_builtin_topics, datawriter_qos, .init = setup, .fini = teardown) +{ + dds_entity_t wrtr; + dds_entity_t publication_rdr; +#if 0 /* disabled pending CHAM-347 */ + dds_return_t ret; + DDS_PublicationBuiltinTopicData *publication_data; +#endif + void * publication_samples[MAX_SAMPLES]; + + + dds_qset_durability(g_qos, g_pol_durability.kind); + dds_qset_deadline(g_qos, from_ddsi_duration(g_pol_deadline.period)); + dds_qset_latency_budget(g_qos, from_ddsi_duration(g_pol_latency_budget.duration)); + dds_qset_liveliness(g_qos, (dds_liveliness_kind_t)g_pol_liveliness.kind, from_ddsi_duration(g_pol_liveliness.lease_duration)); + dds_qset_reliability(g_qos, (dds_reliability_kind_t)g_pol_reliability.kind, from_ddsi_duration(g_pol_reliability.max_blocking_time)); + dds_qset_lifespan(g_qos, from_ddsi_duration(g_pol_lifespan.duration)); + dds_qset_destination_order(g_qos, (dds_destination_order_kind_t)g_pol_destination_order.kind); + dds_qset_userdata(g_qos, g_pol_userdata.value._buffer, g_pol_userdata.value._length); + dds_qset_ownership(g_qos, (dds_ownership_kind_t)g_pol_ownership.kind); + dds_qset_ownership_strength(g_qos, g_pol_ownership_strength.value); + dds_qset_presentation(g_qos, g_pol_presentation.access_scope, g_pol_presentation.coherent_access, g_pol_presentation.ordered_access); + dds_qset_partition(g_qos, g_pol_partition.name._length, c_partitions); + dds_qset_topicdata(g_qos, g_pol_topicdata.value._buffer, g_pol_topicdata.value._length); + + wrtr = dds_create_writer(g_publisher, g_topic, g_qos, NULL); + + publication_samples[0] = DDS_PublicationBuiltinTopicData__alloc(); + + publication_rdr = dds_create_reader(g_participant, DDS_BUILTIN_TOPIC_DCPSPUBLICATION, NULL, NULL); + cr_assert_gt(publication_rdr, 0, "Failed to retrieve built-in datareader for DCPSPublication"); + +#if 0 /* disabled pending CHAM-347 */ + ret = dds_read(publication_rdr, publication_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_gt(ret, 0, "Failed to read Publication data"); + + // Check the QOS settings of the 'remote' qos' + publication_data = (DDS_PublicationBuiltinTopicData *)publication_samples[0]; + + cr_assert_str_eq(publication_data->topic_name, "RoundTrip"); + cr_assert_str_eq(publication_data->type_name, "RoundTripModule::DataType"); + cr_assert_eq(publication_data->durability.kind, g_pol_durability.kind); + cr_assert_eq(publication_data->deadline.period.sec, g_pol_deadline.period.sec); + cr_assert_eq(publication_data->deadline.period.nanosec, g_pol_deadline.period.nanosec); + cr_assert_eq(publication_data->latency_budget.duration.sec, g_pol_latency_budget.duration.sec); + cr_assert_eq(publication_data->latency_budget.duration.nanosec, g_pol_latency_budget.duration.nanosec); + cr_assert_eq(publication_data->liveliness.kind, g_pol_liveliness.kind); + cr_assert_eq(publication_data->liveliness.lease_duration.sec, g_pol_liveliness.lease_duration.sec); + cr_assert_eq(publication_data->liveliness.lease_duration.nanosec, g_pol_liveliness.lease_duration.nanosec); + cr_assert_eq(publication_data->reliability.kind, g_pol_reliability.kind); + cr_assert_eq(publication_data->reliability.max_blocking_time.sec, g_pol_reliability.max_blocking_time.sec); + cr_assert_eq(publication_data->reliability.max_blocking_time.nanosec, g_pol_reliability.max_blocking_time.nanosec); + cr_assert_eq(publication_data->lifespan.duration.sec, g_pol_lifespan.duration.sec); + cr_assert_eq(publication_data->lifespan.duration.nanosec, g_pol_lifespan.duration.nanosec); + cr_assert_eq(publication_data->destination_order.kind, g_pol_destination_order.kind); + cr_assert_eq(publication_data->user_data.value._buffer, g_pol_userdata.value._buffer); + cr_assert_eq(publication_data->user_data.value._length, g_pol_userdata.value._length); + cr_assert_eq(publication_data->ownership.kind, g_pol_ownership.kind); + cr_assert_eq(publication_data->ownership_strength.value, g_pol_ownership_strength.value); + cr_assert_eq(publication_data->presentation.access_scope, g_pol_presentation.access_scope); + cr_assert_eq(publication_data->presentation.coherent_access, g_pol_presentation.coherent_access); + cr_assert_eq(publication_data->presentation.ordered_access, g_pol_presentation.ordered_access); + + cr_assert_eq(publication_data->partition.name._length, g_pol_partition.name._length); + for (uint32_t i = 0; i < publication_data->partition.name._length; ++i) + { + cr_assert_str_eq(publication_data->partition.name._buffer[i], c_partitions[i]); + } + + cr_assert_str_eq(publication_data->topic_data.value._buffer, g_pol_topicdata.value._buffer); + cr_assert_eq(publication_data->topic_data.value._length, g_pol_topicdata.value._length); + cr_assert_str_eq(publication_data->group_data.value._buffer, g_pol_groupdata.value._buffer); + cr_assert_eq(publication_data->group_data.value._length, g_pol_groupdata.value._length); +#endif + DDS_PublicationBuiltinTopicData_free(publication_samples[0], DDS_FREE_ALL); +} + +Test(ddsc_builtin_topics, topic_qos, .init = setup, .fini = teardown) +{ + dds_entity_t tpc; + dds_entity_t topic_rdr; + +#if 0 /* disabled pending CHAM-347 */ + dds_return_t ret; + DDS_TopicBuiltinTopicData *topic_data; +#endif + + void * topic_samples[MAX_SAMPLES]; + + dds_qset_durability(g_qos, g_pol_durability.kind); + dds_qset_durability_service(g_qos, + from_ddsi_duration(g_pol_durability_service.service_cleanup_delay), + (dds_history_kind_t)g_pol_durability_service.history_kind, + g_pol_durability_service.history_depth, + g_pol_durability_service.max_samples, + g_pol_durability_service.max_instances, + g_pol_durability_service.max_samples_per_instance); + dds_qset_deadline(g_qos, from_ddsi_duration(g_pol_deadline.period)); + dds_qset_latency_budget(g_qos, from_ddsi_duration(g_pol_latency_budget.duration)); + dds_qset_liveliness(g_qos, (dds_liveliness_kind_t)g_pol_liveliness.kind, from_ddsi_duration(g_pol_liveliness.lease_duration)); + dds_qset_reliability(g_qos, (dds_reliability_kind_t)g_pol_reliability.kind, from_ddsi_duration(g_pol_reliability.max_blocking_time)); + dds_qset_transport_priority(g_qos, g_pol_transport_priority.value); + dds_qset_lifespan(g_qos, from_ddsi_duration(g_pol_lifespan.duration)); + dds_qset_destination_order(g_qos, (dds_destination_order_kind_t)g_pol_destination_order.kind); + dds_qset_history(g_qos, (dds_history_kind_t)g_pol_history.kind, g_pol_history.depth); + dds_qset_resource_limits(g_qos, g_pol_resource_limits.max_samples, g_pol_resource_limits.max_instances, + g_pol_resource_limits.max_samples_per_instance); + dds_qset_ownership(g_qos, (dds_ownership_kind_t)g_pol_ownership.kind); + dds_qset_topicdata(g_qos, g_pol_topicdata.value._buffer, g_pol_topicdata.value._length); + + + tpc = dds_create_topic(g_participant, &Space_Type1_desc, "SpaceType1", g_qos, NULL); + + topic_samples[0] = DDS_PublicationBuiltinTopicData__alloc(); + + topic_rdr = dds_create_reader(g_participant, DDS_BUILTIN_TOPIC_DCPSTOPIC, NULL, NULL); + cr_assert_gt(topic_rdr, 0, "Failed to retrieve built-in datareader for DCPSPublication"); +#if 0 /* disabled pending CHAM-347 */ + ret = dds_read(topic_rdr, topic_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_gt(ret, 0, "Failed to read Topic data"); + + topic_data = (DDS_TopicBuiltinTopicData *)topic_samples[0]; + + cr_assert_str_eq(topic_data->name, "SpaceType1"); + cr_assert_str_eq(topic_data->type_name, "RoundTripModule::DataType"); + cr_assert_eq(topic_data->durability.kind, g_pol_durability.kind); + cr_assert_eq(topic_data->durability_service.service_cleanup_delay.sec, g_pol_durability_service.service_cleanup_delay.sec); + cr_assert_eq(topic_data->durability_service.service_cleanup_delay.nanosec, g_pol_durability_service.service_cleanup_delay.nanosec); + cr_assert_eq(topic_data->durability_service.history_kind, g_pol_durability_service.history_kind); + cr_assert_eq(topic_data->durability_service.history_depth, g_pol_durability_service.history_depth); + cr_assert_eq(topic_data->durability_service.max_samples, g_pol_durability_service.max_samples); + cr_assert_eq(topic_data->durability_service.max_instances, g_pol_durability_service.max_instances); + cr_assert_eq(topic_data->durability_service.max_samples_per_instance, g_pol_durability_service.max_samples_per_instance); + cr_assert_eq(topic_data->deadline.period.sec, g_pol_deadline.period.sec); + cr_assert_eq(topic_data->deadline.period.nanosec, g_pol_deadline.period.nanosec); + cr_assert_eq(topic_data->latency_budget.duration.sec, g_pol_latency_budget.duration.sec); + cr_assert_eq(topic_data->latency_budget.duration.nanosec, g_pol_latency_budget.duration.nanosec); + cr_assert_eq(topic_data->liveliness.kind, g_pol_liveliness.kind); + cr_assert_eq(topic_data->liveliness.lease_duration.sec, g_pol_liveliness.lease_duration.sec); + cr_assert_eq(topic_data->liveliness.lease_duration.nanosec, g_pol_liveliness.lease_duration.nanosec); + cr_assert_eq(topic_data->reliability.kind, g_pol_reliability.kind); + cr_assert_eq(topic_data->reliability.max_blocking_time.sec, g_pol_reliability.max_blocking_time.sec); + cr_assert_eq(topic_data->reliability.max_blocking_time.nanosec, g_pol_reliability.max_blocking_time.nanosec); + cr_assert_eq(topic_data->transport_priority.value, g_pol_transport_priority.value); + cr_assert_eq(topic_data->lifespan.duration.sec, g_pol_lifespan.duration.sec); + cr_assert_eq(topic_data->lifespan.duration.nanosec, g_pol_lifespan.duration.nanosec); + cr_assert_eq(topic_data->destination_order.kind, g_pol_destination_order.kind); + cr_assert_eq(topic_data->history.kind, g_pol_history.kind); + cr_assert_eq(topic_data->history.depth, g_pol_history.depth); + cr_assert_eq(topic_data->resource_limits.max_samples, g_pol_resource_limits.max_samples); + cr_assert_eq(topic_data->resource_limits.max_instances, g_pol_resource_limits.max_instances); + cr_assert_eq(topic_data->resource_limits.max_samples_per_instance, g_pol_resource_limits.max_samples_per_instance); + cr_assert_eq(topic_data->ownership.kind, g_pol_ownership.kind); + cr_assert_str_eq(topic_data->topic_data.value._buffer, g_pol_topicdata.value._buffer); + cr_assert_eq(topic_data->topic_data.value._length, g_pol_topicdata.value._length); +#endif + DDS_TopicBuiltinTopicData_free(topic_samples[0], DDS_FREE_ALL); +} diff --git a/src/core/ddsc/tests/config.c b/src/core/ddsc/tests/config.c new file mode 100644 index 0000000..30bb2d7 --- /dev/null +++ b/src/core/ddsc/tests/config.c @@ -0,0 +1,79 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include +#include +#include "os/os.h" +#include "config_env.h" +#include "ddsc/ddsc_project.h" + +#define FORCE_ENV + +#define URI_VARIABLE DDSC_PROJECT_NAME_NOSPACE_CAPS"_URI" +#define MAX_PARTICIPANTS_VARIABLE "MAX_PARTICIPANTS" + +static void config__check_env( + _In_z_ const char * env_variable, + _In_z_ const char * expected_value) +{ + const char * env_uri = os_getenv(env_variable); + const char * const env_not_set = "Environment variable '%s' isn't set. This needs to be set to '%s' for this test to run."; + const char * const env_not_as_expected = "Environment variable '%s' has an unexpected value: '%s' (expected: '%s')"; + +#ifdef FORCE_ENV + { + bool env_ok; + + if ( env_uri == NULL ) { + cr_log_info(env_not_set, env_variable, expected_value); + env_ok = false; + } else if ( strncmp(env_uri, expected_value, strlen(expected_value)) != 0 ) { + cr_log_info(env_not_as_expected, env_variable, env_uri, expected_value); + env_ok = false; + } else { + env_ok = true; + } + + if ( !env_ok ) { + os_result r; + char *envstr; + + envstr = os_malloc(strlen(env_variable) + strlen("=") + strlen(expected_value) + 1); + (void) sprintf(envstr, "%s=%s", env_variable, expected_value); + + r = os_putenv(envstr); + cr_assert_eq(r, os_resultSuccess, "Invoking os_putenv(\"%s\") failed", envstr); + cr_log_warn("Environment variable '%s' set to expected value '%s'", env_variable, expected_value); + + os_free(envstr); + } + } +#else + cr_assert_not_null(env_uri, env_not_set, env_variable, expected_value); + cr_assert_str_eq(env_uri, expected_value, env_not_as_expected, env_variable, env_uri, expected_value); +#endif /* FORCE_ENV */ + +} + +Test(ddsc_config, simple_udp, .init = os_osInit, .fini = os_osExit) { + + dds_entity_t participant; + + config__check_env(URI_VARIABLE, CONFIG_ENV_SIMPLE_UDP); + config__check_env(MAX_PARTICIPANTS_VARIABLE, CONFIG_ENV_MAX_PARTICIPANTS); + + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + + cr_assert_gt(participant, 0, "dds_create_participant"); + + dds_delete(participant); +} diff --git a/src/core/ddsc/tests/config_env.h.in b/src/core/ddsc/tests/config_env.h.in new file mode 100644 index 0000000..30a5439 --- /dev/null +++ b/src/core/ddsc/tests/config_env.h.in @@ -0,0 +1,18 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef CONFIG_ENV_H +#define CONFIG_ENV_H + +#define CONFIG_ENV_SIMPLE_UDP "@Criterion_ddsc_config_simple_udp_uri@" +#define CONFIG_ENV_MAX_PARTICIPANTS "@Criterion_ddsc_config_simple_udp_max_participants@" + +#endif /* CONFIG_ENV_H */ diff --git a/src/core/ddsc/tests/config_simple_udp.xml b/src/core/ddsc/tests/config_simple_udp.xml new file mode 100644 index 0000000..ca79023 --- /dev/null +++ b/src/core/ddsc/tests/config_simple_udp.xml @@ -0,0 +1,42 @@ + + + + + + 3 + + + + + + + 127.0.0.1 + true + true + false + + + lax + + + warning + vortexdds-.${NON_EXISTENT_ENV_VARIABLE:-l}${CYCLONEDDS_URI:+o}g + + + ${MAX_PARTICIPANTS} + 100 ms + + + + diff --git a/src/core/ddsc/tests/dispose.c b/src/core/ddsc/tests/dispose.c new file mode 100644 index 0000000..c066f3a --- /dev/null +++ b/src/core/ddsc/tests/dispose.c @@ -0,0 +1,1099 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include "os/os.h" +#include +#include +#include +#include "Space.h" + +/* Add --verbose command line argument to get the cr_log_info traces (if there are any). */ + +#if 0 +#define PRINT_SAMPLE(info, sample) cr_log_info("%s (%d, %d, %d)\n", info, sample.long_1, sample.long_2, sample.long_3); +#else +#define PRINT_SAMPLE(info, sample) +#endif + + + +/************************************************************************************************** + * + * Test fixtures + * + *************************************************************************************************/ +#define MAX_SAMPLES 7 +#define INITIAL_SAMPLES 2 + +static dds_entity_t g_participant = 0; +static dds_entity_t g_topic = 0; +static dds_entity_t g_reader = 0; +static dds_entity_t g_writer = 0; +static dds_entity_t g_waitset = 0; + +static dds_time_t g_past = 0; +static dds_time_t g_present = 0; + + +static void* g_samples[MAX_SAMPLES]; +static Space_Type1 g_data[MAX_SAMPLES]; +static dds_sample_info_t g_info[MAX_SAMPLES]; + +static char* +create_topic_name(const char *prefix, char *name, size_t size) +{ + /* Get semi random g_topic name. */ + os_procId pid = os_procIdSelf(); + uintmax_t tid = os_threadIdToInteger(os_threadIdSelf()); + (void) snprintf(name, size, "%s_pid%"PRIprocId"_tid%"PRIuMAX"", prefix, pid, tid); + return name; +} + +static void +disposing_init(void) +{ + Space_Type1 sample = { 0 }; + dds_qos_t *qos = dds_qos_create (); + dds_attach_t triggered; + dds_return_t ret; + char name[100]; + + /* Use by source timestamp to be able to check the time related funtions. */ + dds_qset_destination_order(qos, DDS_DESTINATIONORDER_BY_SOURCE_TIMESTAMP); + + g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_participant, 0, "Failed to create prerequisite g_participant"); + + g_waitset = dds_create_waitset(g_participant); + cr_assert_gt(g_waitset, 0, "Failed to create g_waitset"); + + g_topic = dds_create_topic(g_participant, &Space_Type1_desc, create_topic_name("ddsc_disposing_test", name, sizeof name), qos, NULL); + cr_assert_gt(g_topic, 0, "Failed to create prerequisite g_topic"); + + /* Create a reader that keeps one sample on three instances. */ + dds_qset_reliability(qos, DDS_RELIABILITY_RELIABLE, DDS_MSECS(100)); + dds_qset_resource_limits(qos, DDS_LENGTH_UNLIMITED, 3, 1); + g_reader = dds_create_reader(g_participant, g_topic, qos, NULL); + cr_assert_gt(g_reader, 0, "Failed to create prerequisite g_reader"); + + /* Create a writer that will not automatically dispose unregistered samples. */ + dds_qset_writer_data_lifecycle(qos, false); + g_writer = dds_create_writer(g_participant, g_topic, qos, NULL); + cr_assert_gt(g_writer, 0, "Failed to create prerequisite g_writer"); + + /* Sync g_writer to g_reader. */ + ret = dds_set_enabled_status(g_writer, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_writer status"); + ret = dds_waitset_attach(g_waitset, g_writer, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_writer"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_writer r"); + cr_assert_eq(g_writer, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_writer a"); + ret = dds_waitset_detach(g_waitset, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_writer"); + + /* Sync g_reader to g_writer. */ + ret = dds_set_enabled_status(g_reader, DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_reader status"); + ret = dds_waitset_attach(g_waitset, g_reader, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_reader"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_reader r"); + cr_assert_eq(g_reader, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_reader a"); + ret = dds_waitset_detach(g_waitset, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_reader"); + + /* Write initial samples. */ + for (int i = 0; i < INITIAL_SAMPLES; i++) { + sample.long_1 = i; + sample.long_2 = i*2; + sample.long_3 = i*3; + PRINT_SAMPLE("INIT: Write ", sample); + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + } + + /* Initialize reading buffers. */ + memset (g_data, 0, sizeof (g_data)); + for (int i = 0; i < MAX_SAMPLES; i++) { + g_samples[i] = &g_data[i]; + } + + /* Initialize times. */ + g_present = dds_time(); + g_past = g_present - DDS_SECS(1); + + dds_qos_delete(qos); +} + +static void +disposing_fini(void) +{ + dds_delete(g_reader); + dds_delete(g_writer); + dds_delete(g_waitset); + dds_delete(g_topic); + dds_delete(g_participant); +} + + +#if 0 +#else +/************************************************************************************************** + * + * These will check the dds_writedispose() in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_writedispose, deleted, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + dds_delete(g_writer); + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_writedispose(g_writer, NULL); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_writedispose, null, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_writedispose(g_writer, NULL); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_writedispose, invalid_writers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t writer), ddsc_writedispose, invalid_writers, .init=disposing_init, .fini=disposing_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_writedispose(writer, NULL); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_writedispose, non_writers) = { + DataPoints(dds_entity_t*, &g_waitset, &g_reader, &g_topic, &g_participant), +}; +Theory((dds_entity_t *writer), ddsc_writedispose, non_writers, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_writedispose(*writer, NULL); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_writedispose, disposing_old_instance, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 oldInstance = { 0, 22, 22 }; + dds_return_t ret; + + ret = dds_writedispose(g_writer, &oldInstance); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing old instance returned %d", dds_err_nr(ret)); + + /* Read all samples that matches filter. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 == 0) { + /* Check data. */ + cr_assert_eq(sample->long_2, 22); + cr_assert_eq(sample->long_3, 22); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_IST_NOT_ALIVE_DISPOSED); + } else if (sample->long_1 == 1) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else { + cr_assert(false, "Unknown sample read"); + } + } +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_writedispose, disposing_new_instance, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 newInstance = { INITIAL_SAMPLES, 42, 42 }; + dds_return_t ret; + + ret = dds_writedispose(g_writer, &newInstance); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing new instance returned %d", dds_err_nr(ret)); + + /* Read all samples that matches filter. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 3, "# read %d, expected %d", ret, 3); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 < INITIAL_SAMPLES) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else if (sample->long_1 == INITIAL_SAMPLES) { + /* Check data. */ + cr_assert_eq(sample->long_2, 42); + cr_assert_eq(sample->long_3, 42); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_IST_NOT_ALIVE_DISPOSED); + } else { + cr_assert(false, "Unknown sample read"); + } + } +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_writedispose, timeout, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 newInstance1 = { INITIAL_SAMPLES , 22, 22 }; + Space_Type1 newInstance2 = { INITIAL_SAMPLES+1, 42, 42 }; + dds_return_t ret; + + ret = dds_writedispose(g_writer, &newInstance1); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing new instance returned %d", dds_err_nr(ret)); + ret = dds_writedispose(g_writer, &newInstance2); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_TIMEOUT, "Disposing new instance returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + + +/************************************************************************************************** + * + * These will check the dds_writedispose_ts() in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_writedispose_ts, deleted, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + dds_delete(g_writer); + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_writedispose_ts(g_writer, NULL, g_present); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_writedispose_ts, null, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_writedispose_ts(g_writer, NULL, g_present); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_writedispose_ts, timeout, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 newInstance1 = { INITIAL_SAMPLES , 22, 22 }; + Space_Type1 newInstance2 = { INITIAL_SAMPLES+1, 42, 42 }; + dds_return_t ret; + + ret = dds_writedispose_ts(g_writer, &newInstance1, g_present); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing new instance returned %d", dds_err_nr(ret)); + ret = dds_writedispose_ts(g_writer, &newInstance2, g_present); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_TIMEOUT, "Disposing new instance returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_writedispose_ts, invalid_writers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t writer), ddsc_writedispose_ts, invalid_writers, .init=disposing_init, .fini=disposing_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_writedispose_ts(writer, NULL, g_present); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_writedispose_ts, non_writers) = { + DataPoints(dds_entity_t*, &g_waitset, &g_reader, &g_topic, &g_participant), +}; +Theory((dds_entity_t *writer), ddsc_writedispose_ts, non_writers, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_writedispose_ts(*writer, NULL, g_present); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_writedispose_ts, disposing_old_instance, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 oldInstance = { 0, 22, 22 }; + dds_return_t ret; + + ret = dds_writedispose_ts(g_writer, &oldInstance, g_present); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing old instance returned %d", dds_err_nr(ret)); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 == 0) { + /* Check data. */ + cr_assert_eq(sample->long_2, 22); + cr_assert_eq(sample->long_3, 22); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_IST_NOT_ALIVE_DISPOSED); + } else if (sample->long_1 == 1) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else { + cr_assert(false, "Unknown sample read"); + } + } +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_writedispose_ts, disposing_new_instance, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 newInstance = { INITIAL_SAMPLES, 42, 42 }; + dds_return_t ret; + + ret = dds_writedispose_ts(g_writer, &newInstance, g_present); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing new instance returned %d", dds_err_nr(ret)); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 3, "# read %d, expected %d", ret, 3); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 < INITIAL_SAMPLES) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else if (sample->long_1 == INITIAL_SAMPLES) { + /* Check data. */ + cr_assert_eq(sample->long_2, 42); + cr_assert_eq(sample->long_3, 42); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_IST_NOT_ALIVE_DISPOSED); + } else { + cr_assert(false, "Unknown sample read"); + } + } +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_writedispose_ts, disposing_past_sample, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 oldInstance = { 0, 0, 0 }; + dds_return_t ret; + + /* Disposing a sample in the past should trigger a lost sample. */ + ret = dds_set_enabled_status(g_reader, DDS_SAMPLE_LOST_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_reader status"); + ret = dds_waitset_attach(g_waitset, g_reader, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_reader"); + + /* Now, dispose a sample in the past. */ + ret = dds_writedispose_ts(g_writer, &oldInstance, g_past); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing old instance returned %d", dds_err_nr(ret)); + + /* Wait for 'sample lost'. */ + ret = dds_waitset_wait(g_waitset, NULL, 0, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Disposing past sample did not trigger 'sample lost'"); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 < INITIAL_SAMPLES) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else { + cr_assert(false, "Unknown sample read"); + } + } + +} +/*************************************************************************************************/ + + + + +/************************************************************************************************** + * + * These will check the dds_dispose() in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_dispose, deleted, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + dds_delete(g_writer); + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_dispose(g_writer, NULL); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_dispose, null, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_dispose(g_writer, NULL); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_dispose, timeout, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 newInstance1 = { INITIAL_SAMPLES , 22, 22 }; + Space_Type1 newInstance2 = { INITIAL_SAMPLES+1, 42, 42 }; + dds_return_t ret; + + ret = dds_dispose(g_writer, &newInstance1); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing new instance returned %d", dds_err_nr(ret)); + ret = dds_dispose(g_writer, &newInstance2); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_TIMEOUT, "Disposing new instance returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_dispose, invalid_writers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t writer), ddsc_dispose, invalid_writers, .init=disposing_init, .fini=disposing_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_dispose(writer, NULL); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_dispose, non_writers) = { + DataPoints(dds_entity_t*, &g_waitset, &g_reader, &g_topic, &g_participant), +}; +Theory((dds_entity_t *writer), ddsc_dispose, non_writers, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_dispose(*writer, NULL); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_dispose, disposing_old_instance, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 oldInstance = { 0, 22, 22 }; + dds_return_t ret; + + ret = dds_dispose(g_writer, &oldInstance); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing old instance returned %d", dds_err_nr(ret)); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 == 0) { + /* Check data. */ + cr_assert_eq(sample->long_2, 0); + cr_assert_eq(sample->long_3, 0); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_IST_NOT_ALIVE_DISPOSED); + } else if (sample->long_1 == 1) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else { + cr_assert(false, "Unknown sample read"); + } + } +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_dispose, disposing_new_instance, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 newInstance = { INITIAL_SAMPLES, 42, 42 }; + dds_return_t ret; + + ret = dds_dispose(g_writer, &newInstance); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing new instance returned %d", dds_err_nr(ret)); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 3, "# read %d, expected %d", ret, 3); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 < INITIAL_SAMPLES) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else if (sample->long_1 == INITIAL_SAMPLES) { + /* Don't check data; it's not valid. */ + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, false); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_IST_NOT_ALIVE_DISPOSED); + } else { + cr_assert(false, "Unknown sample read"); + } + } +} +/*************************************************************************************************/ + + + + +/************************************************************************************************** + * + * These will check the dds_dispose_ts() in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_dispose_ts, deleted, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + dds_delete(g_writer); + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_dispose_ts(g_writer, NULL, g_present); + OS_WARNING_MSVC_ON(6387); /* Disable SAL warning on intentional misuse of the API */ + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_dispose_ts, null, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_dispose_ts(g_writer, NULL, g_present); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_dispose_ts, timeout, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 newInstance1 = { INITIAL_SAMPLES , 22, 22 }; + Space_Type1 newInstance2 = { INITIAL_SAMPLES+1, 42, 42 }; + dds_return_t ret; + + ret = dds_dispose_ts(g_writer, &newInstance1, g_present); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing new instance returned %d", dds_err_nr(ret)); + ret = dds_dispose_ts(g_writer, &newInstance2, g_present); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_TIMEOUT, "Disposing new instance returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_dispose_ts, invalid_writers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t writer), ddsc_dispose_ts, invalid_writers, .init=disposing_init, .fini=disposing_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_dispose_ts(writer, NULL, g_present); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_dispose_ts, non_writers) = { + DataPoints(dds_entity_t*, &g_waitset, &g_reader, &g_topic, &g_participant), +}; +Theory((dds_entity_t *writer), ddsc_dispose_ts, non_writers, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_dispose_ts(*writer, NULL, g_present); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_dispose_ts, disposing_old_instance, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 oldInstance = { 0, 22, 22 }; + dds_return_t ret; + + ret = dds_dispose_ts(g_writer, &oldInstance, g_present); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing old instance returned %d", dds_err_nr(ret)); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 == 0) { + /* Check data (data part of dispose is not used, only the key part). */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_IST_NOT_ALIVE_DISPOSED); + } else if (sample->long_1 == 1) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else { + cr_assert(false, "Unknown sample read"); + } + } +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_dispose_ts, disposing_new_instance, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 newInstance = { INITIAL_SAMPLES, 42, 42 }; + dds_return_t ret; + + ret = dds_dispose_ts(g_writer, &newInstance, g_present); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing new instance returned %d", dds_err_nr(ret)); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 3, "# read %d, expected %d", ret, 3); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 < INITIAL_SAMPLES) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else if (sample->long_1 == INITIAL_SAMPLES) { + /* Don't check data; it's not valid. */ + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, false); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_IST_NOT_ALIVE_DISPOSED); + } else { + cr_assert(false, "Unknown sample read"); + } + } +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_dispose_ts, disposing_past_sample, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 oldInstance = { 0, 0, 0 }; + dds_return_t ret; + + /* Disposing a sample in the past should trigger a lost sample. */ + ret = dds_set_enabled_status(g_reader, DDS_SAMPLE_LOST_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_reader status"); + ret = dds_waitset_attach(g_waitset, g_reader, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_reader"); + + /* Now, dispose a sample in the past. */ + ret = dds_dispose_ts(g_writer, &oldInstance, g_past); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing old instance returned %d", dds_err_nr(ret)); + + /* Wait for 'sample lost'. */ + ret = dds_waitset_wait(g_waitset, NULL, 0, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Disposing past sample did not trigger 'sample lost'"); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if ((sample->long_1 == 0) || (sample->long_1 == 1)) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else { + cr_assert(false, "Unknown sample read"); + } + } + +} +/*************************************************************************************************/ + + + + +/************************************************************************************************** + * + * These will check the dds_dispose_ih() in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_dispose_ih, deleted, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + dds_delete(g_writer); + ret = dds_dispose_ih(g_writer, DDS_HANDLE_NIL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_dispose_ih, invalid_handles) = { + DataPoints(dds_instance_handle_t, DDS_HANDLE_NIL, 0, 1, 100, UINT64_MAX), +}; +Theory((dds_instance_handle_t handle), ddsc_dispose_ih, invalid_handles, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + ret = dds_dispose_ih(g_writer, handle); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_PRECONDITION_NOT_MET, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_dispose_ih, invalid_writers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t writer), ddsc_dispose_ih, invalid_writers, .init=disposing_init, .fini=disposing_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_dispose_ih(writer, DDS_HANDLE_NIL); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_dispose_ih, non_writers) = { + DataPoints(dds_entity_t*, &g_waitset, &g_reader, &g_topic, &g_participant), +}; +Theory((dds_entity_t *writer), ddsc_dispose_ih, non_writers, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + ret = dds_dispose_ih(*writer, DDS_HANDLE_NIL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_dispose_ih, disposing_old_instance, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 oldInstance = { 0, 22, 22 }; + dds_instance_handle_t hdl = dds_instance_lookup(g_writer, &oldInstance); + dds_return_t ret; + + ret = dds_dispose_ih(g_writer, hdl); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing old instance returned %d", dds_err_nr(ret)); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 == 0) { + /* Check data. */ + cr_assert_eq(sample->long_2, 0); + cr_assert_eq(sample->long_3, 0); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_IST_NOT_ALIVE_DISPOSED); + } else if (sample->long_1 == 1) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else { + cr_assert(false, "Unknown sample read"); + } + } +} +/*************************************************************************************************/ + + + + +/************************************************************************************************** + * + * These will check the dds_dispose_ih_ts() in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_dispose_ih_ts, deleted, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + dds_delete(g_writer); + ret = dds_dispose_ih_ts(g_writer, DDS_HANDLE_NIL, g_present); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_dispose_ih_ts, invalid_handles) = { + DataPoints(dds_instance_handle_t, DDS_HANDLE_NIL, 0, 1, 100, UINT64_MAX), +}; +Theory((dds_instance_handle_t handle), ddsc_dispose_ih_ts, invalid_handles, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + ret = dds_dispose_ih_ts(g_writer, handle, g_present); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_PRECONDITION_NOT_MET, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_dispose_ih_ts, invalid_writers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t writer), ddsc_dispose_ih_ts, invalid_writers, .init=disposing_init, .fini=disposing_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_dispose_ih_ts(writer, DDS_HANDLE_NIL, g_present); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_dispose_ih_ts, non_writers) = { + DataPoints(dds_entity_t*, &g_waitset, &g_reader, &g_topic, &g_participant), +}; +Theory((dds_entity_t *writer), ddsc_dispose_ih_ts, non_writers, .init=disposing_init, .fini=disposing_fini) +{ + dds_return_t ret; + ret = dds_dispose_ih_ts(*writer, DDS_HANDLE_NIL, g_present); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_dispose_ih_ts, disposing_old_instance, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 oldInstance = { 0, 22, 22 }; + dds_instance_handle_t hdl = dds_instance_lookup(g_writer, &oldInstance); + dds_return_t ret; + + ret = dds_dispose_ih_ts(g_writer, hdl, g_present); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing old instance returned %d", dds_err_nr(ret)); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 == 0) { + /* Check data (data part of dispose is not used, only the key part). */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_IST_NOT_ALIVE_DISPOSED); + } else if (sample->long_1 == 1) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else { + cr_assert(false, "Unknown sample read"); + } + } +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_dispose_ih_ts, disposing_past_sample, .init=disposing_init, .fini=disposing_fini) +{ + Space_Type1 oldInstance = { 0, 0, 0 }; + dds_instance_handle_t hdl = dds_instance_lookup(g_writer, &oldInstance); + dds_return_t ret; + + /* Disposing a sample in the past should trigger a lost sample. */ + ret = dds_set_enabled_status(g_reader, DDS_SAMPLE_LOST_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_reader status"); + ret = dds_waitset_attach(g_waitset, g_reader, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_reader"); + + /* Now, dispose a sample in the past. */ + ret = dds_dispose_ih_ts(g_writer, hdl, g_past); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing old instance returned %d", dds_err_nr(ret)); + + /* Wait for 'sample lost'. */ + ret = dds_waitset_wait(g_waitset, NULL, 0, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Disposing past sample did not trigger 'sample lost'"); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if ((sample->long_1 == 0) || (sample->long_1 == 1)) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else { + cr_assert(false, "Unknown sample read"); + } + } + +} +/*************************************************************************************************/ + + +#endif diff --git a/src/core/ddsc/tests/entity_api.c b/src/core/ddsc/tests/entity_api.c new file mode 100644 index 0000000..e821080 --- /dev/null +++ b/src/core/ddsc/tests/entity_api.c @@ -0,0 +1,365 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include +#include + +/* We are deliberately testing some bad arguments that SAL will complain about. + * So, silence SAL regarding these issues. */ +#pragma warning(push) +#pragma warning(disable: 6387 28020) + +/* Add --verbose command line argument to get the cr_log_info traces (if there are any). */ + +static dds_entity_t entity = -1; + +#define cr_assert_status_eq(s1, s2, ...) cr_assert_eq(dds_err_nr(s1), s2, __VA_ARGS__) + +/* Fixture to create prerequisite entity */ +void create_entity(void) +{ + cr_assert_eq(entity, -1, "entity already created pre create_entity fixture"); + entity = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(entity, 0, "create_entity fixture failed"); +} + +/* Fixture to delete prerequisite entity */ +void delete_entity(void) +{ + cr_assert_gt(entity, 0, "entity not created pre delete_entity fixture"); + dds_return_t ret = dds_delete(entity); + cr_assert_status_eq(ret, DDS_RETCODE_OK, "delete_entity fixture failed (ret: %d)", dds_err_nr(ret)); + entity = -1; +} + +Test(ddsc_entity, create, .fini = delete_entity) +{ + /* Use participant as entity in the tests. */ + entity = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(entity, 0, "dds_create_participant"); +} + +Test(ddsc_entity, enable, .init = create_entity, .fini = delete_entity) +{ + dds_return_t status; + + /* Check enabling with bad parameters. */ + status = dds_enable(0); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_enable (NULL)"); + + /* Check actual enabling. */ + /* TODO: CHAM-96: Check enabling. + status = dds_enable(&entity); + cr_assert_status_eq(status, dds_err_nr(DDS_RETCODE_OK), "dds_enable (delayed enable)"); + */ + + /* Check re-enabling (should be a noop). */ + status = dds_enable(entity); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_enable (already enabled)"); +} + +void entity_qos_get_set(dds_entity_t e, const char* info) +{ + dds_return_t status; + dds_qos_t *qos = dds_qos_create(); + + /* Get QoS. */ + status = dds_get_qos (e, qos); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_qos(e, qos) ret: %d, %s", dds_err_nr(status), info); + + status = dds_set_qos (e, qos); /* Doesn't change anything, so no need to forbid. But we return NOT_SUPPORTED anyway for now*/ + cr_assert_status_eq(status, DDS_RETCODE_UNSUPPORTED, "dds_set_qos(entity, qos) %s", info); + + dds_qos_delete(qos); +} + +Test(ddsc_entity, qos, .init = create_entity, .fini = delete_entity) +{ + dds_return_t status; + dds_qos_t *qos = dds_qos_create(); + + /* Don't check inconsistent and immutable policies. That's a job + * for the specific entity children, not for the generic part. */ + + /* Check getting QoS with bad parameters. */ + status = dds_get_qos (0, NULL); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_get_qos(NULL, NULL)"); + status = dds_get_qos (entity, NULL); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_get_qos(entity, NULL)"); + status = dds_get_qos (0, qos); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_get_qos(NULL, qos)"); + + /* Check setting QoS with bad parameters. */ + status = dds_set_qos (0, NULL); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_set_qos(NULL, NULL)"); + status = dds_set_qos (entity, NULL); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_set_qos(entity, NULL)"); + status = dds_set_qos (0, qos); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_set_qos(NULL, qos)"); + + /* Check set/get with entity without initial qos. */ + entity_qos_get_set(entity, "{without initial qos}"); + + /* Check set/get with entity with initial qos. */ + { + dds_entity_t par = dds_create_participant (DDS_DOMAIN_DEFAULT, qos, NULL); + entity_qos_get_set(par, "{with initial qos}"); + dds_delete(par); + } + + /* Delete qos. */ + dds_qos_delete(qos); +} + +Test(ddsc_entity, listener, .init = create_entity, .fini = delete_entity) +{ + dds_return_t status; + dds_listener_t *l1 = dds_listener_create(NULL); + dds_listener_t *l2 = dds_listener_create(NULL); + void *cb1; + void *cb2; + + /* Don't check actual workings of the listeners. That's a job + * for the specific entity children, not for the generic part. */ + + /* Set some random values for the l2 listener callbacks. + * I know for sure that these will not be called within this test. + * Otherwise, the following would let everything crash. + * We just set them to know for sure that we got what we set. */ + dds_lset_liveliness_changed(l2, (dds_on_liveliness_changed_fn) 1234); + dds_lset_requested_deadline_missed(l2, (dds_on_requested_deadline_missed_fn) 5678); + dds_lset_requested_incompatible_qos(l2, (dds_on_requested_incompatible_qos_fn) 8765); + dds_lset_publication_matched(l2, (dds_on_publication_matched_fn) 4321); + + /* Check getting Listener with bad parameters. */ + status = dds_get_listener (0, NULL); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_get_listener(NULL, NULL)"); + status = dds_get_listener (entity, NULL); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_get_listener(entity, NULL)"); + status = dds_get_listener (0, l1); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_get_listener(NULL, listener)"); + + /* Get Listener, which should be unset. */ + status = dds_get_listener (entity, l1); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_listener(entity, listener)"); + dds_lget_liveliness_changed (l1, (dds_on_liveliness_changed_fn*)&cb1); + cr_assert_eq(cb1, DDS_LUNSET, "Listener not initialized to NULL"); + dds_lget_requested_deadline_missed (l1, (dds_on_requested_deadline_missed_fn*)&cb1); + cr_assert_eq(cb1, DDS_LUNSET, "Listener not initialized to NULL"); + dds_lget_requested_incompatible_qos (l1, (dds_on_requested_incompatible_qos_fn*)&cb1); + cr_assert_eq(cb1, DDS_LUNSET, "Listener not initialized to NULL"); + dds_lget_publication_matched (l1, (dds_on_publication_matched_fn*)&cb1); + cr_assert_eq(cb1, DDS_LUNSET, "Listener not initialized to NULL"); + + /* Check setting Listener with bad parameters. */ + status = dds_set_listener (0, NULL); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_set_listener(NULL, NULL)"); + status = dds_set_listener (0, l2); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_set_listener(NULL, listener)"); + + /* Getting after setting should return set listener. */ + status = dds_set_listener (entity, l2); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_set_listener(entity, listener)"); + status = dds_get_listener (entity, l1); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_listener(entity, listener)"); + dds_lget_liveliness_changed (l1, (dds_on_liveliness_changed_fn*)&cb1); + dds_lget_liveliness_changed (l2, (dds_on_liveliness_changed_fn*)&cb2); + cr_assert_eq(cb1, cb2, "Listeners are not equal"); + dds_lget_requested_deadline_missed (l1, (dds_on_requested_deadline_missed_fn*)&cb1); + dds_lget_requested_deadline_missed (l2, (dds_on_requested_deadline_missed_fn*)&cb2); + cr_assert_eq(cb1, cb2, "Listeners are not equal"); + dds_lget_requested_incompatible_qos (l1, (dds_on_requested_incompatible_qos_fn*)&cb1); + dds_lget_requested_incompatible_qos (l2, (dds_on_requested_incompatible_qos_fn*)&cb2); + cr_assert_eq(cb1, cb2, "Listeners are not equal"); + dds_lget_publication_matched (l1, (dds_on_publication_matched_fn*)&cb1); + dds_lget_publication_matched (l2, (dds_on_publication_matched_fn*)&cb2); + cr_assert_eq(cb1, cb2, "Listeners are not equal"); + + /* Reset listener. */ + status = dds_set_listener (entity, NULL); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_set_listener(entity, NULL)"); + status = dds_get_listener (entity, l2); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_listener(entity, listener)"); + dds_lget_liveliness_changed (l2, (dds_on_liveliness_changed_fn*)&cb2); + cr_assert_eq(cb2, DDS_LUNSET, "Listener not reset"); + dds_lget_requested_deadline_missed (l2, (dds_on_requested_deadline_missed_fn*)&cb2); + cr_assert_eq(cb2, DDS_LUNSET, "Listener not reset"); + dds_lget_requested_incompatible_qos (l2, (dds_on_requested_incompatible_qos_fn*)&cb2); + cr_assert_eq(cb2, DDS_LUNSET, "Listener not reset"); + dds_lget_publication_matched (l2, (dds_on_publication_matched_fn*)&cb2); + cr_assert_eq(cb2, DDS_LUNSET, "Listener not reset"); + + dds_free(l2); + dds_free(l1); +} + +Test(ddsc_entity, status, .init = create_entity, .fini = delete_entity) +{ + dds_return_t status1; + uint32_t s1 = 0; + + /* Don't check actual bad statuses. That's a job + * for the specific entity children, not for the generic part. */ + + /* Check getting Status with bad parameters. */ + status1 = dds_get_enabled_status (0, NULL); + cr_assert_status_eq(status1, DDS_RETCODE_BAD_PARAMETER, "dds_get_enabled_status(NULL, NULL)"); + status1 = dds_get_enabled_status (entity, NULL); + cr_assert_status_eq(status1, DDS_RETCODE_BAD_PARAMETER, "dds_get_enabled_status(entity, NULL)"); + status1 = dds_get_enabled_status (0, &s1); + cr_assert_status_eq(status1, DDS_RETCODE_BAD_PARAMETER, "dds_get_enabled_status(NULL, status)"); + + /* Get Status, which should be 0 for a participant. */ + status1 = dds_get_enabled_status (entity, &s1); + cr_assert_status_eq(status1, DDS_RETCODE_OK, "dds_get_enabled_status(entity, status)"); + cr_assert_eq(s1, 0, "Enabled status mask is not 0"); + + /* Check setting Status with bad parameters. */ + status1 = dds_set_enabled_status (0, 0); + cr_assert_status_eq(status1, DDS_RETCODE_BAD_PARAMETER, "dds_set_enabled_status(NULL, 0)"); + + /* I shouldn't be able to set statuses on a participant. */ + status1 = dds_set_enabled_status (entity, 0); + cr_assert_status_eq(status1, DDS_RETCODE_OK, "dds_set_enabled_status(entity, 0) %d", dds_err_nr(status1)); + status1 = dds_set_enabled_status (entity, DDS_DATA_AVAILABLE_STATUS); + cr_assert_status_eq(status1, DDS_RETCODE_BAD_PARAMETER, "dds_set_enabled_status(entity, status)"); + + /* Check getting Status changes with bad parameters. */ + status1 = dds_get_status_changes (0, NULL); + cr_assert_status_eq(status1, DDS_RETCODE_BAD_PARAMETER, "dds_get_status_changes(NULL, NULL)"); + status1 = dds_get_status_changes (entity, NULL); + cr_assert_status_eq(status1, DDS_RETCODE_BAD_PARAMETER, "dds_get_status_changes(entity, NULL)"); + status1 = dds_get_status_changes (0, &s1); + cr_assert_status_eq(status1, DDS_RETCODE_BAD_PARAMETER, "dds_get_status_changes(NULL, status)"); + status1 = dds_get_status_changes (entity, &s1); + cr_assert_status_eq(status1, DDS_RETCODE_OK, "dds_get_status_changes(entity, status)"); + + /* Status read and take shouldn't work either. */ + status1 = dds_read_status (0, &s1, 0); + cr_assert_status_eq(status1, DDS_RETCODE_BAD_PARAMETER, "dds_read_status(NULL, status, 0)"); + status1 = dds_read_status (entity, &s1, 0); + cr_assert_status_eq(status1, DDS_RETCODE_OK, "dds_read_status(entity, status, 0)"); + status1 = dds_take_status (0, &s1, 0); + cr_assert_status_eq(status1, DDS_RETCODE_BAD_PARAMETER, "dds_take_status(NULL, status, 0)"); + status1 = dds_take_status (entity, &s1, 0); + cr_assert_status_eq(status1, DDS_RETCODE_OK, "dds_take_status(entity, status, 0)"); +} + + +Test(ddsc_entity, instance_handle, .init = create_entity, .fini = delete_entity) +{ + dds_return_t status; + dds_instance_handle_t hdl; + + /* Don't check actual handle contents. That's a job + * for the specific entity children, not for the generic part. */ + + /* Check getting Handle with bad parameters. */ + status = dds_get_instance_handle (0, NULL); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_instancehandle_get(NULL, NULL)"); + status = dds_get_instance_handle (entity, NULL); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_instancehandle_get(entity, NULL)"); + status = dds_get_instance_handle (0, &hdl); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_instancehandle_get(NULL, handle)"); + + /* Get Instance Handle, which should not be 0 for a participant. */ + status = dds_get_instance_handle (entity, &hdl); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_instancehandle_get(entity, handle)"); + cr_assert_neq(hdl, 0, "Entity instance handle is 0"); +} + +Test(ddsc_entity, get_entities, .init = create_entity, .fini = delete_entity) +{ + dds_return_t status; + dds_entity_t par; + dds_entity_t child; + + /* ---------- Get Parent ------------ */ + + /* Check getting Parent with bad parameters. */ + par = dds_get_parent (0); + cr_assert_eq(dds_err_nr(par), DDS_RETCODE_BAD_PARAMETER, "Parent was returned (despite of bad parameter)"); + + /* Get Parent, a participant doesn't have a parent. */ + par = dds_get_parent (entity); + cr_assert_eq(dds_err_nr(par), DDS_ENTITY_NIL, "Parent was returned (despite of it being a participant)"); + + /* ---------- Get Participant ------------ */ + + /* Check getting Participant with bad parameters. */ + par = dds_get_participant (0); + cr_assert_eq(dds_err_nr(par), DDS_RETCODE_BAD_PARAMETER, "Participant was returned (despite of bad parameter)"); + + /* Get Participant, a participants' participant is itself. */ + par = dds_get_participant (entity); + cr_assert_eq(par, entity, "Returned participant was not expected"); + + /* ---------- Get Children ------------ */ + + /* Check getting Children with bad parameters. */ + status = dds_get_children (0, &child, 1); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_get_children(NULL, child, 1)"); + status = dds_get_children (entity, NULL, 1); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_get_children(entity, NULL, 1)"); + status = dds_get_children (entity, &child, 0); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_get_children(entity, child, 0)"); + status = dds_get_children (0, NULL, 1); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_get_children(NULL, NULL, 1)"); + status = dds_get_children (0, &child, 0); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_get_children(NULL, child, 0)"); + + /* Get Children, of which there are currently none. */ + status = dds_get_children (entity, NULL, 0); + if (status > 0) { + cr_assert("dds_get_children(entity, NULL, 0) un-expectantly found children"); + } else { + cr_assert_eq(status, 0, "dds_get_children(entity, NULL, 0) failed"); + } + status = dds_get_children (entity, &child, 1); + if (status > 0) { + cr_assert("dds_get_children(entity, child, 1) un-expectantly returned children"); + } else { + cr_assert_eq(status, 0, "dds_get_children(entity, child, 1) failed"); + } +} + +Test(ddsc_entity, get_domainid, .init = create_entity, .fini = delete_entity) +{ + dds_return_t status; + dds_domainid_t id; + + /* Check getting ID with bad parameters. */ + status = dds_get_domainid (0, NULL); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_get_domainid(NULL, NULL)"); + status = dds_get_domainid (entity, NULL); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_get_domainid(entity, NULL)"); + status = dds_get_domainid (0, &id); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_get_domainid(NULL, id)"); + + /* Get and check the domain id. */ + status = dds_get_domainid (entity, &id); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_domainid(entity, id)"); + cr_assert_eq(id, 0, "Different domain_id was returned than expected"); +} + +Test(ddsc_entity, delete, .init = create_entity) +{ + dds_return_t status; + status = dds_delete(0); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_delete(NULL)"); + + status = dds_delete(entity); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_delete(entity)"); + entity = 0; +} + +#pragma warning(pop) diff --git a/src/core/ddsc/tests/entity_hierarchy.c b/src/core/ddsc/tests/entity_hierarchy.c new file mode 100644 index 0000000..8078b30 --- /dev/null +++ b/src/core/ddsc/tests/entity_hierarchy.c @@ -0,0 +1,995 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include "os/os.h" +#include +#include +#include +#include "RoundTrip.h" + +/* Add --verbose command line argument to get the cr_log_info traces (if there are any). */ + + + +/************************************************************************************************** + * + * Test fixtures + * + *************************************************************************************************/ + +static dds_entity_t g_keep = 0; +static dds_entity_t g_participant = 0; +static dds_entity_t g_topic = 0; +static dds_entity_t g_subscriber = 0; +static dds_entity_t g_publisher = 0; +static dds_entity_t g_reader = 0; +static dds_entity_t g_writer = 0; +static dds_entity_t g_readcond = 0; +static dds_entity_t g_querycond = 0; + +/* Dummy query condition callback. */ +static bool +accept_all(const void * sample) +{ + return true; +} + +static char* +create_topic_name(const char *prefix, char *name, size_t size) +{ + /* Get semi random g_topic name. */ + os_procId pid = os_procIdSelf(); + uintmax_t tid = os_threadIdToInteger(os_threadIdSelf()); + (void) snprintf(name, size, "%s_pid%"PRIprocId"_tid%"PRIuMAX"", prefix, pid, tid); + return name; +} + +static void +hierarchy_init(void) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + char name[100]; + + g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_participant, 0, "Failed to create prerequisite g_participant"); + + g_topic = dds_create_topic(g_participant, &RoundTripModule_DataType_desc, create_topic_name("ddsc_hierarchy_test", name, sizeof name), NULL, NULL); + cr_assert_gt(g_topic, 0, "Failed to create prerequisite g_topic"); + + g_publisher = dds_create_publisher(g_participant, NULL, NULL); + cr_assert_gt(g_publisher, 0, "Failed to create prerequisite g_publisher"); + + g_subscriber = dds_create_subscriber(g_participant, NULL, NULL); + cr_assert_gt(g_subscriber, 0, "Failed to create prerequisite g_subscriber"); + + g_writer = dds_create_writer(g_publisher, g_topic, NULL, NULL); + cr_assert_gt(g_writer, 0, "Failed to create prerequisite g_writer"); + + g_reader = dds_create_reader(g_subscriber, g_topic, NULL, NULL); + cr_assert_gt(g_reader, 0, "Failed to create prerequisite g_reader"); + + g_readcond = dds_create_readcondition(g_reader, mask); + cr_assert_gt(g_readcond, 0, "Failed to create prerequisite g_readcond"); + + g_querycond = dds_create_querycondition(g_reader, mask, accept_all); + cr_assert_gt(g_querycond, 0, "Failed to create prerequisite g_querycond"); + + /* The deletion of the last participant will close down every thing. This + * means that the API will react differently after that. Because the + * testing we're doing here is quite generic, we'd like to not close down + * everything when we delete our participant. For that, we create a second + * participant, which will keep everything running. + */ + g_keep = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_keep, 0, "Failed to create prerequisite g_keep"); +} + +static void +hierarchy_fini(void) +{ + dds_delete(g_querycond); + dds_delete(g_readcond); + dds_delete(g_reader); + dds_delete(g_writer); + dds_delete(g_subscriber); + dds_delete(g_publisher); + dds_delete(g_topic); + dds_delete(g_participant); + dds_delete(g_keep); +} + + +#if 0 +#else + +/************************************************************************************************** + * + * These will check the recursive deletion. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_entity_delete, recursive, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_domainid_t id; + dds_return_t ret; + + /* First be sure that 'dds_get_domainid' returns ok. */ + ret = dds_get_domainid(g_participant, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK); + ret = dds_get_domainid(g_topic, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK); + ret = dds_get_domainid(g_publisher, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK); + ret = dds_get_domainid(g_subscriber, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK); + ret = dds_get_domainid(g_writer, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK); + ret = dds_get_domainid(g_reader, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK); + ret = dds_get_domainid(g_readcond, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK); + ret = dds_get_domainid(g_querycond, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK); + + /* Deleting the top dog (participant) should delete all children. */ + ret = dds_delete(g_participant); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK); + + /* Check if all the entities are deleted now. */ + ret = dds_get_domainid(g_participant, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); + ret = dds_get_domainid(g_topic, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); + ret = dds_get_domainid(g_publisher, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); + ret = dds_get_domainid(g_subscriber, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); + ret = dds_get_domainid(g_writer, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); + ret = dds_get_domainid(g_reader, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); + ret = dds_get_domainid(g_readcond, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); + ret = dds_get_domainid(g_querycond, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_delete, recursive_with_deleted_topic) +{ + dds_domainid_t id; + dds_return_t ret; + char name[100]; + + /* Internal handling of topic is different from all the other entities. + * It's very interesting if this recursive deletion still works and + * doesn't crash when the topic is already deleted (CHAM-424). */ + + /* First, create a topic and a writer with that topic. */ + g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_participant, 0, "Failed to create prerequisite g_participant"); + g_topic = dds_create_topic(g_participant, &RoundTripModule_DataType_desc, create_topic_name("ddsc_hierarchy_test", name, 100), NULL, NULL); + cr_assert_gt(g_topic, 0, "Failed to create prerequisite g_topic"); + g_writer = dds_create_writer(g_participant, g_topic, NULL, NULL); + cr_assert_gt(g_writer, 0, "Failed to create prerequisite g_writer"); + g_keep = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_keep, 0, "Failed to create prerequisite g_keep"); + + /* Second, delete the topic to make sure that the writer holds the last + * reference to the topic and thus will delete it when it itself is + * deleted. */ + ret = dds_delete(g_topic); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK); + + /* Third, deleting the participant should delete all children of which + * the writer with the last topic reference is one. */ + ret = dds_delete(g_participant); + /* Before the CHAM-424 fix, we would not get here because of a crash, + * or it (incidentally) continued but returned an error. */ + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK); + + /* Check if the entities are actually deleted. */ + ret = dds_get_domainid(g_participant, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "%s", dds_err_str(ret)); + ret = dds_get_domainid(g_topic, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); + ret = dds_get_domainid(g_writer, &id); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); + + dds_delete(g_keep); +} +/*************************************************************************************************/ + + + + +/************************************************************************************************** + * + * These will check the dds_get_participant in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_participant, valid_entities) = { + DataPoints(dds_entity_t*, &g_readcond, &g_querycond, &g_reader, &g_subscriber, &g_writer, &g_publisher, &g_topic, &g_participant), +}; +Theory((dds_entity_t *entity), ddsc_entity_get_participant, valid_entities, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t participant; + participant = dds_get_participant(*entity); + cr_assert_eq(participant, g_participant); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_participant, deleted_entities) = { + DataPoints(dds_entity_t*, &g_readcond, &g_querycond, &g_reader, &g_subscriber, &g_writer, &g_publisher, &g_topic, &g_participant), +}; +Theory((dds_entity_t *entity), ddsc_entity_get_participant, deleted_entities, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t participant; + dds_delete(*entity); + participant = dds_get_participant(*entity); + cr_assert_eq(dds_err_nr(participant), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(participant)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_participant, invalid_entities) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t entity), ddsc_entity_get_participant, invalid_entities, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_entity_t participant; + + participant = dds_get_participant(entity); + cr_assert_eq(dds_err_nr(participant), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(participant), dds_err_nr(exp)); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the dds_get_parent in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_parent, conditions) = { + DataPoints(dds_entity_t*, &g_readcond, &g_querycond), +}; +Theory((dds_entity_t *entity), ddsc_entity_get_parent, conditions, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t parent; + parent = dds_get_parent(*entity); + cr_assert_eq(parent, g_reader); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_get_parent, reader, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t parent; + parent = dds_get_parent(g_reader); + cr_assert_eq(parent, g_subscriber); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_get_parent, writer, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t parent; + parent = dds_get_parent(g_writer); + cr_assert_eq(parent, g_publisher); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_parent, pubsubtop) = { + DataPoints(dds_entity_t*, &g_publisher, &g_subscriber, &g_topic), +}; +Theory((dds_entity_t *entity), ddsc_entity_get_parent, pubsubtop, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t parent; + parent = dds_get_parent(*entity); + cr_assert_eq(parent, g_participant); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_get_parent, participant, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t parent; + parent = dds_get_parent(g_participant); + cr_assert_eq(dds_err_nr(parent), DDS_ENTITY_NIL, "returned %d", dds_err_nr(parent)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_parent, deleted_entities) = { + DataPoints(dds_entity_t*, &g_readcond, &g_querycond, &g_reader, &g_subscriber, &g_writer, &g_publisher, &g_topic, &g_participant), +}; +Theory((dds_entity_t *entity), ddsc_entity_get_parent, deleted_entities, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t parent; + dds_delete(*entity); + parent = dds_get_parent(*entity); + cr_assert_eq(dds_err_nr(parent), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_parent, invalid_entities) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t entity), ddsc_entity_get_parent, invalid_entities, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_entity_t parent; + + parent = dds_get_parent(entity); + cr_assert_eq(dds_err_nr(parent), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(parent), dds_err_nr(exp)); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the dds_get_children in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_entity_get_children, null, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_return_t ret; + ret = dds_get_children(g_participant, NULL, 0); + cr_assert_eq(ret, 3); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_get_children, invalid_size, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_return_t ret; + dds_entity_t child; + ret = dds_get_children(g_participant, &child, INT32_MAX); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_get_children, too_small, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_return_t ret; + dds_entity_t children[2]; + ret = dds_get_children(g_participant, children, 2); + cr_assert_eq(ret, 3); + cr_assert((children[0] == g_publisher) || (children[0] == g_subscriber) || (children[0] == g_topic)); + cr_assert((children[1] == g_publisher) || (children[1] == g_subscriber) || (children[1] == g_topic)); + cr_assert_neq(children[0], children[1]); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_get_children, participant, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_return_t ret; + dds_entity_t children[4]; + ret = dds_get_children(g_participant, children, 4); + cr_assert_eq(ret, 3); + cr_assert((children[0] == g_publisher) || (children[0] == g_subscriber) || (children[0] == g_topic)); + cr_assert((children[1] == g_publisher) || (children[1] == g_subscriber) || (children[1] == g_topic)); + cr_assert((children[2] == g_publisher) || (children[2] == g_subscriber) || (children[2] == g_topic)); + cr_assert_neq(children[0], children[1]); + cr_assert_neq(children[0], children[2]); + cr_assert_neq(children[1], children[2]); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_get_children, topic, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_return_t ret; + dds_entity_t child; + ret = dds_get_children(g_topic, &child, 1); + cr_assert_eq(ret, 0); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_get_children, publisher, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_return_t ret; + dds_entity_t child; + ret = dds_get_children(g_publisher, &child, 1); + cr_assert_eq(ret, 1); + cr_assert_eq(child, g_writer); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_get_children, subscriber, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_return_t ret; + dds_entity_t children[2]; + ret = dds_get_children(g_subscriber, children, 2); + cr_assert_eq(ret, 1); + cr_assert_eq(children[0], g_reader); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_get_children, writer, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_return_t ret; + ret = dds_get_children(g_writer, NULL, 0); + cr_assert_eq(ret, 0); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_get_children, reader, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_return_t ret; + dds_entity_t children[2]; + ret = dds_get_children(g_reader, children, 2); + cr_assert_eq(ret, 2); + cr_assert((children[0] == g_readcond) || (children[0] == g_querycond)); + cr_assert((children[1] == g_readcond) || (children[1] == g_querycond)); + cr_assert_neq(children[0], children[1]); +} +/*************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_children, conditions) = { + DataPoints(dds_entity_t*, &g_readcond, &g_querycond), +}; +Theory((dds_entity_t *entity), ddsc_entity_get_children, conditions, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_return_t ret; + dds_entity_t child; + ret = dds_get_children(*entity, &child, 1); + cr_assert_eq(ret, 0); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_children, deleted_entities) = { + DataPoints(dds_entity_t*, &g_readcond, &g_querycond, &g_reader, &g_subscriber, &g_writer, &g_publisher, &g_topic, &g_participant), +}; +Theory((dds_entity_t *entity), ddsc_entity_get_children, deleted_entities, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_return_t ret; + dds_entity_t children[4]; + dds_delete(*entity); + ret = dds_get_children(*entity, children, 4); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_children, invalid_entities) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t entity), ddsc_entity_get_children, invalid_entities, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_entity_t children[4]; + dds_return_t ret; + + ret = dds_get_children(entity, children, 4); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the dds_get_topic in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_topic, data_entities) = { + DataPoints(dds_entity_t*, &g_readcond, &g_querycond, &g_reader, &g_writer), +}; +Theory((dds_entity_t *entity), ddsc_entity_get_topic, data_entities, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t topic; + topic = dds_get_topic(*entity); + cr_assert_eq(topic, g_topic ); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_topic, deleted_entities) = { + DataPoints(dds_entity_t*, &g_readcond, &g_querycond, &g_reader, &g_writer), +}; +Theory((dds_entity_t *entity), ddsc_entity_get_topic, deleted_entities, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t topic; + dds_delete(*entity); + topic = dds_get_topic(*entity); + cr_assert_eq(dds_err_nr(topic), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_topic, invalid_entities) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t entity), ddsc_entity_get_topic, invalid_entities, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_entity_t topic; + + topic = dds_get_topic(entity); + cr_assert_eq(dds_err_nr(topic), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(topic), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_topic, non_data_entities) = { + DataPoints(dds_entity_t*, &g_subscriber, &g_publisher, &g_topic, &g_participant), +}; +Theory((dds_entity_t *entity), ddsc_entity_get_topic, non_data_entities, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t topic; + topic = dds_get_topic(*entity); + cr_assert_eq(dds_err_nr(topic), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(topic)); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the dds_get_publisher in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_entity_get_publisher, writer, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t publisher; + publisher = dds_get_publisher(g_writer); + cr_assert_eq(publisher, g_publisher); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_get_publisher, deleted_writer, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t publisher; + dds_delete(g_writer); + publisher = dds_get_publisher(g_writer); + cr_assert_eq(dds_err_nr(publisher), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_publisher, invalid_writers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t entity), ddsc_entity_get_publisher, invalid_writers, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_entity_t publisher; + + publisher = dds_get_publisher(entity); + cr_assert_eq(dds_err_nr(publisher), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(publisher), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_publisher, non_writers) = { + DataPoints(dds_entity_t*, &g_publisher, &g_reader, &g_publisher, &g_topic, &g_participant), +}; +Theory((dds_entity_t *cond), ddsc_entity_get_publisher, non_writers, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t publisher; + publisher = dds_get_publisher(*cond); + cr_assert_eq(dds_err_nr(publisher), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(publisher)); +} +/*************************************************************************************************/ + + + + +/************************************************************************************************** + * + * These will check the dds_get_subscriber in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_subscriber, readers) = { + DataPoints(dds_entity_t*, &g_readcond, &g_querycond, &g_reader), +}; +Theory((dds_entity_t *entity), ddsc_entity_get_subscriber, readers, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t subscriber; + subscriber = dds_get_subscriber(*entity); + cr_assert_eq(subscriber, g_subscriber); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_subscriber, deleted_readers) = { + DataPoints(dds_entity_t*, &g_readcond, &g_querycond, &g_reader), +}; +Theory((dds_entity_t *entity), ddsc_entity_get_subscriber, deleted_readers, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t subscriber; + dds_delete(*entity); + subscriber = dds_get_subscriber(*entity); + cr_assert_eq(dds_err_nr(subscriber), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_subscriber, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t entity), ddsc_entity_get_subscriber, invalid_readers, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_entity_t subscriber; + + subscriber = dds_get_subscriber(entity); + cr_assert_eq(dds_err_nr(subscriber), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(subscriber), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_subscriber, non_readers) = { + DataPoints(dds_entity_t*, &g_subscriber, &g_writer, &g_publisher, &g_topic, &g_participant), +}; +Theory((dds_entity_t *cond), ddsc_entity_get_subscriber, non_readers, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t subscriber; + subscriber = dds_get_subscriber(*cond); + cr_assert_eq(dds_err_nr(subscriber), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(subscriber)); +} +/*************************************************************************************************/ + + + + + + +/************************************************************************************************** + * + * These will check the dds_get_datareader in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_datareader, conditions) = { + DataPoints(dds_entity_t*, &g_readcond, &g_querycond), +}; +Theory((dds_entity_t *cond), ddsc_entity_get_datareader, conditions, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t reader; + reader = dds_get_datareader(*cond); + cr_assert_eq(reader, g_reader); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_datareader, deleted_conds) = { + DataPoints(dds_entity_t*, &g_readcond, &g_querycond), +}; +Theory((dds_entity_t *cond), ddsc_entity_get_datareader, deleted_conds, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t reader; + dds_delete(*cond); + reader = dds_get_datareader(*cond); + cr_assert_eq(dds_err_nr(reader), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_datareader, invalid_conds) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t cond), ddsc_entity_get_datareader, invalid_conds, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_entity_t reader; + + reader = dds_get_datareader(cond); + cr_assert_eq(dds_err_nr(reader), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(reader), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_entity_get_datareader, non_conds) = { + DataPoints(dds_entity_t*, &g_reader, &g_subscriber, &g_writer, &g_publisher, &g_topic, &g_participant), +}; +Theory((dds_entity_t *cond), ddsc_entity_get_datareader, non_conds, .init=hierarchy_init, .fini=hierarchy_fini) +{ + dds_entity_t reader; + reader = dds_get_datareader(*cond); + cr_assert_eq(dds_err_nr(reader), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(reader)); +} + +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_implicit_publisher, deleted) +{ + dds_entity_t participant; + dds_entity_t writer; + dds_entity_t topic; + dds_return_t ret; + char name[100]; + + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0); + + topic = dds_create_topic(participant, &RoundTripModule_DataType_desc, create_topic_name("ddsc_entity_implicit_publisher_test", name, 100), NULL, NULL); + cr_assert_gt(topic, 0); + + writer = dds_create_writer(participant, topic, NULL, NULL); + cr_assert_gt(writer, 0); + + ret = dds_get_children(participant, NULL, 0); + cr_assert_eq(ret, 2); + + dds_delete(writer); + + ret = dds_get_children(participant, NULL, 0); + cr_assert_eq(ret, 1); + + dds_delete(topic); + dds_delete(participant); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_implicit_publisher, invalid_topic) +{ + dds_entity_t participant; + dds_entity_t writer; + + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0); + + /* Disable SAL warning on intentional misuse of the API */ + OS_WARNING_MSVC_OFF(28020); + writer = dds_create_writer(participant, 0, NULL, NULL); + /* Disable SAL warning on intentional misuse of the API */ + OS_WARNING_MSVC_ON(28020); + cr_assert_lt(writer, 0); + + dds_delete(writer); + dds_delete(participant); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_implicit_subscriber, deleted) +{ + dds_entity_t participant; + dds_entity_t reader; + dds_entity_t topic; + dds_return_t ret; + char name[100]; + + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0); + + topic = dds_create_topic(participant, &RoundTripModule_DataType_desc, create_topic_name("ddsc_entity_implicit_subscriber_test", name, 100), NULL, NULL); + cr_assert_gt(topic, 0); + + reader = dds_create_reader(participant, topic, NULL, NULL); + cr_assert_gt(reader, 0); + + ret = dds_get_children(participant, NULL, 0); + cr_assert_eq(ret, 2); + + dds_delete(reader); + + ret = dds_get_children(participant, NULL, 0); + cr_assert_eq(ret, 1); + + dds_delete(topic); + dds_delete(participant); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_explicit_subscriber, invalid_topic) +{ + dds_entity_t participant; + dds_entity_t reader; + dds_entity_t subscriber; + + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0); + + subscriber = dds_create_subscriber(participant, NULL,NULL); + /* Disable SAL warning on intentional misuse of the API */ + OS_WARNING_MSVC_OFF(28020); + reader = dds_create_reader(subscriber, 0, NULL, NULL); + OS_WARNING_MSVC_ON(28020); + cr_assert_lt(reader, 0); + + dds_delete(reader); + dds_delete(participant); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_get_children, implicit_publisher) +{ + dds_entity_t participant; + dds_entity_t publisher; + dds_entity_t writer; + dds_entity_t topic; + dds_entity_t child[2], child2[2]; + dds_return_t ret; + char name[100]; + + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0); + + topic = dds_create_topic(participant, &RoundTripModule_DataType_desc, create_topic_name("ddsc_entity_implicit_publisher_test", name, 100), NULL, NULL); + cr_assert_gt(topic, 0); + + writer = dds_create_writer(participant, topic, NULL, NULL); + cr_assert_gt(writer, 0); + ret = dds_get_children(participant, child, 2); + cr_assert_eq(ret, 2); + if(child[0] == topic){ + publisher = child[1]; + } else if(child[1] == topic){ + publisher = child[0]; + } else{ + cr_assert(false, "topic was not returned"); + } + cr_assert_neq(publisher, topic); + + cr_assert_gt(publisher, 0); + cr_assert_neq(publisher, writer); + + dds_delete(writer); + + ret = dds_get_children(participant, child2, 2); + cr_assert_eq(ret, 2); + cr_assert( (child2[0] == child[0]) || (child2[0] == child[1]) ); + cr_assert( (child2[1] == child[0]) || (child2[1] == child[1]) ); + + dds_delete(topic); + dds_delete(participant); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_get_children, implicit_subscriber) +{ + dds_entity_t participant; + dds_entity_t subscriber; + dds_entity_t reader; + dds_entity_t topic; + dds_entity_t child[2], child2[2]; + dds_return_t ret; + char name[100]; + + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0); + + topic = dds_create_topic(participant, &RoundTripModule_DataType_desc, create_topic_name("ddsc_entity_implicit_subscriber_test", name, 100), NULL, NULL); + cr_assert_gt(topic, 0); + + reader = dds_create_reader(participant, topic, NULL, NULL); + cr_assert_gt(reader, 0); + ret = dds_get_children(participant, child, 2); + cr_assert_eq(ret, 2); + if(child[0] == topic){ + subscriber = child[1]; + } else if(child[1] == topic){ + subscriber = child[0]; + } else{ + cr_assert(false, "topic was not returned"); + } + cr_assert_neq(subscriber, topic); + + cr_assert_gt(subscriber, 0); + cr_assert_neq(subscriber, reader); + + dds_delete(reader); + + ret = dds_get_children(participant, child2, 2); + cr_assert_eq(ret, 2); + cr_assert( (child2[0] == child[0]) || (child2[0] == child[1]) ); + cr_assert( (child2[1] == child[0]) || (child2[1] == child[1]) ); + + dds_delete(topic); + dds_delete(participant); + +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_get_parent, implicit_publisher) +{ + dds_entity_t participant; + dds_entity_t writer; + dds_entity_t parent; + dds_entity_t topic; + dds_return_t ret; + char name[100]; + + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0); + + topic = dds_create_topic(participant, &RoundTripModule_DataType_desc, create_topic_name("ddsc_entity_implicit_publisher_promotion_test", name, 100), NULL, NULL); + cr_assert_gt(topic, 0); + + writer = dds_create_writer(participant, topic, NULL, NULL); + cr_assert_gt(writer, 0); + + parent = dds_get_parent(writer); + cr_assert_neq(parent, participant); + cr_assert_gt(parent, 0); + + dds_delete(writer); + + ret = dds_delete(parent); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK); + dds_delete(participant); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_entity_get_parent, implicit_subscriber) +{ + dds_entity_t participant; + dds_entity_t reader; + dds_entity_t parent; + dds_entity_t topic; + dds_return_t ret; + char name[100]; + + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0); + + topic = dds_create_topic(participant, &RoundTripModule_DataType_desc, create_topic_name("ddsc_entity_implicit_subscriber_promotion_test", name, 100), NULL, NULL); + cr_assert_gt(topic, 0); + + reader = dds_create_reader(participant, topic, NULL, NULL); + cr_assert_gt(reader, 0); + + parent = dds_get_parent(reader); + cr_assert_neq(parent, participant); + cr_assert_gt(parent, 0); + + dds_delete(reader); + + ret = dds_delete(parent); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK); + dds_delete(participant); + +} +/*************************************************************************************************/ + +/*************************************************************************************************/ + +#endif diff --git a/src/core/ddsc/tests/entity_status.c b/src/core/ddsc/tests/entity_status.c new file mode 100644 index 0000000..3a8579c --- /dev/null +++ b/src/core/ddsc/tests/entity_status.c @@ -0,0 +1,1335 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include "ddsc/dds.h" +#include "os/os.h" +#include "RoundTrip.h" +#include +#include +#include + +#define cr_assert_dds_return_t_eq(ret_t, expected, ...) cr_assert_eq(dds_err_nr(ret_t), (expected), __VA_ARGS__) + +/**************************************************************************** + * Test globals. + ****************************************************************************/ +static dds_entity_t participant; +static dds_entity_t subscriber; +static dds_entity_t publisher; +static dds_entity_t top; +static dds_entity_t wri; +static dds_entity_t rea; +static dds_entity_t waitSetwr; +static dds_entity_t waitSetrd; +static dds_return_t ret; +static uint32_t sta; + +static dds_qos_t *qos; +static dds_attach_t wsresults[1]; +static dds_attach_t wsresults2[2]; +static size_t wsresultsize = 1U; +static size_t wsresultsize2 = 2U; +static dds_time_t waitTimeout = DDS_SECS (2); +static dds_time_t shortTimeout = DDS_MSECS (10); +static dds_publication_matched_status_t publication_matched; +static dds_subscription_matched_status_t subscription_matched; +static dds_resource_limits_qospolicy_t resource_limits = {1,1,1}; + +static dds_instance_handle_t reader_i_hdl = 0; +static dds_instance_handle_t writer_i_hdl = 0; + +/**************************************************************************** + * Test initializations and teardowns. + ****************************************************************************/ +static char* +create_topic_name(const char *prefix, char *name, size_t size) +{ + /* Get semi random g_topic name. */ + os_procId pid = os_procIdSelf(); + uintmax_t tid = os_threadIdToInteger(os_threadIdSelf()); + (void) snprintf(name, size, "%s_pid%"PRIprocId"_tid%"PRIuMAX"", prefix, pid, tid); + return name; +} + +static void +init_entity_status(void) +{ + char topicName[100]; + + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "Failed to create prerequisite participant"); + + top = dds_create_topic(participant, &RoundTripModule_DataType_desc, create_topic_name("ddsc_status_test", topicName, 100), NULL, NULL); + cr_assert_gt(top, 0, "Failed to create prerequisite topic"); + + qos = dds_qos_create(); + cr_assert_not_null(qos, "Failed to create prerequisite qos"); + dds_qset_resource_limits (qos, resource_limits.max_samples, resource_limits.max_instances, resource_limits.max_samples_per_instance); + dds_qset_reliability(qos, DDS_RELIABILITY_BEST_EFFORT, DDS_MSECS(100)); + dds_qset_history (qos, DDS_HISTORY_KEEP_ALL, 0); + dds_qset_destination_order (qos, DDS_DESTINATIONORDER_BY_SOURCE_TIMESTAMP); + + subscriber = dds_create_subscriber(participant, qos, NULL); + cr_assert_gt(subscriber, 0); + rea = dds_create_reader(subscriber, top, qos, NULL); + cr_assert_gt(rea, 0); + publisher = dds_create_publisher(participant, qos, NULL); + cr_assert_gt(publisher, 0); + wri = dds_create_writer(publisher, top, qos, NULL); + cr_assert_gt(wri, 0); + + waitSetwr = dds_create_waitset(participant); + ret = dds_waitset_attach (waitSetwr, wri, wri); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + waitSetrd = dds_create_waitset(participant); + ret = dds_waitset_attach (waitSetrd, rea, rea); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + /* Get reader/writer handles because they can be tested against. */ + ret = dds_get_instance_handle(rea, &reader_i_hdl); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to get prerequisite reader_i_hdl"); + ret = dds_get_instance_handle(wri, &writer_i_hdl); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to get prerequisite writer_i_hdl"); +} + +static void +fini_entity_status(void) +{ + dds_waitset_detach(waitSetrd, rea); + dds_waitset_detach(waitSetwr, wri); + + dds_delete(waitSetrd); + dds_delete(waitSetwr); + dds_delete(wri); + dds_delete(publisher); + dds_delete(rea); + dds_qos_delete(qos); + dds_delete(top); + dds_delete(subscriber); + dds_delete(participant); +} + +/**************************************************************************** + * Triggering tests + ****************************************************************************/ +Test(ddsc_entity_status, publication_matched, .init=init_entity_status, .fini=fini_entity_status) +{ + /* We're interested in publication matched status. */ + ret = dds_set_enabled_status(wri, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + /* Wait for publication matched status */ + ret = dds_waitset_wait(waitSetwr, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, wsresultsize); + ret = dds_get_publication_matched_status(wri, &publication_matched); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(publication_matched.current_count, 1); + cr_assert_eq(publication_matched.current_count_change, 1); + cr_assert_eq(publication_matched.total_count, 1); + cr_assert_eq(publication_matched.total_count_change, 1); + cr_assert_eq(publication_matched.last_subscription_handle, reader_i_hdl); + + /* Getting the status should have reset the trigger, + * meaning that the wait should timeout. */ + ret = dds_waitset_wait(waitSetwr, wsresults, wsresultsize, shortTimeout); + cr_assert_eq(dds_err_nr(ret), 0, "returned %d", dds_err_nr(ret)); + + /* Un-match the publication by deleting the reader. */ + dds_delete(rea); + + /* Wait for publication matched status */ + ret = dds_waitset_wait(waitSetwr, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, wsresultsize); + ret = dds_get_publication_matched_status(wri, &publication_matched); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(publication_matched.current_count, 0); + cr_assert_eq(publication_matched.current_count_change, -1); + cr_assert_eq(publication_matched.total_count, 1); + cr_assert_eq(publication_matched.total_count_change, 0); + cr_assert_eq(publication_matched.last_subscription_handle, reader_i_hdl); +} + +Test(ddsc_entity_status, subscription_matched, .init=init_entity_status, .fini=fini_entity_status) +{ + /* We're interested in subscription matched status. */ + ret = dds_set_enabled_status(rea, DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + /* Wait for subscription matched status */ + ret = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, wsresultsize); + ret = dds_get_subscription_matched_status(rea, &subscription_matched); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(subscription_matched.current_count, 1); + cr_assert_eq(subscription_matched.current_count_change, 1); + cr_assert_eq(subscription_matched.total_count, 1); + cr_assert_eq(subscription_matched.total_count_change, 1); + cr_assert_eq(subscription_matched.last_publication_handle, writer_i_hdl); + + /* Getting the status should have reset the trigger, + * meaning that the wait should timeout. */ + ret = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, shortTimeout); + cr_assert_eq(dds_err_nr(ret), 0, "returned %d", dds_err_nr(ret)); + + /* Un-match the subscription by deleting the writer. */ + dds_delete(wri); + + /* Wait for subscription matched status */ + ret = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, wsresultsize); + ret = dds_get_subscription_matched_status(rea, &subscription_matched); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(subscription_matched.current_count, 0); + cr_assert_eq(subscription_matched.current_count_change, -1); + cr_assert_eq(subscription_matched.total_count, 1); + cr_assert_eq(subscription_matched.total_count_change, 0); + cr_assert_eq(subscription_matched.last_publication_handle, writer_i_hdl); +} + +Test(ddsc_entity, incompatible_qos, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_entity_t reader2; + dds_requested_incompatible_qos_status_t req_incompatible_qos = {0}; + dds_offered_incompatible_qos_status_t off_incompatible_qos = {0}; + dds_qset_durability (qos, DDS_DURABILITY_PERSISTENT); + + /* Create a reader with persistent durability */ + reader2 = dds_create_reader(participant, top, qos, NULL); + cr_assert_gt(reader2, 0); + ret = dds_waitset_attach (waitSetrd, reader2, reader2); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + /* Get reader and writer status conditions and attach to waitset */ + ret = dds_set_enabled_status(rea, DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + ret = dds_set_enabled_status(reader2, DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + ret = dds_set_enabled_status(wri, DDS_OFFERED_INCOMPATIBLE_QOS_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + /* Wait for subscription requested incompatible status, which should only be + * triggered on reader2. */ + ret = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, 1); + cr_assert_eq(reader2, (dds_entity_t)(intptr_t)wsresults[0]); + + /* Get and check the status. */ + ret = dds_get_requested_incompatible_qos_status (reader2, &req_incompatible_qos); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(req_incompatible_qos.total_count, 1); + cr_assert_eq(req_incompatible_qos.total_count_change, 1); + cr_assert_eq(req_incompatible_qos.last_policy_id, DDS_DURABILITY_QOS_POLICY_ID); + + /*Getting the status should have reset the trigger, waitset should timeout */ + ret = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, shortTimeout); + cr_assert_eq(dds_err_nr(ret), 0, "returned %d", dds_err_nr(ret)); + + /* Wait for offered incompatible QoS status */ + ret = dds_waitset_wait(waitSetwr, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, wsresultsize); + ret = dds_get_offered_incompatible_qos_status (wri, &off_incompatible_qos); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(off_incompatible_qos.total_count, 1); + cr_assert_eq(off_incompatible_qos.total_count_change, 1); + cr_assert_eq(off_incompatible_qos.last_policy_id, DDS_DURABILITY_QOS_POLICY_ID); + + /*Getting the status should have reset the trigger, waitset should timeout */ + ret = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, shortTimeout); + cr_assert_eq(dds_err_nr(ret), 0, "returned %d", dds_err_nr(ret)); + + ret = dds_waitset_detach(waitSetrd, reader2); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + dds_delete(reader2); +} + +Test(ddsc_entity, liveliness_changed, .init=init_entity_status, .fini=fini_entity_status) +{ + uint32_t set_status = 0; + dds_liveliness_changed_status_t liveliness_changed; + + ret = dds_set_enabled_status(rea, DDS_LIVELINESS_CHANGED_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + /* Get the status set */ + ret = dds_get_enabled_status (rea, &set_status); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(set_status, DDS_LIVELINESS_CHANGED_STATUS); + + /* wait for LIVELINESS_CHANGED status */ + ret = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, wsresultsize); + + ret = dds_get_liveliness_changed_status (rea, &liveliness_changed); + cr_assert_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(liveliness_changed.alive_count, 1); + cr_assert_eq(liveliness_changed.alive_count_change, 1); + cr_assert_eq(liveliness_changed.not_alive_count, 0); + cr_assert_eq(liveliness_changed.not_alive_count_change,0); + cr_assert_eq(liveliness_changed.last_publication_handle, writer_i_hdl); + + /*Getting the status should have reset the trigger, waitset should timeout */ + ret = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, shortTimeout); + cr_assert_eq(dds_err_nr(ret), 0, "returned %d", dds_err_nr(ret)); + + /* Reset writer */ + ret = dds_waitset_detach(waitSetwr, wri); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + dds_delete(wri); + + /* wait for LIVELINESS_CHANGED when a writer is deleted */ + ret = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, wsresultsize); + + ret = dds_get_liveliness_changed_status (rea, &liveliness_changed); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(liveliness_changed.alive_count, 0); + cr_assert_eq(liveliness_changed.alive_count_change, 0); + cr_assert_eq(liveliness_changed.not_alive_count, 1); + cr_assert_eq(liveliness_changed.not_alive_count_change,1); + cr_assert_eq(liveliness_changed.last_publication_handle, writer_i_hdl); +} + +Test(ddsc_entity, sample_rejected, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_sample_rejected_status_t sample_rejected = {0}; + + /* Topic instance */ + RoundTripModule_DataType sample = { 0 }; + + ret = dds_set_enabled_status(wri, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + ret = dds_set_enabled_status(rea, DDS_SUBSCRIPTION_MATCHED_STATUS | DDS_SAMPLE_REJECTED_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + /* Wait for subscription matched and publication matched */ + ret = dds_waitset_wait(waitSetwr, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, wsresultsize); + ret = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, wsresultsize); + + ret = dds_set_enabled_status(rea, DDS_SAMPLE_REJECTED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK); + + /* write data - write more than resource limits set by a data reader */ + for (int i = 0; i < 5; i++) + { + ret = dds_write (wri, &sample); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + } + + /* wait for sample rejected status */ + ret = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, wsresultsize); + ret = dds_get_sample_rejected_status (rea, &sample_rejected); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(sample_rejected.total_count, 4); + cr_assert_eq(sample_rejected.total_count_change, 4); + cr_assert_eq(sample_rejected.last_reason, DDS_REJECTED_BY_SAMPLES_LIMIT); + + /*Getting the status should have reset the trigger, waitset should timeout */ + ret = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, shortTimeout); + cr_assert_eq(dds_err_nr(ret), 0, "returned %d", dds_err_nr(ret)); +} + +#if 0 +/* This is basically the same as the Lite test, but inconsistent topic is not triggered. + * That is actually what I would expect, because the code doesn't seem to be the way + * to go to test for inconsistent topic. */ +Test(ddsc_entity, inconsistent_topic) +{ + dds_inconsistent_topic_status_t topic_status; + + top = dds_create_topic(participant, &RoundTripModule_DataType_desc, "RoundTrip1", NULL, NULL); + cr_assert_gt(top, 0, "fails %d", dds_err_nr(top)); + + /*Set reader topic and writer topic statuses enabled*/ + ret = dds_set_enabled_status(top, DDS_INCONSISTENT_TOPIC_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + ret = dds_set_enabled_status(top, DDS_INCONSISTENT_TOPIC_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + /* Wait for pub inconsistent topic status callback */ + ret = dds_waitset_wait(waitSetwr, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, wsresultsize); + ret = dds_get_inconsistent_topic_status (top, &topic_status); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_gt(topic_status.total_count, 0); + + /*Getting the status should have reset the trigger, waitset should timeout */ + status = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, shortTimeout); + cr_assert_eq(dds_err_nr(status), 0, "returned %d", dds_err_nr(status)); + + /* Wait for sub inconsistent topic status callback */ + ret = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(status, wsresultsize); + ret = dds_get_inconsistent_topic_status (top, &topic_status); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_gt(topic_status.total_count, 0); + + /*Getting the status should have reset the trigger, waitset should timeout */ + status = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, shortTimeout); + cr_assert_eq(dds_err_nr(status), 0, "returned %d", dds_err_nr(status)); + + dds_delete(top); +} +#endif + +Test(ddsc_entity, sample_lost, .init=init_entity_status, .fini=fini_entity_status) +{ + + dds_sample_lost_status_t sample_lost = {0}; + dds_time_t time1; + /* Topic instance */ + RoundTripModule_DataType sample = { 0 }; + + ret = dds_set_enabled_status(wri, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + ret = dds_set_enabled_status(rea, DDS_SUBSCRIPTION_MATCHED_STATUS | DDS_SAMPLE_LOST_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + /* Wait for subscription matched and publication matched */ + ret = dds_waitset_wait(waitSetwr, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, wsresultsize); + ret = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, wsresultsize); + + ret = dds_set_enabled_status(rea, DDS_SAMPLE_LOST_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + /* get current time - subtraction ensures that this is truly historic on all platforms. */ + time1 = dds_time () - 1000000; + + /* write a sample with current timestamp */ + ret = dds_write_ts (wri, &sample, dds_time ()); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + /* second sample with older timestamp */ + ret = dds_write_ts (wri, &sample, time1); + + /* wait for sample lost status */ + ret = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, wsresultsize); + ret = dds_get_sample_lost_status (rea, &sample_lost); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(sample_lost.total_count, 1); + cr_assert_eq(sample_lost.total_count_change, 1); + + /*Getting the status should have reset the trigger, waitset should timeout */ + ret = dds_waitset_wait(waitSetrd, wsresults, wsresultsize, shortTimeout); + cr_assert_eq(dds_err_nr(ret), 0, "returned %d", dds_err_nr(ret)); + +} + +Test(ddsc_entity, data_available, .init=init_entity_status, .fini=fini_entity_status) +{ + RoundTripModule_DataType sample = { 0 }; + + ret = dds_set_enabled_status(wri, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + ret = dds_set_enabled_status(rea, DDS_SUBSCRIPTION_MATCHED_STATUS | DDS_DATA_AVAILABLE_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + /* Wait for subscription and publication matched status */ + ret = dds_waitset_wait(waitSetwr, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, wsresultsize); + ret = dds_waitset_wait(waitSetrd, wsresults2, wsresultsize2, waitTimeout); + cr_assert_eq(ret, wsresultsize); + + /* Write the sample */ + ret = dds_write (wri, &sample); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + /* wait for data available */ + ret = dds_take_status(rea, &sta, DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(sta, DDS_SUBSCRIPTION_MATCHED_STATUS); + + ret = dds_waitset_wait(waitSetrd, wsresults2, wsresultsize2, waitTimeout); + cr_assert_eq(ret, wsresultsize); + + ret = dds_get_status_changes (rea, &sta); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + ret = dds_waitset_detach(waitSetrd, rea); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + dds_delete(rea); + + /* Wait for reader to be deleted */ + ret = dds_waitset_wait(waitSetwr, wsresults, wsresultsize, waitTimeout); + cr_assert_neq(ret, 0); +} + +Test(ddsc_entity, all_data_available, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_entity_t reader2; + dds_entity_t waitSetrd2; + dds_sample_info_t info; + + /* Topic instance */ + RoundTripModule_DataType p_sample = { 0 }; + void * s_samples[1]; + RoundTripModule_DataType s_sample; + + memset (&s_sample, 0, sizeof (s_sample)); + s_samples[0] = &s_sample; + + reader2 = dds_create_reader(subscriber, top, NULL, NULL); + cr_assert_gt(reader2, 0); + + ret = dds_set_enabled_status(wri, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + ret = dds_set_enabled_status(rea, DDS_SUBSCRIPTION_MATCHED_STATUS | DDS_DATA_AVAILABLE_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + ret = dds_set_enabled_status(reader2, DDS_SUBSCRIPTION_MATCHED_STATUS | DDS_DATA_AVAILABLE_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + waitSetrd2 = dds_create_waitset(participant); + ret = dds_waitset_attach (waitSetrd2, reader2, reader2); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + /* Wait for publication matched status */ + ret = dds_waitset_wait(waitSetwr, wsresults, wsresultsize, waitTimeout); + cr_assert_eq(ret, wsresultsize); + + /* Wait for subscription matched status on both readers */ + ret = dds_waitset_wait(waitSetrd, wsresults2, wsresultsize2, waitTimeout); + cr_assert_eq(ret, wsresultsize); + ret = dds_waitset_wait(waitSetrd2, wsresults2, wsresultsize2, waitTimeout); + cr_assert_eq(ret, wsresultsize); + + ret = dds_write (wri, &p_sample); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + /* Reset the publication and subscription matched status */ + ret = dds_get_publication_matched_status(wri, NULL); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + ret = dds_take_status (rea, &sta, DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(sta, DDS_SUBSCRIPTION_MATCHED_STATUS); + ret = dds_take_status (reader2, &sta, DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(sta, DDS_SUBSCRIPTION_MATCHED_STATUS); + + /* wait for data */ + ret = dds_waitset_wait(waitSetrd, wsresults2, wsresultsize2, waitTimeout); + cr_assert_neq(ret, 0); + + ret = dds_waitset_wait(waitSetrd2, wsresults2, wsresultsize2, waitTimeout); + cr_assert_neq(ret, 0); + + ret = dds_waitset_detach(waitSetrd, rea); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + ret = dds_waitset_detach(waitSetrd2, reader2); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + ret = dds_delete(waitSetrd2); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + + /* Get DATA_ON_READERS status*/ + ret = dds_get_status_changes (subscriber, &sta); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(sta, DDS_DATA_ON_READERS_STATUS); + + /* Get DATA_AVAILABLE status */ + ret = dds_get_status_changes (rea, &sta); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(sta, DDS_DATA_AVAILABLE_STATUS); + + /* Get DATA_AVAILABLE status */ + ret = dds_get_status_changes (reader2, &sta); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(sta, DDS_DATA_AVAILABLE_STATUS); + + /* Read 1 data sample from reader1 */ + ret = dds_take (rea, s_samples, &info, 1, 1); + cr_assert_eq(ret, 1); + + /* status after taking the data should be reset */ + ret = dds_get_status_changes (rea, &sta); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_neq(sta, ~DDS_DATA_AVAILABLE_STATUS); + + /* status from reader2 */ + ret = dds_get_status_changes (reader2, &sta); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_neq(sta, ~DDS_DATA_AVAILABLE_STATUS); + + /* status from subscriber */ + ret = dds_get_status_changes (subscriber, &sta); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(sta, 0); + + RoundTripModule_DataType_free (&s_sample, DDS_FREE_CONTENTS); + + dds_delete(reader2); + + /* Wait for reader to be deleted */ + ret = dds_waitset_wait(waitSetwr, wsresults, wsresultsize, waitTimeout); + cr_assert_neq(ret, 0); +} + +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_enabled_status, bad_param) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t e), ddsc_get_enabled_status, bad_param) +{ + uint32_t status; + dds_return_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_get_enabled_status(e, &status); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} + +Test(ddsc_get_enabled_status, deleted_reader, .init=init_entity_status, .fini=fini_entity_status) +{ + uint32_t status; + dds_delete(rea); + ret = dds_get_enabled_status(rea, &status); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "dds_get_enabled_status(): returned %d", dds_err_nr(ret)); +} + +Test(ddsc_get_enabled_status, illegal, .init=init_entity_status, .fini=fini_entity_status) +{ + uint32_t status; + ret = dds_get_enabled_status(waitSetrd, &status); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} + +TheoryDataPoints(ddsc_get_enabled_status, status_ok) = { + DataPoints(dds_entity_t *,&rea, &wri, &participant, &top, &publisher, &subscriber), +}; +Theory((dds_entity_t *e), ddsc_get_enabled_status, status_ok, .init=init_entity_status, .fini=fini_entity_status) +{ + uint32_t status; + ret = dds_get_enabled_status (*e, &status); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK, "dds_get_enabled_status(entity, status)"); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_set_enabled_status, bad_param) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t e), ddsc_set_enabled_status, bad_param) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_set_enabled_status(e, 0 /*mask*/); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} + +Test(ddsc_set_enabled_status, deleted_reader, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_delete(rea); + ret = dds_set_enabled_status(rea, 0 /*mask*/); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "dds_set_enabled_status(): returned %d", dds_err_nr(ret)); +} + +Test(ddsc_set_enabled_status, illegal, .init=init_entity_status, .fini=fini_entity_status) +{ + ret = dds_set_enabled_status(waitSetrd, 0); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "dds_set_enabled_status(): returned %d", dds_err_nr(ret)); +} + +TheoryDataPoints(ddsc_set_enabled_status, status_ok) = { + DataPoints(dds_entity_t *,&rea, &wri, &participant, &top, &publisher, &subscriber), +}; +Theory((dds_entity_t *entity), ddsc_set_enabled_status, status_ok, .init=init_entity_status, .fini=fini_entity_status) +{ + ret = dds_set_enabled_status (*entity, 0); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK, "dds_set_enabled_status(entity, mask)"); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_status, bad_param) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t e), ddsc_read_status, bad_param) +{ + uint32_t status; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_read_status(e, &status, 0 /*mask*/); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} + +Test(ddsc_read_status, deleted_reader, .init=init_entity_status, .fini=fini_entity_status) +{ + uint32_t status; + dds_delete(rea); + ret = dds_read_status(rea, &status, 0 /*mask*/); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "dds_read_status(): returned %d", dds_err_nr(ret)); +} + +Test (ddsc_read_status, illegal, .init=init_entity_status, .fini=fini_entity_status) +{ + uint32_t status; + ret = dds_read_status(waitSetrd, &status, 0); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "dds_read_status(): returned %d", dds_err_nr(ret)); +} +TheoryDataPoints(ddsc_read_status, status_ok) = { + DataPoints(dds_entity_t *,&rea, &wri, &participant, &top, &publisher, &subscriber), +}; +Theory((dds_entity_t *e), ddsc_read_status, status_ok, .init=init_entity_status, .fini=fini_entity_status) +{ + uint32_t status; + ret = dds_read_status (*e, &status, 0); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK, "dds_read_status(entity, status, mask)"); +} + +Test (ddsc_read_status, invalid_status_on_reader, .init=init_entity_status, .fini=fini_entity_status) +{ + uint32_t status; + ret = dds_read_status(rea, &status, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "dds_read_status(): returned %d", dds_err_nr(ret)); +} + +Test (ddsc_read_status, invalid_status_on_writer, .init=init_entity_status, .fini=fini_entity_status) +{ + uint32_t status; + ret = dds_read_status(wri, &status, DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "dds_read_status(): returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_status, bad_param) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t e), ddsc_take_status, bad_param) +{ + uint32_t status; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_take_status(e, &status, 0 /*mask*/); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} + +Test(ddsc_take_status, deleted_reader, .init=init_entity_status, .fini=fini_entity_status) +{ + uint32_t status; + dds_delete(rea); + ret = dds_take_status(rea, &status, 0 /*mask*/); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "dds_take_status(): returned %d", dds_err_nr(ret)); +} +Test(ddsc_take_status, illegal, .init=init_entity_status, .fini=fini_entity_status) +{ + uint32_t status; + ret = dds_take_status(waitSetrd, &status, 0); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "dds_take_status(): returned %d", dds_err_nr(ret)); +} + +TheoryDataPoints(ddsc_take_status, status_ok) = { + DataPoints(dds_entity_t *,&rea, &wri, &participant, &top, &publisher, &subscriber), +}; +Theory((dds_entity_t *e), ddsc_take_status, status_ok, .init=init_entity_status, .fini=fini_entity_status) +{ + uint32_t status; + ret = dds_take_status (*e, &status, 0 /*mask*/); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK, "dds_take_status(entity, status, mask)"); +} + +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_status_changes, bad_param) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t e), ddsc_get_status_changes, bad_param) +{ + uint32_t status; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_get_status_changes(e, &status); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} + +Test(ddsc_get_status_changes, deleted_reader, .init=init_entity_status, .fini=fini_entity_status) +{ + uint32_t status; + dds_delete(rea); + ret = dds_get_status_changes(rea, &status); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "dds_get_status_changes(): returned %d", dds_err_nr(ret)); +} + +Test(ddsc_get_status_changes, illegal, .init=init_entity_status, .fini=fini_entity_status) +{ + uint32_t status; + ret = dds_get_status_changes(waitSetrd, &status); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "dds_get_status_changes(): returned %d", dds_err_nr(ret)); +} + +TheoryDataPoints(ddsc_get_status_changes, status_ok) = { + DataPoints(dds_entity_t *,&rea, &wri, &participant, &top, &publisher, &subscriber), +}; +Theory((dds_entity_t *e), ddsc_get_status_changes, status_ok, .init=init_entity_status, .fini=fini_entity_status) +{ + uint32_t status; + ret = dds_get_status_changes (*e, &status); + cr_assert_dds_return_t_eq(ret, DDS_RETCODE_OK, "dds_get_status_changes(entity, status)"); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_triggered, bad_param) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t e), ddsc_triggered, bad_param) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_triggered(e); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "dds_triggered(): returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} + +Test(ddsc_triggered, deleted_reader, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_delete(rea); + ret = dds_triggered(rea); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "dds_triggered(): returned %d", dds_err_nr(ret)); +} + +TheoryDataPoints(ddsc_triggered, status_ok) = { + DataPoints(dds_entity_t *,&rea, &wri, &participant, &top, &publisher, &subscriber, &waitSetrd), +}; +Theory((dds_entity_t *e), ddsc_triggered, status_ok, .init=init_entity_status, .fini=fini_entity_status) +{ + ret = dds_triggered (*e); + cr_assert_geq(ret, 0, "dds_triggered(entity)"); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_inconsistent_topic_status, inconsistent_topic_status, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_inconsistent_topic_status_t inconsistent_topic_status; + ret = dds_get_inconsistent_topic_status(top, &inconsistent_topic_status); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %d", dds_err_nr(ret)); + cr_assert_eq(inconsistent_topic_status.total_count, 0); + cr_assert_eq(inconsistent_topic_status.total_count_change, 0); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_inconsistent_topic_status, bad_params) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t topic), ddsc_get_inconsistent_topic_status, bad_params) +{ + dds_inconsistent_topic_status_t topic_status; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_get_inconsistent_topic_status(topic, &topic_status); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_inconsistent_topic_status, null, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_set_enabled_status(top, 0); + ret = dds_get_inconsistent_topic_status(top, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_inconsistent_topic_status, non_topics) = { + DataPoints(dds_entity_t*, &rea, &wri, &participant), +}; +Theory((dds_entity_t *topic), ddsc_get_inconsistent_topic_status, non_topics, .init=init_entity_status, .fini=fini_entity_status) +{ + ret = dds_get_inconsistent_topic_status(*topic, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_inconsistent_topic_status, deleted_topic, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_delete(top); + ret = dds_get_inconsistent_topic_status(top, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %s", dds_err_str(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_publication_matched_status, bad_params) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t writer), ddsc_get_publication_matched_status, bad_params) +{ + dds_publication_matched_status_t status; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_get_publication_matched_status(writer, &status); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_publication_matched_status, null, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_set_enabled_status(wri, 0); + ret = dds_get_publication_matched_status(wri, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_publication_matched_status, non_writers) = { + DataPoints(dds_entity_t*, &rea, &top, &participant), +}; +Theory((dds_entity_t *writer), ddsc_get_publication_matched_status, non_writers, .init=init_entity_status, .fini=fini_entity_status) +{ + ret = dds_get_publication_matched_status(*writer, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_publication_matched_status, deleted_writer, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_delete(wri); + ret = dds_get_publication_matched_status(wri, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_liveliness_lost_status, liveliness_lost_status, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_liveliness_lost_status_t liveliness_lost_status; + ret = dds_get_liveliness_lost_status(wri, &liveliness_lost_status); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %d", dds_err_nr(ret)); + cr_assert_eq(liveliness_lost_status.total_count, 0); + cr_assert_eq(liveliness_lost_status.total_count_change, 0); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_liveliness_lost_status, bad_params) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t writer), ddsc_get_liveliness_lost_status, bad_params) +{ + dds_liveliness_lost_status_t status; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_get_liveliness_lost_status(writer, &status); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_liveliness_lost_status, null, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_set_enabled_status(wri, 0); + ret = dds_get_liveliness_lost_status(wri, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_liveliness_lost_status, non_writers) = { + DataPoints(dds_entity_t*, &rea, &top, &participant), +}; +Theory((dds_entity_t *writer), ddsc_get_liveliness_lost_status, non_writers, .init=init_entity_status, .fini=fini_entity_status) +{ + ret = dds_get_liveliness_lost_status(*writer, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_liveliness_lost_status, deleted_writer, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_delete(wri); + ret = dds_get_liveliness_lost_status(wri, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_offered_deadline_missed_status, offered_deadline_missed_status, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_offered_deadline_missed_status_t offered_deadline_missed_status; + ret = dds_get_offered_deadline_missed_status(wri, &offered_deadline_missed_status); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %d", dds_err_nr(ret)); + cr_assert_eq(offered_deadline_missed_status.total_count, 0); + cr_assert_eq(offered_deadline_missed_status.total_count_change, 0); + cr_assert_eq(offered_deadline_missed_status.last_instance_handle, 0); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_offered_deadline_missed_status, bad_params) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t writer), ddsc_get_offered_deadline_missed_status, bad_params) +{ + dds_offered_deadline_missed_status_t status; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_get_offered_deadline_missed_status(writer, &status); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_offered_deadline_missed_status, null, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_set_enabled_status(wri, 0); + ret = dds_get_offered_deadline_missed_status(wri, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_offered_deadline_missed_status, non_writers) = { + DataPoints(dds_entity_t*, &rea, &top, &participant), +}; +Theory((dds_entity_t *writer), ddsc_get_offered_deadline_missed_status, non_writers, .init=init_entity_status, .fini=fini_entity_status) +{ + ret = dds_get_offered_deadline_missed_status(*writer, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_offered_deadline_missed_status, deleted_writer, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_delete(wri); + ret = dds_get_offered_deadline_missed_status(wri, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_offered_incompatible_qos_status, bad_params) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t writer), ddsc_get_offered_incompatible_qos_status, bad_params) +{ + dds_offered_incompatible_qos_status_t status; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_get_offered_incompatible_qos_status(writer, &status); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_offered_incompatible_qos_status, null, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_set_enabled_status(wri, 0); + ret = dds_get_offered_incompatible_qos_status(wri, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_offered_incompatible_qos_status, non_writers) = { + DataPoints(dds_entity_t*, &rea, &top, &participant), +}; +Theory((dds_entity_t *writer), ddsc_get_offered_incompatible_qos_status, non_writers, .init=init_entity_status, .fini=fini_entity_status) +{ + ret = dds_get_offered_incompatible_qos_status(*writer, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_offered_incompatible_qos_status, deleted_writer, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_delete(wri); + ret = dds_get_offered_incompatible_qos_status(wri, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_subscription_matched_status, bad_params) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t reader), ddsc_get_subscription_matched_status, bad_params) +{ + dds_subscription_matched_status_t status; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_get_subscription_matched_status(reader, &status); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_subscription_matched_status, null, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_set_enabled_status(rea, 0); + ret = dds_get_subscription_matched_status(rea, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %s", dds_err_str(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_subscription_matched_status, non_readers) = { + DataPoints(dds_entity_t*, &wri, &top, &participant), +}; +Theory((dds_entity_t *reader), ddsc_get_subscription_matched_status, non_readers, .init=init_entity_status, .fini=fini_entity_status) +{ + ret = dds_get_subscription_matched_status(*reader, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_subscription_matched_status, deleted_reader, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_delete(rea); + ret = dds_get_subscription_matched_status(rea, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_liveliness_changed_status, bad_params) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t reader), ddsc_get_liveliness_changed_status, bad_params) +{ + dds_liveliness_changed_status_t status; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_get_liveliness_changed_status(reader, &status); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_liveliness_changed_status, null, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_set_enabled_status(rea, 0); + ret = dds_get_liveliness_changed_status(rea, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_liveliness_changed_status, non_readers) = { + DataPoints(dds_entity_t*, &wri, &top, &participant), +}; +Theory((dds_entity_t *reader), ddsc_get_liveliness_changed_status, non_readers, .init=init_entity_status, .fini=fini_entity_status) +{ + ret = dds_get_liveliness_changed_status(*reader, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_liveliness_changed_status, deleted_reader, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_delete(rea); + ret = dds_get_liveliness_changed_status(rea, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_sample_rejected_status, bad_params) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t reader), ddsc_get_sample_rejected_status, bad_params) +{ + dds_sample_rejected_status_t status; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_get_sample_rejected_status(reader, &status); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_sample_rejected_status, null, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_set_enabled_status(rea, 0); + ret = dds_get_sample_rejected_status(rea, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_sample_rejected_status, non_readers) = { + DataPoints(dds_entity_t*, &wri, &top, &participant), +}; +Theory((dds_entity_t *reader), ddsc_get_sample_rejected_status, non_readers, .init=init_entity_status, .fini=fini_entity_status) +{ + ret = dds_get_sample_rejected_status(*reader, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_sample_rejected_status, deleted_reader, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_delete(rea); + ret = dds_get_sample_rejected_status(rea, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_sample_lost_status, bad_params) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t reader), ddsc_get_sample_lost_status, bad_params) +{ + dds_sample_lost_status_t status; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_get_sample_lost_status(reader, &status); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_sample_lost_status, null, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_set_enabled_status(rea, 0); + ret = dds_get_sample_lost_status(rea, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_sample_lost_status, non_readers) = { + DataPoints(dds_entity_t*, &wri, &top, &participant), +}; +Theory((dds_entity_t *reader), ddsc_get_sample_lost_status, non_readers, .init=init_entity_status, .fini=fini_entity_status) +{ + ret = dds_get_sample_lost_status(*reader, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_sample_lost_status, deleted_reader, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_delete(rea); + ret = dds_get_sample_lost_status(rea, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_requested_deadline_missed_status, requested_deadline_missed_status, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_requested_deadline_missed_status_t requested_deadline_missed_status; + ret = dds_get_requested_deadline_missed_status(rea, &requested_deadline_missed_status); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %d", dds_err_nr(ret)); + cr_assert_eq(requested_deadline_missed_status.total_count, 0); + cr_assert_eq(requested_deadline_missed_status.total_count_change, 0); + cr_assert_eq(requested_deadline_missed_status.last_instance_handle, DDS_HANDLE_NIL); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_requested_deadline_missed_status, bad_params) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t reader), ddsc_get_requested_deadline_missed_status, bad_params) +{ + dds_requested_deadline_missed_status_t status; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_get_requested_deadline_missed_status(reader, &status); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_requested_deadline_missed_status, null, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_set_enabled_status(rea, 0); + ret = dds_get_requested_deadline_missed_status(rea, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_requested_deadline_missed_status, non_readers) = { + DataPoints(dds_entity_t*, &wri, &top, &participant), +}; +Theory((dds_entity_t *reader), ddsc_get_requested_deadline_missed_status, non_readers, .init=init_entity_status, .fini=fini_entity_status) +{ + ret = dds_get_requested_deadline_missed_status(*reader, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_requested_deadline_missed_status, deleted_reader, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_delete(rea); + ret = dds_get_requested_deadline_missed_status(rea, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_requested_incompatible_qos_status, bad_params) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t reader), ddsc_get_requested_incompatible_qos_status, bad_params) +{ + dds_requested_incompatible_qos_status_t status; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + + ret = dds_get_requested_incompatible_qos_status(reader, &status); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_requested_incompatible_qos_status, null, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_set_enabled_status(rea, 0); + ret = dds_get_requested_incompatible_qos_status(rea, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_get_requested_incompatible_qos_status, non_readers) = { + DataPoints(dds_entity_t*, &wri, &top, &participant), +}; +Theory((dds_entity_t *reader), ddsc_get_requested_incompatible_qos_status, non_readers, .init=init_entity_status, .fini=fini_entity_status) +{ + ret = dds_get_requested_incompatible_qos_status(*reader, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_get_requested_incompatible_qos_status, deleted_reader, .init=init_entity_status, .fini=fini_entity_status) +{ + dds_delete(rea); + ret = dds_get_requested_incompatible_qos_status(rea, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ diff --git a/src/core/ddsc/tests/err.c b/src/core/ddsc/tests/err.c new file mode 100644 index 0000000..4994294 --- /dev/null +++ b/src/core/ddsc/tests/err.c @@ -0,0 +1,33 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include +#include + +Test(ddsc_err, str) +{ + cr_assert_str_eq(dds_err_str(1 ), "Success"); + cr_assert_str_eq(dds_err_str(-255 ), "Unknown"); + cr_assert_str_eq(dds_err_str(DDS_RETCODE_OK * -1), "Success"); + cr_assert_str_eq(dds_err_str(DDS_RETCODE_ERROR * -1), "Error"); + cr_assert_str_eq(dds_err_str(DDS_RETCODE_UNSUPPORTED * -1), "Unsupported"); + cr_assert_str_eq(dds_err_str(DDS_RETCODE_BAD_PARAMETER * -1), "Bad Parameter"); + cr_assert_str_eq(dds_err_str(DDS_RETCODE_PRECONDITION_NOT_MET * -1), "Precondition Not Met"); + cr_assert_str_eq(dds_err_str(DDS_RETCODE_OUT_OF_RESOURCES * -1), "Out Of Resources"); + cr_assert_str_eq(dds_err_str(DDS_RETCODE_NOT_ENABLED * -1), "Not Enabled"); + cr_assert_str_eq(dds_err_str(DDS_RETCODE_IMMUTABLE_POLICY * -1), "Immutable Policy"); + cr_assert_str_eq(dds_err_str(DDS_RETCODE_INCONSISTENT_POLICY * -1), "Inconsistent Policy"); + cr_assert_str_eq(dds_err_str(DDS_RETCODE_ALREADY_DELETED * -1), "Already Deleted"); + cr_assert_str_eq(dds_err_str(DDS_RETCODE_TIMEOUT * -1), "Timeout"); + cr_assert_str_eq(dds_err_str(DDS_RETCODE_NO_DATA * -1), "No Data"); + cr_assert_str_eq(dds_err_str(DDS_RETCODE_ILLEGAL_OPERATION * -1), "Illegal Operation"); +} diff --git a/src/core/ddsc/tests/file_id.c b/src/core/ddsc/tests/file_id.c new file mode 100644 index 0000000..d672aff --- /dev/null +++ b/src/core/ddsc/tests/file_id.c @@ -0,0 +1,39 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include +#include +#include "os/os.h" + +Test(ddsc_err, unique_file_id) +{ + dds_entity_t participant, reader, writer; + + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0); + + /* Disable SAL warning on intentional misuse of the API */ + OS_WARNING_MSVC_OFF(28020); + reader = dds_create_reader(0, 0, NULL, NULL); + cr_assert_lt(reader, 0); + + writer = dds_create_writer(0, 0, NULL, NULL); + cr_assert_lt(writer, 0); + + OS_WARNING_MSVC_ON(28020); + cr_log_info("file_id for dds_create_reader: %d", dds_err_file_id(reader)); + cr_log_info("file_id for dds_create_writer: %d", dds_err_file_id(writer)); + + cr_assert_neq(dds_err_file_id(reader), dds_err_file_id(writer)); + + dds_delete(participant); +} diff --git a/src/core/ddsc/tests/listener.c b/src/core/ddsc/tests/listener.c new file mode 100644 index 0000000..a45b809 --- /dev/null +++ b/src/core/ddsc/tests/listener.c @@ -0,0 +1,1044 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include "os/os.h" +#include "RoundTrip.h" +#include +#include + +/**************************************************************************** + * TODO: (CHAM-279) Add DDS_INCONSISTENT_TOPIC_STATUS test + * TODO: (CHAM-277) Add DDS_OFFERED/REQUESTED_DEADLINE_MISSED_STATUS test + * TODO: (CHAM-278) Add DDS_LIVELINESS_LOST_STATUS test + * TODO: Check DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS intermittent fail (total_count != 1) + ****************************************************************************/ + + + +/**************************************************************************** + * Convenience test macros. + ****************************************************************************/ +#define ASSERT_CALLBACK_EQUAL(fntype, listener, expected) \ + do { \ + dds_on_##fntype##_fn cb; \ + dds_lget_##fntype(listener, &cb); \ + cr_expect_eq(cb, expected, "Callback 'on_" #fntype "' matched expected value '" #expected "'"); \ + } while (0) + +#define STR(fntype) #fntype##_cb + +#define TEST_GET_SET(listener, fntype, cb) \ + do { \ + dds_on_##fntype##_fn dummy = NULL; \ + /* Initially expect DDS_LUNSET on a newly created listener */ \ + ASSERT_CALLBACK_EQUAL(fntype, listener, DDS_LUNSET); \ + /* Using listener or callback NULL, shouldn't crash and be noop */ \ + dds_lset_##fntype(NULL, NULL); \ + dds_lget_##fntype(NULL, NULL); \ + dds_lget_##fntype(listener, NULL); \ + dds_lget_##fntype(NULL, &dummy); \ + cr_expect_eq(dummy, NULL, "lget 'on_" #fntype "' with NULL listener was not a noop"); \ + /* Set to NULL, get to confirm it succeeds */ \ + dds_lset_##fntype(listener, NULL); \ + ASSERT_CALLBACK_EQUAL(fntype, listener, NULL); \ + /* Set to a proper cb method, get to confirm it succeeds */ \ + dds_lset_##fntype(listener, cb); \ + ASSERT_CALLBACK_EQUAL(fntype, listener, cb); \ + } while (0) + + + +/**************************************************************************** + * Test globals. + ****************************************************************************/ +static dds_entity_t g_participant = 0; +static dds_entity_t g_subscriber = 0; +static dds_entity_t g_publisher = 0; +static dds_entity_t g_topic = 0; +static dds_entity_t g_writer = 0; +static dds_entity_t g_reader = 0; + +static dds_listener_t *g_listener = NULL; +static dds_qos_t *g_qos = NULL; +static os_mutex g_mutex; +static os_cond g_cond; + + + +/**************************************************************************** + * Callback stuff. + ****************************************************************************/ +static uint32_t cb_called = 0; +static dds_entity_t cb_topic = 0; +static dds_entity_t cb_writer = 0; +static dds_entity_t cb_reader = 0; +static dds_entity_t cb_subscriber = 0; + +static dds_inconsistent_topic_status_t cb_inconsistent_topic_status = { 0 }; +static dds_liveliness_lost_status_t cb_liveliness_lost_status = { 0 }; +static dds_offered_deadline_missed_status_t cb_offered_deadline_missed_status = { 0 }; +static dds_offered_incompatible_qos_status_t cb_offered_incompatible_qos_status = { 0 }; +static dds_sample_lost_status_t cb_sample_lost_status = { 0 }; +static dds_sample_rejected_status_t cb_sample_rejected_status = { 0 }; +static dds_liveliness_changed_status_t cb_liveliness_changed_status = { 0 }; +static dds_requested_deadline_missed_status_t cb_requested_deadline_missed_status = { 0 }; +static dds_requested_incompatible_qos_status_t cb_requested_incompatible_qos_status= { 0 }; +static dds_publication_matched_status_t cb_publication_matched_status = { 0 }; +static dds_subscription_matched_status_t cb_subscription_matched_status = { 0 }; + + +static void +inconsistent_topic_cb( + dds_entity_t topic, + const dds_inconsistent_topic_status_t status, void* arg) +{ + os_mutexLock(&g_mutex); + cb_topic = topic; + cb_inconsistent_topic_status = status; + cb_called |= DDS_INCONSISTENT_TOPIC_STATUS; + os_condBroadcast(&g_cond); + os_mutexUnlock(&g_mutex); +} + +static void +liveliness_lost_cb( + dds_entity_t writer, + const dds_liveliness_lost_status_t status, + void* arg) +{ + os_mutexLock(&g_mutex); + cb_writer = writer; + cb_liveliness_lost_status = status; + cb_called |= DDS_LIVELINESS_LOST_STATUS; + os_condBroadcast(&g_cond); + os_mutexUnlock(&g_mutex); +} + +static void +offered_deadline_missed_cb( + dds_entity_t writer, + const dds_offered_deadline_missed_status_t status, + void* arg) +{ + os_mutexLock(&g_mutex); + cb_writer = writer; + cb_offered_deadline_missed_status = status; + cb_called |= DDS_OFFERED_DEADLINE_MISSED_STATUS; + os_condBroadcast(&g_cond); + os_mutexUnlock(&g_mutex); +} + +static void +offered_incompatible_qos_cb( + dds_entity_t writer, + const dds_offered_incompatible_qos_status_t status, + void* arg) +{ + os_mutexLock(&g_mutex); + cb_writer = writer; + cb_offered_incompatible_qos_status = status; + cb_called |= DDS_OFFERED_INCOMPATIBLE_QOS_STATUS; + os_condBroadcast(&g_cond); + os_mutexUnlock(&g_mutex); +} + +static void +data_on_readers_cb( + dds_entity_t subscriber, + void* arg) +{ + os_mutexLock(&g_mutex); + cb_subscriber = subscriber; + cb_called |= DDS_DATA_ON_READERS_STATUS; + os_condBroadcast(&g_cond); + os_mutexUnlock(&g_mutex); +} + +static void +sample_lost_cb( + dds_entity_t reader, + const dds_sample_lost_status_t status, + void* arg) +{ + os_mutexLock(&g_mutex); + cb_reader = reader; + cb_sample_lost_status = status; + cb_called |= DDS_SAMPLE_LOST_STATUS; + os_condBroadcast(&g_cond); + os_mutexUnlock(&g_mutex); +} + +static void +data_available_cb( + dds_entity_t reader, + void* arg) +{ + os_mutexLock(&g_mutex); + cb_reader = reader; + cb_called |= DDS_DATA_AVAILABLE_STATUS; + os_condBroadcast(&g_cond); + os_mutexUnlock(&g_mutex); +} + +static void +sample_rejected_cb( + dds_entity_t reader, + const dds_sample_rejected_status_t status, + void* arg) +{ + os_mutexLock(&g_mutex); + cb_reader = reader; + cb_sample_rejected_status = status; + cb_called |= DDS_SAMPLE_REJECTED_STATUS; + os_condBroadcast(&g_cond); + os_mutexUnlock(&g_mutex); +} + +static void +liveliness_changed_cb( + dds_entity_t reader, + const dds_liveliness_changed_status_t status, + void* arg) +{ + os_mutexLock(&g_mutex); + cb_reader = reader; + cb_liveliness_changed_status = status; + cb_called |= DDS_LIVELINESS_CHANGED_STATUS; + os_condBroadcast(&g_cond); + os_mutexUnlock(&g_mutex); +} + +static void +requested_deadline_missed_cb( + dds_entity_t reader, + const dds_requested_deadline_missed_status_t status, + void* arg) +{ + os_mutexLock(&g_mutex); + cb_reader = reader; + cb_requested_deadline_missed_status = status; + cb_called |= DDS_REQUESTED_DEADLINE_MISSED_STATUS; + os_condBroadcast(&g_cond); + os_mutexUnlock(&g_mutex); +} + +static void +requested_incompatible_qos_cb( + dds_entity_t reader, + const dds_requested_incompatible_qos_status_t status, + void* arg) +{ + os_mutexLock(&g_mutex); + cb_reader = reader; + cb_requested_incompatible_qos_status = status; + cb_called |= DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS; + os_condBroadcast(&g_cond); + os_mutexUnlock(&g_mutex); +} + +static void +publication_matched_cb( + dds_entity_t writer, + const dds_publication_matched_status_t status, + void* arg) +{ + os_mutexLock(&g_mutex); + cb_writer = writer; + cb_publication_matched_status = status; + cb_called |= DDS_PUBLICATION_MATCHED_STATUS; + os_condBroadcast(&g_cond); + os_mutexUnlock(&g_mutex); +} + +static void +subscription_matched_cb( + dds_entity_t reader, + const dds_subscription_matched_status_t status, + void* arg) +{ + os_mutexLock(&g_mutex); + cb_reader = reader; + cb_subscription_matched_status = status; + cb_called |= DDS_SUBSCRIPTION_MATCHED_STATUS; + os_condBroadcast(&g_cond); + os_mutexUnlock(&g_mutex); +} + +static void +callback_dummy(void) +{ +} + +static uint32_t +waitfor_cb(uint32_t expected) +{ + os_time timeout = { 5, 0 }; + os_result osr = os_resultSuccess; + os_mutexLock(&g_mutex); + while (((cb_called & expected) != expected) && (osr == os_resultSuccess)) { + osr = os_condTimedWait(&g_cond, &g_mutex, &timeout); + } + os_mutexUnlock(&g_mutex); + return cb_called; +} + + + +/**************************************************************************** + * Test initializations and teardowns. + ****************************************************************************/ +static char* +create_topic_name(const char *prefix, char *name, size_t size) +{ + /* Get semi random g_topic name. */ + os_procId pid = os_procIdSelf(); + uintmax_t tid = os_threadIdToInteger(os_threadIdSelf()); + (void) snprintf(name, size, "%s_pid%"PRIprocId"_tid%"PRIuMAX"", prefix, pid, tid); + return name; +} + +static void +init_triggering_base(void) +{ + char name[100]; + + os_osInit(); + + os_mutexInit(&g_mutex); + os_condInit(&g_cond, &g_mutex); + + g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_participant, 0, "Failed to create prerequisite g_participant"); + + g_subscriber = dds_create_subscriber(g_participant, NULL, NULL); + cr_assert_gt(g_subscriber, 0, "Failed to create prerequisite g_subscriber"); + + g_publisher = dds_create_publisher(g_participant, NULL, NULL); + cr_assert_gt(g_publisher, 0, "Failed to create prerequisite g_publisher"); + + g_topic = dds_create_topic(g_participant, &RoundTripModule_DataType_desc, create_topic_name("ddsc_listener_test", name, 100), NULL, NULL); + cr_assert_gt(g_topic, 0, "Failed to create prerequisite g_topic"); + + g_listener = dds_listener_create(NULL); + cr_assert_not_null(g_listener, "Failed to create prerequisite g_listener"); + + g_qos = dds_qos_create(); + cr_assert_not_null(g_qos, "Failed to create prerequisite g_qos"); + dds_qset_reliability(g_qos, DDS_RELIABILITY_RELIABLE, DDS_SECS(1)); + dds_qset_history(g_qos, DDS_HISTORY_KEEP_ALL, 0); +} + +static void +init_triggering_test(void) +{ + uint32_t triggered; + + /* Initialize base. */ + init_triggering_base(); + + /* Set QoS Policies that'll help us test various status callbacks. */ + dds_qset_destination_order(g_qos, DDS_DESTINATIONORDER_BY_SOURCE_TIMESTAMP); + dds_qset_reliability(g_qos, DDS_RELIABILITY_BEST_EFFORT, DDS_MSECS(100)); + dds_qset_resource_limits(g_qos, 1, 1, 1); + + /* Use these to be sure reader and writer know each other. */ + dds_lset_publication_matched(g_listener, publication_matched_cb); + dds_lset_subscription_matched(g_listener, subscription_matched_cb); + dds_lset_liveliness_changed(g_listener, liveliness_changed_cb); + + /* Create reader and writer with proper listeners. */ + g_writer = dds_create_writer(g_publisher, g_topic, g_qos, g_listener); + cr_assert_gt(g_writer, 0, "Failed to create prerequisite writer"); + g_reader = dds_create_reader(g_subscriber, g_topic, g_qos, g_listener); + cr_assert_gt(g_reader, 0, "Failed to create prerequisite reader"); + + /* Sync. */ + triggered = waitfor_cb(DDS_PUBLICATION_MATCHED_STATUS | DDS_SUBSCRIPTION_MATCHED_STATUS | DDS_LIVELINESS_CHANGED_STATUS); + cr_assert_eq(triggered & DDS_LIVELINESS_CHANGED_STATUS, DDS_LIVELINESS_CHANGED_STATUS); + cr_assert_eq(triggered & DDS_PUBLICATION_MATCHED_STATUS, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_eq(triggered & DDS_SUBSCRIPTION_MATCHED_STATUS, DDS_SUBSCRIPTION_MATCHED_STATUS); +} + +static void +fini_triggering_base(void) +{ + dds_qos_delete(g_qos); + dds_listener_delete(g_listener); + dds_delete(g_participant); + os_condDestroy(&g_cond); + os_mutexDestroy(&g_mutex); + os_osExit(); +} + +static void +fini_triggering_test(void) +{ + dds_delete(g_reader); + dds_delete(g_writer); + fini_triggering_base(); +} + + +#if 0 +#else +/**************************************************************************** + * API tests + ****************************************************************************/ +Test(ddsc_listener, create_and_delete) +{ + /* Verify create doesn't return null */ + dds_listener_t *listener; + listener = dds_listener_create(NULL); + cr_assert_not_null(listener); + + /* Check default cb's are set */ + ASSERT_CALLBACK_EQUAL(inconsistent_topic, listener, DDS_LUNSET); + ASSERT_CALLBACK_EQUAL(liveliness_lost, listener, DDS_LUNSET); + ASSERT_CALLBACK_EQUAL(offered_deadline_missed, listener, DDS_LUNSET); + ASSERT_CALLBACK_EQUAL(offered_incompatible_qos, listener, DDS_LUNSET); + ASSERT_CALLBACK_EQUAL(data_on_readers, listener, DDS_LUNSET); + ASSERT_CALLBACK_EQUAL(sample_lost, listener, DDS_LUNSET); + ASSERT_CALLBACK_EQUAL(sample_rejected, listener, DDS_LUNSET); + ASSERT_CALLBACK_EQUAL(liveliness_changed, listener, DDS_LUNSET); + ASSERT_CALLBACK_EQUAL(requested_deadline_missed, listener, DDS_LUNSET); + ASSERT_CALLBACK_EQUAL(requested_incompatible_qos, listener, DDS_LUNSET); + ASSERT_CALLBACK_EQUAL(publication_matched, listener, DDS_LUNSET); + ASSERT_CALLBACK_EQUAL(subscription_matched, listener, DDS_LUNSET); + ASSERT_CALLBACK_EQUAL(data_available, listener, DDS_LUNSET); + + dds_listener_delete(listener); + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + dds_listener_delete(NULL); + OS_WARNING_MSVC_ON(6387); +} + +Test(ddsc_listener, reset) +{ + dds_listener_t *listener; + listener = dds_listener_create(NULL); + cr_assert_not_null(listener); + + /* Set a listener cb to a non-default value */ + dds_lset_data_available(listener, NULL); + ASSERT_CALLBACK_EQUAL(data_available, listener, NULL); + + /* Listener cb should revert to default after reset */ + dds_listener_reset(listener); + ASSERT_CALLBACK_EQUAL(data_available, listener, DDS_LUNSET); + + /* Resetting a NULL listener should not crash */ + dds_listener_reset(NULL); + + dds_listener_delete(listener); +} + +Test(ddsc_listener, copy) +{ + dds_listener_t *listener1 = NULL, *listener2 = NULL; + listener1 = dds_listener_create(NULL); + listener2 = dds_listener_create(NULL); + cr_assert_not_null(listener1); + cr_assert_not_null(listener2); + + /* Set some listener1 callbacks to non-default values */ + dds_lset_data_available(listener1, NULL); + dds_lset_sample_lost(listener1, sample_lost_cb); + ASSERT_CALLBACK_EQUAL(data_available, listener1, NULL); + ASSERT_CALLBACK_EQUAL(sample_lost, listener1, sample_lost_cb); + ASSERT_CALLBACK_EQUAL(data_available, listener2, DDS_LUNSET); + ASSERT_CALLBACK_EQUAL(sample_lost, listener2, DDS_LUNSET); + + /* Cb's should be copied to listener2 */ + dds_listener_copy(listener2, listener1); + ASSERT_CALLBACK_EQUAL(data_available, listener1, NULL); + ASSERT_CALLBACK_EQUAL(data_available, listener2, NULL); + ASSERT_CALLBACK_EQUAL(sample_lost, listener1, sample_lost_cb); + ASSERT_CALLBACK_EQUAL(sample_lost, listener2, sample_lost_cb); + + /* Calling copy with NULL should not crash and be noops. */ + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + dds_listener_copy(listener2, NULL); + dds_listener_copy(NULL, listener1); + dds_listener_copy(NULL, NULL); + OS_WARNING_MSVC_ON(6387); + + dds_listener_delete(listener1); + dds_listener_delete(listener2); +} + +Test(ddsc_listener, merge) +{ + dds_listener_t *listener1 = NULL, *listener2 = NULL; + listener1 = dds_listener_create(NULL); + listener2 = dds_listener_create(NULL); + cr_assert_not_null(listener1); + cr_assert_not_null(listener2); + + /* Set all listener1 callbacks to non-default values */ + dds_lset_inconsistent_topic (listener1, inconsistent_topic_cb); + dds_lset_liveliness_lost (listener1, liveliness_lost_cb); + dds_lset_offered_deadline_missed (listener1, offered_deadline_missed_cb); + dds_lset_offered_incompatible_qos (listener1, offered_incompatible_qos_cb); + dds_lset_data_on_readers (listener1, data_on_readers_cb); + dds_lset_sample_lost (listener1, sample_lost_cb); + dds_lset_data_available (listener1, data_available_cb); + dds_lset_sample_rejected (listener1, sample_rejected_cb); + dds_lset_liveliness_changed (listener1, liveliness_changed_cb); + dds_lset_requested_deadline_missed (listener1, requested_deadline_missed_cb); + dds_lset_requested_incompatible_qos (listener1, requested_incompatible_qos_cb); + dds_lset_publication_matched (listener1, publication_matched_cb); + dds_lset_subscription_matched (listener1, subscription_matched_cb); + + /* Merging listener1 into empty listener2 should act a bit like a copy. */ + dds_listener_merge(listener2, listener1); + ASSERT_CALLBACK_EQUAL(inconsistent_topic, listener2, inconsistent_topic_cb); + ASSERT_CALLBACK_EQUAL(liveliness_lost, listener2, liveliness_lost_cb); + ASSERT_CALLBACK_EQUAL(offered_deadline_missed, listener2, offered_deadline_missed_cb); + ASSERT_CALLBACK_EQUAL(offered_incompatible_qos, listener2, offered_incompatible_qos_cb); + ASSERT_CALLBACK_EQUAL(data_on_readers, listener2, data_on_readers_cb); + ASSERT_CALLBACK_EQUAL(sample_lost, listener2, sample_lost_cb); + ASSERT_CALLBACK_EQUAL(data_available, listener2, data_available_cb); + ASSERT_CALLBACK_EQUAL(sample_rejected, listener2, sample_rejected_cb); + ASSERT_CALLBACK_EQUAL(liveliness_changed, listener2, liveliness_changed_cb); + ASSERT_CALLBACK_EQUAL(requested_deadline_missed, listener2, requested_deadline_missed_cb); + ASSERT_CALLBACK_EQUAL(requested_incompatible_qos, listener2, requested_incompatible_qos_cb); + ASSERT_CALLBACK_EQUAL(publication_matched, listener2, publication_matched_cb); + ASSERT_CALLBACK_EQUAL(subscription_matched, listener2, subscription_matched_cb); + + /* Merging listener into a full listener2 should act as a noop. */ + dds_lset_inconsistent_topic (listener2, (dds_on_inconsistent_topic_fn)callback_dummy); + dds_lset_liveliness_lost (listener2, (dds_on_liveliness_lost_fn)callback_dummy); + dds_lset_offered_deadline_missed (listener2, (dds_on_offered_deadline_missed_fn)callback_dummy); + dds_lset_offered_incompatible_qos (listener2, (dds_on_offered_incompatible_qos_fn)callback_dummy); + dds_lset_data_on_readers (listener2, (dds_on_data_on_readers_fn)callback_dummy); + dds_lset_sample_lost (listener2, (dds_on_sample_lost_fn)callback_dummy); + dds_lset_data_available (listener2, (dds_on_data_available_fn)callback_dummy); + dds_lset_sample_rejected (listener2, (dds_on_sample_rejected_fn)callback_dummy); + dds_lset_liveliness_changed (listener2, (dds_on_liveliness_changed_fn)callback_dummy); + dds_lset_requested_deadline_missed (listener2, (dds_on_requested_deadline_missed_fn)callback_dummy); + dds_lset_requested_incompatible_qos (listener2, (dds_on_requested_incompatible_qos_fn)callback_dummy); + dds_lset_publication_matched (listener2, (dds_on_publication_matched_fn)callback_dummy); + dds_lset_subscription_matched (listener2, (dds_on_subscription_matched_fn)callback_dummy); + dds_listener_merge(listener2, listener1); + ASSERT_CALLBACK_EQUAL(inconsistent_topic, listener2, (dds_on_inconsistent_topic_fn)callback_dummy); + ASSERT_CALLBACK_EQUAL(liveliness_lost, listener2, (dds_on_liveliness_lost_fn)callback_dummy); + ASSERT_CALLBACK_EQUAL(offered_deadline_missed, listener2, (dds_on_offered_deadline_missed_fn)callback_dummy); + ASSERT_CALLBACK_EQUAL(offered_incompatible_qos, listener2, (dds_on_offered_incompatible_qos_fn)callback_dummy); + ASSERT_CALLBACK_EQUAL(data_on_readers, listener2, (dds_on_data_on_readers_fn)callback_dummy); + ASSERT_CALLBACK_EQUAL(sample_lost, listener2, (dds_on_sample_lost_fn)callback_dummy); + ASSERT_CALLBACK_EQUAL(data_available, listener2, (dds_on_data_available_fn)callback_dummy); + ASSERT_CALLBACK_EQUAL(sample_rejected, listener2, (dds_on_sample_rejected_fn)callback_dummy); + ASSERT_CALLBACK_EQUAL(liveliness_changed, listener2, (dds_on_liveliness_changed_fn)callback_dummy); + ASSERT_CALLBACK_EQUAL(requested_deadline_missed, listener2, (dds_on_requested_deadline_missed_fn)callback_dummy); + ASSERT_CALLBACK_EQUAL(requested_incompatible_qos, listener2, (dds_on_requested_incompatible_qos_fn)callback_dummy); + ASSERT_CALLBACK_EQUAL(publication_matched, listener2, (dds_on_publication_matched_fn)callback_dummy); + ASSERT_CALLBACK_EQUAL(subscription_matched, listener2, (dds_on_subscription_matched_fn)callback_dummy); + + /* Using NULLs shouldn't crash and be noops. */ + dds_listener_merge(listener2, NULL); + dds_listener_merge(NULL, listener1); + dds_listener_merge(NULL, NULL); + + dds_listener_delete(listener1); + dds_listener_delete(listener2); +} + +Test(ddsc_listener, getters_setters) +{ + /* test all individual cb get/set methods */ + dds_listener_t *listener = dds_listener_create(NULL); + cr_assert_not_null(listener); + + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ \ + TEST_GET_SET(listener, inconsistent_topic, inconsistent_topic_cb); + TEST_GET_SET(listener, liveliness_lost, liveliness_lost_cb); + TEST_GET_SET(listener, offered_deadline_missed, offered_deadline_missed_cb); + TEST_GET_SET(listener, offered_incompatible_qos, offered_incompatible_qos_cb); + TEST_GET_SET(listener, data_on_readers, data_on_readers_cb); + TEST_GET_SET(listener, sample_lost, sample_lost_cb); + TEST_GET_SET(listener, sample_rejected, sample_rejected_cb); + TEST_GET_SET(listener, liveliness_changed, liveliness_changed_cb); + TEST_GET_SET(listener, requested_deadline_missed, requested_deadline_missed_cb); + TEST_GET_SET(listener, requested_incompatible_qos, requested_incompatible_qos_cb); + TEST_GET_SET(listener, publication_matched, publication_matched_cb); + TEST_GET_SET(listener, subscription_matched, subscription_matched_cb); + TEST_GET_SET(listener, data_available, data_available_cb); + OS_WARNING_MSVC_ON(6387); + + dds_listener_delete(listener); +} + + + +/**************************************************************************** + * Triggering tests + ****************************************************************************/ +Test(ddsc_listener, propagation, .init=init_triggering_base, .fini=fini_triggering_base) +{ + RoundTripModule_DataType sample = { 0 }; + dds_listener_t *listener_par = NULL; + dds_listener_t *listener_pub = NULL; + dds_listener_t *listener_sub = NULL; + uint32_t triggered; + dds_return_t ret; + + /* Let participant be interested in data. */ + listener_par = dds_listener_create(NULL); + cr_assert_not_null(listener_par, "Failed to create prerequisite listener_par"); + dds_lset_data_on_readers(listener_par, data_on_readers_cb); + ret = dds_set_listener(g_participant, listener_par); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite listener_par"); + dds_listener_delete(listener_par); + + /* Let publisher be interested in publication matched. */ + listener_pub = dds_listener_create(NULL); + cr_assert_not_null(listener_pub, "Failed to create prerequisite listener_pub"); + dds_lset_publication_matched(listener_pub, publication_matched_cb); + ret = dds_set_listener(g_publisher, listener_pub); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite listener_pub"); + dds_listener_delete(listener_pub); + + /* Let subscriber be interested in subscription matched. */ + listener_sub = dds_listener_create(NULL); + cr_assert_not_null(listener_pub, "Failed to create prerequisite listener_sub"); + dds_lset_subscription_matched(listener_sub, subscription_matched_cb); + ret = dds_set_listener(g_subscriber, listener_sub); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite listener_sub"); + dds_listener_delete(listener_sub); + + /* Create reader and writer without listeners. */ + g_reader = dds_create_reader(g_subscriber, g_topic, g_qos, NULL); + cr_assert_gt(g_reader, 0, "Failed to create prerequisite reader"); + g_writer = dds_create_writer(g_publisher, g_topic, g_qos, NULL); + cr_assert_gt(g_writer, 0, "Failed to create prerequisite writer"); + + /* Publication and Subscription should be matched. */ + triggered = waitfor_cb(DDS_PUBLICATION_MATCHED_STATUS | DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_eq(triggered & DDS_SUBSCRIPTION_MATCHED_STATUS, DDS_SUBSCRIPTION_MATCHED_STATUS, "DDS_SUBSCRIPTION_MATCHED_STATUS not triggered"); + cr_assert_eq(triggered & DDS_PUBLICATION_MATCHED_STATUS, DDS_PUBLICATION_MATCHED_STATUS, "DDS_PUBLICATION_MATCHED_STATUS not triggered"); + cr_assert_eq(cb_writer, g_writer); + cr_assert_eq(cb_reader, g_reader); + + /* Write sample. */ + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to write prerequisite data"); + + /* Data on readers should be triggered with the right status. */ + triggered = waitfor_cb(DDS_DATA_ON_READERS_STATUS); + cr_assert_eq(triggered & DDS_DATA_ON_READERS_STATUS, DDS_DATA_ON_READERS_STATUS, "DDS_DATA_ON_READERS_STATUS not triggered"); + cr_assert_eq(cb_subscriber, g_subscriber); + cr_assert_neq(triggered & DDS_DATA_AVAILABLE_STATUS, DDS_DATA_AVAILABLE_STATUS, "DDS_DATA_AVAILABLE_STATUS triggered"); + + dds_delete(g_writer); + dds_delete(g_reader); +} + + +Test(ddsc_listener, matched, .init=init_triggering_base, .fini=fini_triggering_base) +{ + uint32_t triggered; + + /* We will basically do the same as the 'normal' init_triggering_test() and + * fini_triggering_test() calls. It's just that we do it in a different + * order and use the participant iso subscriber and publisher. */ + + /* We are interested in matched notifications. */ + dds_lset_publication_matched(g_listener, publication_matched_cb); + dds_lset_subscription_matched(g_listener, subscription_matched_cb); + + /* Create reader and writer with proper listeners. + * The creation order is deliberately different from publication_matched and subscription_matched. */ + g_reader = dds_create_reader(g_participant, g_topic, g_qos, g_listener); + cr_assert_gt(g_reader, 0, "Failed to create prerequisite reader"); + g_writer = dds_create_writer(g_participant, g_topic, g_qos, g_listener); + cr_assert_gt(g_writer, 0, "Failed to create prerequisite writer"); + + /* Both matched should be triggered on the right entities. */ + triggered = waitfor_cb(DDS_PUBLICATION_MATCHED_STATUS | DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_eq(triggered & DDS_SUBSCRIPTION_MATCHED_STATUS, DDS_SUBSCRIPTION_MATCHED_STATUS, "DDS_SUBSCRIPTION_MATCHED_STATUS not triggered"); + cr_assert_eq(triggered & DDS_PUBLICATION_MATCHED_STATUS, DDS_PUBLICATION_MATCHED_STATUS, "DDS_PUBLICATION_MATCHED_STATUS not triggered"); + cr_assert_eq(cb_writer, g_writer); + cr_assert_eq(cb_reader, g_reader); + + dds_delete(g_writer); + dds_delete(g_reader); +} + +Test(ddsc_listener, publication_matched, .init=init_triggering_test, .fini=fini_triggering_test) +{ + dds_instance_handle_t reader_hdl; + dds_return_t ret; + uint32_t triggered; + uint32_t status; + + /* Get reader handle that should be part of the status. */ + ret = dds_get_instance_handle(g_reader, &reader_hdl); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to get prerequisite reader_hdl"); + + /* Publication matched should be triggered with the right status. */ + triggered = waitfor_cb(DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_eq(triggered & DDS_PUBLICATION_MATCHED_STATUS, DDS_PUBLICATION_MATCHED_STATUS, "DDS_PUBLICATION_MATCHED_STATUS not triggered"); + cr_assert_eq(cb_writer, g_writer); + cr_assert_eq(cb_publication_matched_status.current_count, 1); + cr_assert_eq(cb_publication_matched_status.current_count_change, 1); + cr_assert_eq(cb_publication_matched_status.total_count, 1); + cr_assert_eq(cb_publication_matched_status.total_count_change, 1); + cr_assert_eq(cb_publication_matched_status.last_subscription_handle, reader_hdl); + + /* The listener should have swallowed the status. */ + ret = dds_read_status(g_writer, &status, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_read_status failed"); + cr_assert_eq(status, 0); + + /* Reset the trigger flags. */ + cb_called = 0; + + /* Un-match the publication by deleting the reader. */ + dds_delete(g_reader); + + /* Publication matched should be triggered with the right status. */ + triggered = waitfor_cb(DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_eq(triggered & DDS_PUBLICATION_MATCHED_STATUS, DDS_PUBLICATION_MATCHED_STATUS, "DDS_PUBLICATION_MATCHED_STATUS not triggered"); + cr_assert_eq(cb_writer, g_writer); + cr_assert_eq(cb_publication_matched_status.current_count, 0); + cr_assert_eq(cb_publication_matched_status.current_count_change, -1); + cr_assert_eq(cb_publication_matched_status.total_count, 1); + cr_assert_eq(cb_publication_matched_status.total_count_change, 0); + cr_assert_eq(cb_publication_matched_status.last_subscription_handle, reader_hdl); +} + +Test(ddsc_listener, subscription_matched, .init=init_triggering_test, .fini=fini_triggering_test) +{ + dds_instance_handle_t writer_hdl; + dds_return_t ret; + uint32_t triggered; + uint32_t status; + + /* Get writer handle that should be part of the status. */ + ret = dds_get_instance_handle(g_writer, &writer_hdl); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to get prerequisite writer_hdl"); + + /* Subscription matched should be triggered with the right status. */ + triggered = waitfor_cb(DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_eq(triggered & DDS_SUBSCRIPTION_MATCHED_STATUS, DDS_SUBSCRIPTION_MATCHED_STATUS, "DDS_SUBSCRIPTION_MATCHED_STATUS not triggered"); + cr_assert_eq(cb_reader, g_reader); + cr_assert_eq(cb_subscription_matched_status.current_count, 1); + cr_assert_eq(cb_subscription_matched_status.current_count_change, 1); + cr_assert_eq(cb_subscription_matched_status.total_count, 1); + cr_assert_eq(cb_subscription_matched_status.total_count_change, 1); + cr_assert_eq(cb_subscription_matched_status.last_publication_handle, writer_hdl); + + /* The listener should have swallowed the status. */ + ret = dds_read_status(g_reader, &status, DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_read_status failed"); + cr_assert_eq(status, 0); + + /* Reset the trigger flags. */ + cb_called = 0; + + /* Un-match the subscription by deleting the writer. */ + dds_delete(g_writer); + + /* Subscription matched should be triggered with the right status. */ + triggered = waitfor_cb(DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_eq(triggered & DDS_SUBSCRIPTION_MATCHED_STATUS, DDS_SUBSCRIPTION_MATCHED_STATUS, "DDS_SUBSCRIPTION_MATCHED_STATUS not triggered"); + cr_assert_eq(cb_reader, g_reader); + cr_assert_eq(cb_subscription_matched_status.current_count, 0); + cr_assert_eq(cb_subscription_matched_status.current_count_change, -1); + cr_assert_eq(cb_subscription_matched_status.total_count, 1); + cr_assert_eq(cb_subscription_matched_status.total_count_change, 0); + cr_assert_eq(cb_subscription_matched_status.last_publication_handle, writer_hdl); +} + +Test(ddsc_listener, incompatible_qos, .init=init_triggering_base, .fini=fini_triggering_base) +{ + dds_return_t ret; + uint32_t triggered; + uint32_t status; + + /* We are interested in incompatible qos notifications. */ + dds_lset_offered_incompatible_qos(g_listener, offered_incompatible_qos_cb); + dds_lset_requested_incompatible_qos(g_listener, requested_incompatible_qos_cb); + + /* Create reader and writer with proper listeners. + * But create reader with persistent durability to get incompatible qos. */ + g_writer = dds_create_writer(g_participant, g_topic, g_qos, g_listener); + cr_assert_gt(g_writer, 0, "Failed to create prerequisite writer"); + dds_qset_durability (g_qos, DDS_DURABILITY_PERSISTENT); + g_reader = dds_create_reader(g_participant, g_topic, g_qos, g_listener); + cr_assert_gt(g_reader, 0, "Failed to create prerequisite reader"); + + /* Incompatible QoS should be triggered with the right status. */ + triggered = waitfor_cb(DDS_OFFERED_INCOMPATIBLE_QOS_STATUS | DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS); + cr_assert_eq(triggered & DDS_OFFERED_INCOMPATIBLE_QOS_STATUS, DDS_OFFERED_INCOMPATIBLE_QOS_STATUS, "DDS_OFFERED_INCOMPATIBLE_QOS_STATUS not triggered"); + cr_assert_eq(triggered & DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS, DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS, "DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS not triggered"); + cr_assert_eq(cb_reader, g_reader); + cr_assert_eq(cb_writer, g_writer); + cr_assert_eq(cb_offered_incompatible_qos_status.total_count, 1, "cb_offered_incompatible_qos_status.total_count(%d) != 1", cb_offered_incompatible_qos_status.total_count); + cr_assert_eq(cb_offered_incompatible_qos_status.total_count_change, 1); + cr_assert_eq(cb_offered_incompatible_qos_status.last_policy_id, DDS_DURABILITY_QOS_POLICY_ID); + cr_assert_eq(cb_requested_incompatible_qos_status.total_count, 1, "cb_requested_incompatible_qos_status.total_count(%d) != 1", cb_requested_incompatible_qos_status.total_count); + cr_assert_eq(cb_requested_incompatible_qos_status.total_count_change, 1); + cr_assert_eq(cb_requested_incompatible_qos_status.last_policy_id, DDS_DURABILITY_QOS_POLICY_ID); + + /* The listener should have swallowed the status. */ + ret = dds_read_status(g_writer, &status, DDS_OFFERED_INCOMPATIBLE_QOS_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_read_status failed"); + cr_assert_eq(status, 0); + ret = dds_read_status(g_reader, &status, DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_read_status failed"); + cr_assert_eq(status, 0); + + dds_delete(g_writer); + dds_delete(g_reader); +} + +Test(ddsc_listener, data_available, .init=init_triggering_test, .fini=fini_triggering_test) +{ + dds_return_t ret; + uint32_t triggered; + uint32_t status; + RoundTripModule_DataType sample = { 0 }; + + /* We are interested in data available notifications. */ + dds_lset_data_available(g_listener, data_available_cb); + ret = dds_set_listener(g_reader, g_listener); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set listener"); + + /* Write sample. */ + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to write prerequisite data"); + + /* Data available should be triggered with the right status. */ + triggered = waitfor_cb(DDS_DATA_AVAILABLE_STATUS); + cr_assert_eq(triggered & DDS_DATA_AVAILABLE_STATUS, DDS_DATA_AVAILABLE_STATUS, "DDS_DATA_AVAILABLE_STATUS not triggered"); + cr_assert_eq(cb_reader, g_reader); + + /* The listener should have swallowed the status. */ + ret = dds_read_status(g_subscriber, &status, DDS_DATA_ON_READERS_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_read_status failed"); + cr_assert_eq(status, 0); + ret = dds_read_status(g_reader, &status, DDS_DATA_AVAILABLE_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_read_status failed"); + cr_assert_eq(status, 0); +} + +Test(ddsc_listener, data_on_readers, .init=init_triggering_test, .fini=fini_triggering_test) +{ + dds_return_t ret; + uint32_t triggered; + uint32_t status; + RoundTripModule_DataType sample = { 0 }; + + /* We are interested in data available notifications. */ + dds_lset_data_on_readers(g_listener, data_on_readers_cb); + ret = dds_set_listener(g_subscriber, g_listener); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set listener"); + + /* Setting data available notifications should not 'sabotage' the on_readers call. */ + dds_lset_data_available(g_listener, data_available_cb); + ret = dds_set_listener(g_reader, g_listener); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set listener"); + + /* Write sample. */ + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to write prerequisite data"); + + /* Data on readers should be triggered with the right status. */ + triggered = waitfor_cb(DDS_DATA_ON_READERS_STATUS); + cr_assert_eq(triggered & DDS_DATA_ON_READERS_STATUS, DDS_DATA_ON_READERS_STATUS, "DDS_DATA_ON_READERS_STATUS not triggered"); + cr_assert_eq(cb_subscriber, g_subscriber); + cr_assert_neq(triggered & DDS_DATA_AVAILABLE_STATUS, DDS_DATA_AVAILABLE_STATUS, "DDS_DATA_AVAILABLE_STATUS triggered"); + + /* The listener should have swallowed the status. */ + ret = dds_read_status(g_subscriber, &status, DDS_DATA_ON_READERS_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_read_status failed"); + cr_assert_eq(status, 0); + ret = dds_read_status(g_reader, &status, DDS_DATA_AVAILABLE_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_read_status failed"); + cr_assert_eq(status, 0); +} + + +Test(ddsc_listener, sample_lost, .init=init_triggering_test, .fini=fini_triggering_test) +{ + dds_return_t ret; + uint32_t triggered; + dds_time_t the_past; + uint32_t status; + RoundTripModule_DataType sample = { 0 }; + + /* Get a time that should be historic on all platforms.*/ + the_past = dds_time() - 1000000; + + /* We are interested in sample lost notifications. */ + dds_lset_sample_lost(g_listener, sample_lost_cb); + ret = dds_set_listener(g_reader, g_listener); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set listener"); + + /* Write first sample with current timestamp. */ + ret = dds_write_ts(g_writer, &sample, dds_time()); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to write contemporary data"); + + /* Write second sample with older timestamp. */ + ret = dds_write_ts(g_writer, &sample, the_past); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to write pre-historic data"); + + /* Sample lost should be triggered with the right status. */ + triggered = waitfor_cb(DDS_SAMPLE_LOST_STATUS); + cr_assert_eq(triggered & DDS_SAMPLE_LOST_STATUS, DDS_SAMPLE_LOST_STATUS, "DDS_SAMPLE_LOST_STATUS not triggered"); + cr_assert_eq(cb_reader, g_reader); + cr_assert_eq(cb_sample_lost_status.total_count, 1); + cr_assert_eq(cb_sample_lost_status.total_count_change, 1); + + /* The listener should have swallowed the status. */ + ret = dds_read_status(g_reader, &status, DDS_SAMPLE_LOST_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_read_status failed"); + cr_assert_eq(status, 0); +} + +Test(ddsc_listener, sample_rejected, .init=init_triggering_test, .fini=fini_triggering_test) +{ + dds_return_t ret; + uint32_t triggered; + uint32_t status; + RoundTripModule_DataType sample = { 0 }; + + /* We are interested in sample rejected notifications. */ + dds_lset_sample_rejected(g_listener, sample_rejected_cb); + ret = dds_set_listener(g_reader, g_listener); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set listener"); + + /* Write more than resource limits set by the reader. */ + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to write data 1"); + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to write data 2"); + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to write data 3"); + + /* Sample lost should be triggered with the right status. */ + triggered = waitfor_cb(DDS_SAMPLE_REJECTED_STATUS); + cr_assert_eq(triggered & DDS_SAMPLE_REJECTED_STATUS, DDS_SAMPLE_REJECTED_STATUS, "DDS_SAMPLE_REJECTED_STATUS not triggered"); + cr_assert_eq(cb_reader, g_reader); + cr_assert_eq(cb_sample_rejected_status.total_count, 2); + cr_assert_eq(cb_sample_rejected_status.total_count_change, 1); + + /* The listener should have swallowed the status. */ + ret = dds_read_status(g_reader, &status, DDS_SAMPLE_REJECTED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_read_status failed"); + cr_assert_eq(status, 0); +} + +Test(ddsc_listener, liveliness_changed, .init=init_triggering_test, .fini=fini_triggering_base) +{ + dds_instance_handle_t writer_hdl; + dds_return_t ret; + uint32_t triggered; + uint32_t status; + + /* The init_triggering_test_byliveliness set our interest in liveliness. */ + + /* Get writer handle that should be part of the status. */ + ret = dds_get_instance_handle(g_writer, &writer_hdl); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to get prerequisite writer_hdl"); + + /* Liveliness changed should be triggered with the right status. */ + triggered = waitfor_cb(DDS_LIVELINESS_CHANGED_STATUS); + cr_assert_eq(triggered & DDS_LIVELINESS_CHANGED_STATUS, DDS_LIVELINESS_CHANGED_STATUS, "DDS_LIVELINESS_CHANGED_STATUS not triggered"); + cr_assert_eq(cb_reader, g_reader); + cr_assert_eq(cb_liveliness_changed_status.alive_count, 1); + cr_assert_eq(cb_liveliness_changed_status.alive_count_change, 1); + cr_assert_eq(cb_liveliness_changed_status.not_alive_count, 0); + cr_assert_eq(cb_liveliness_changed_status.not_alive_count_change, 0); + cr_assert_eq(cb_liveliness_changed_status.last_publication_handle, writer_hdl); + + /* The listener should have swallowed the status. */ + ret = dds_read_status(g_reader, &status, DDS_LIVELINESS_CHANGED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_read_status failed"); + cr_assert_eq(status, 0); + + /* Reset the trigger flags. */ + cb_called = 0; + + /* Change liveliness again by deleting the writer. */ + dds_delete(g_writer); + + /* Liveliness changed should be triggered with the right status. */ + triggered = waitfor_cb(DDS_LIVELINESS_CHANGED_STATUS); + cr_assert_eq(triggered & DDS_LIVELINESS_CHANGED_STATUS, DDS_LIVELINESS_CHANGED_STATUS, "DDS_LIVELINESS_CHANGED_STATUS not triggered"); + cr_assert_eq(cb_reader, g_reader); + cr_assert_eq(cb_liveliness_changed_status.alive_count, 0); + cr_assert_eq(cb_liveliness_changed_status.alive_count_change, 0); + cr_assert_eq(cb_liveliness_changed_status.not_alive_count, 1); + cr_assert_eq(cb_liveliness_changed_status.not_alive_count_change, 1); + cr_assert_eq(cb_liveliness_changed_status.last_publication_handle, writer_hdl); +} + +#if 0 +/* This is basically the same as the Lite test, but inconsistent topic is not triggered. + * That is actually what I would expect, because the code doesn't seem to be the way + * to go to test for inconsistent topic. */ +Test(ddsc_listener, inconsistent_topic, .init=init_triggering_base, .fini=fini_triggering_base) +{ + dds_entity_t wr_topic; + dds_entity_t rd_topic; + dds_entity_t writer; + dds_entity_t reader; + uint32_t triggered; + + os_osInit(); + + os_mutexInit(&g_mutex); + os_condInit(&g_cond, &g_mutex); + + g_qos = dds_qos_create(); + cr_assert_not_null(g_qos, "Failed to create prerequisite g_qos"); + + g_listener = dds_listener_create(NULL); + cr_assert_not_null(g_listener, "Failed to create prerequisite g_listener"); + + g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_participant, 0, "Failed to create prerequisite g_participant"); + + /* We are interested in inconsistent topics. */ + dds_lset_inconsistent_topic(g_listener, inconsistent_topic_cb); + + wr_topic = dds_create_topic(g_participant, &RoundTripModule_DataType_desc, "WRITER_TOPIC", NULL, g_listener); + cr_assert_gt(g_topic, 0, "Failed to create prerequisite wr_topic"); + + rd_topic = dds_create_topic(g_participant, &RoundTripModule_DataType_desc, "READER_TOPIC", NULL, g_listener); + cr_assert_gt(g_topic, 0, "Failed to create prerequisite rd_topic"); + + /* Create reader and writer. */ + writer = dds_create_writer(g_participant, g_topic, NULL, NULL); + cr_assert_gt(writer, 0, "Failed to create prerequisite writer"); + dds_qset_reliability (g_qos, DDS_RELIABILITY_RELIABLE, DDS_SECS (1)); + dds_qset_history (g_qos, DDS_HISTORY_KEEP_ALL, 0); + reader = dds_create_reader(g_subscriber, g_topic, g_qos, NULL); + cr_assert_gt(reader, 0, "Failed to create prerequisite reader"); + + /* Inconsistent topic should be triggered with the right status. */ + triggered = waitfor_cb(DDS_INCONSISTENT_TOPIC_STATUS); + cr_assert_eq(triggered & DDS_INCONSISTENT_TOPIC_STATUS, DDS_INCONSISTENT_TOPIC_STATUS, "DDS_INCONSISTENT_TOPIC_STATUS not triggered"); + + dds_delete(reader); + dds_delete(writer); + dds_delete(rd_topic); + dds_delete(wr_topic); + dds_delete(g_participant); + + dds_listener_delete(g_listener); + dds_qos_delete(g_qos); +} +#endif +#endif diff --git a/src/core/ddsc/tests/participant.c b/src/core/ddsc/tests/participant.c new file mode 100644 index 0000000..8c67657 --- /dev/null +++ b/src/core/ddsc/tests/participant.c @@ -0,0 +1,332 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include +#include +#include "config_env.h" +#include "ddsc/ddsc_project.h" + + +#define cr_assert_status_eq(s1, s2, ...) cr_assert_eq(dds_err_nr(s1), s2, __VA_ARGS__) + + +Test(ddsc_participant, create_and_delete) { + + dds_entity_t participant, participant2, participant3; + + participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "dds_participant_create"); + + participant2 = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant2, 0, "dds_participant_create"); + + dds_delete (participant); + dds_delete (participant2); + + participant3 = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant3, 0, "dds_participant_create"); + + dds_delete (participant3); + +} + + +/* Test for creating participant with no configuration file */ +Test(ddsc_participant, create_with_no_conf_no_env) { + dds_entity_t participant, participant2, participant3; + dds_return_t status; + dds_domainid_t domain_id; + dds_domainid_t valid_domain=0; + + const char * env_uri = os_getenv(DDSC_PROJECT_NAME_NOSPACE_CAPS"_URI"); + cr_assert_eq(env_uri, NULL, DDSC_PROJECT_NAME_NOSPACE_CAPS"_URI must be NULL"); + + //invalid domain + participant = dds_create_participant (1, NULL, NULL); + cr_assert_lt(participant, 0, "Error must be received for invalid domain value"); + + //valid specific domain value + participant2 = dds_create_participant (valid_domain, NULL, NULL); + cr_assert_gt(participant2, 0, "Valid participant must be received for valid specific domain value"); + status = dds_get_domainid(participant2, &domain_id); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_domainid(participant, domain_id)"); + cr_assert_eq(domain_id, valid_domain, "Retrieved domain ID must be valid"); + + //DDS_DOMAIN_DEFAULT from user + participant3 = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant3, 0, "Valid participant must be received for DDS_DOMAIN_DEFAULT"); + status = dds_get_domainid(participant3, &domain_id); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_domainid(participant, domain_id)"); + cr_assert_eq(domain_id, valid_domain, "Retrieved domain ID must be valid"); + + dds_delete(participant2); + dds_delete(participant3); + + +} + + +////WITH CONF + +/* Test for creating participant with valid configuration file */ +Test(ddsc_participant, create_with_conf_no_env) { + dds_entity_t participant, participant2, participant3; + dds_return_t status; + dds_domainid_t domain_id; + dds_domainid_t valid_domain=3; + + static char env_uri_str[1000]; + (void) sprintf(env_uri_str, "%s=%s", DDSC_PROJECT_NAME_NOSPACE_CAPS"_URI", CONFIG_ENV_SIMPLE_UDP); + os_putenv(env_uri_str); + + static char env_mp_str[100]; + (void) sprintf(env_mp_str, "%s=%s", "MAX_PARTICIPANTS", CONFIG_ENV_MAX_PARTICIPANTS); + os_putenv(env_mp_str); + + + const char * env_uri = os_getenv(DDSC_PROJECT_NAME_NOSPACE_CAPS"_URI"); + cr_assert_neq(env_uri, NULL, DDSC_PROJECT_NAME_NOSPACE_CAPS"_URI must be set"); + + //invalid domain + participant = dds_create_participant (1, NULL, NULL); + cr_assert_lt(participant, 0, "Error must be received for invalid domain value"); + + //valid specific domain value + participant2 = dds_create_participant (valid_domain, NULL, NULL); + cr_assert_gt(participant2, 0, "Valid participant must be received for valid specific domain value"); + status = dds_get_domainid(participant2, &domain_id); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_domainid(participant, domain_id)"); + cr_assert_eq(domain_id, valid_domain, "Retrieved domain ID must be valid"); + + + //DDS_DOMAIN_DEFAULT from the user + participant3 = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant3, 0, "Valid participant must be received for DDS_DOMAIN_DEFAULT"); + status = dds_get_domainid(participant3, &domain_id); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_domainid(participant, domain_id)"); + cr_assert_eq(domain_id, valid_domain, "Retrieved domain ID must be valid"); + + dds_delete(participant2); + dds_delete(participant3); + + +} + +Test(ddsc_participant_lookup, one) { + + dds_entity_t participant; + dds_entity_t participants[3]; + dds_domainid_t domain_id; + dds_return_t status, num_of_found_pp; + size_t size = 3; + + /* Create a participant */ + participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "dds_participant_create"); + + /* Get domain id */ + status = dds_get_domainid(participant, &domain_id); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_domainid(participant, domain_id)"); + + num_of_found_pp = dds_lookup_participant( domain_id, participants, size); + cr_assert_eq(num_of_found_pp, 1, "dds_lookup_participant(domain_id, participants, size)"); + cr_assert_eq(participants[0], participant,"dds_lookup_participant did not return the participant"); + + dds_delete (participant); +} + +Test(ddsc_participant_lookup, multiple) { + + dds_entity_t participant, participant2; + dds_entity_t participants[2]; + dds_domainid_t domain_id; + dds_return_t status, num_of_found_pp; + size_t size = 2; + + /* Create participants */ + participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "dds_participant_create"); + + participant2 = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant2, 0, "dds_participant_create"); + + /* Get domain id */ + status = dds_get_domainid(participant, &domain_id); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_domainid(participant, domain_id)"); + + num_of_found_pp = dds_lookup_participant( domain_id, participants, size); + cr_assert_eq(num_of_found_pp, 2, "dds_lookup_participant(domain_id, participants, size)"); + cr_assert(participants[0] == participant || participants[0] == participant2,"ddsc_participant_lookup"); + cr_assert(participants[1] == participant || participants[1] == participant2,"ddsc_participant_lookup"); + cr_assert_neq(participants[0], participants[1], "dds_lookup_participant returned a participant twice"); + + dds_delete (participant2); + dds_delete (participant); +} + +Test(ddsc_participant_lookup, array_too_small) { + + dds_entity_t participant, participant2, participant3; + dds_entity_t participants[2]; + dds_domainid_t domain_id; + dds_return_t status, num_of_found_pp; + size_t size = 2; + + /* Create participants */ + participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "dds_participant_create"); + + participant2 = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant2, 0, "dds_participant_create"); + + participant3 = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant3, 0, "dds_participant_create"); + + /* Get domain id */ + status = dds_get_domainid(participant, &domain_id); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_domainid(participant, domain_id)"); + + num_of_found_pp = dds_lookup_participant( domain_id, participants, size); + cr_assert_eq(num_of_found_pp, 3, "dds_lookup_participant(domain_id, participants, size)"); + cr_assert(participants[0] == participant || participants[0] == participant2 || participants[0] == participant3,"ddsc_participant_lookup"); + cr_assert(participants[1] == participant || participants[1] == participant2 || participants[1] == participant3,"ddsc_participant_lookup"); + cr_assert_neq(participants[0], participants[1], "dds_lookup_participant returned a participant twice"); + + dds_delete (participant3); + dds_delete (participant2); + dds_delete (participant); +} + +Test(ddsc_participant_lookup, null_zero){ + + dds_entity_t participant; + dds_domainid_t domain_id; + dds_return_t status, num_of_found_pp; + size_t size = 0; + + /* Create a participant */ + participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "dds_participant_create"); + + /* Get domain id */ + status = dds_get_domainid(participant, &domain_id); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_domainid(participant, domain_id)"); + + num_of_found_pp = dds_lookup_participant( domain_id, NULL, size); + cr_assert_eq(num_of_found_pp, 1, "dds_lookup_participant(domain_id, participants, size)"); + + dds_delete (participant); +} + +Test(ddsc_participant_lookup, null_nonzero){ + + dds_entity_t participant; + dds_domainid_t domain_id; + dds_return_t status, num_of_found_pp; + size_t size = 2; + + /* Create a participant */ + participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "dds_participant_create"); + + /* Get domain id */ + status = dds_get_domainid(participant, &domain_id); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_domainid(participant, domain_id)"); + + num_of_found_pp = dds_lookup_participant( domain_id, NULL, size); + cr_assert_status_eq(num_of_found_pp, DDS_RETCODE_BAD_PARAMETER, "dds_lookup_participant did not return bad parameter"); + + dds_delete (participant); +} + +Test(ddsc_participant_lookup, unknown_id) { + + dds_entity_t participant; + dds_entity_t participants[3]; + dds_domainid_t domain_id; + dds_return_t status, num_of_found_pp; + size_t size = 3; + + /* Create a participant */ + participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "dds_participant_create"); + + /* Get domain id */ + status = dds_get_domainid(participant, &domain_id); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_domainid(participant, domain_id)"); + domain_id ++; + + num_of_found_pp = dds_lookup_participant( domain_id, participants, size); + cr_assert_eq(num_of_found_pp, 0, "dds_lookup_participant(domain_id, participants, size)"); + + dds_delete (participant); +} + +Test(ddsc_participant_lookup, none) { + + dds_entity_t participants[2]; + dds_return_t num_of_found_pp; + size_t size = 2; + + num_of_found_pp = dds_lookup_participant( 0, participants, size); + cr_assert_eq(num_of_found_pp, 0, "dds_lookup_participant did not return 0"); +} + +Test(ddsc_participant_lookup, no_more) { + + dds_entity_t participant; + dds_entity_t participants[3]; + dds_domainid_t domain_id; + dds_return_t status, num_of_found_pp; + size_t size = 3; + + /* Create a participant */ + participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "dds_participant_create"); + + /* Get domain id */ + status = dds_get_domainid(participant, &domain_id); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_domainid(participant, domain_id)"); + + dds_delete (participant); + + num_of_found_pp = dds_lookup_participant( domain_id, participants, size); + cr_assert_eq(num_of_found_pp, 0, "dds_lookup_participant did not return 0"); +} + +Test(ddsc_participant_lookup, deleted) { + + dds_entity_t participant, participant2; + dds_entity_t participants[2]; + dds_domainid_t domain_id; + dds_return_t status, num_of_found_pp; + size_t size = 2; + + /* Create participants */ + participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "dds_participant_create"); + + participant2 = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant2, 0, "dds_participant_create"); + + /* Get domain id */ + status = dds_get_domainid(participant, &domain_id); + cr_assert_status_eq(status, DDS_RETCODE_OK, "dds_get_domainid(participant, domain_id)"); + + dds_delete (participant2); + + num_of_found_pp = dds_lookup_participant( domain_id, participants, size); + cr_assert_eq(num_of_found_pp, 1, "dds_lookup_participant did not return one participant"); + cr_assert(participants[0] == participant,"ddsc_participant_lookup"); + + dds_delete (participant); +} diff --git a/src/core/ddsc/tests/publisher.c b/src/core/ddsc/tests/publisher.c new file mode 100644 index 0000000..6c1ddfa --- /dev/null +++ b/src/core/ddsc/tests/publisher.c @@ -0,0 +1,268 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include +#include + +/* We are deliberately testing some bad arguments that SAL will complain about. + * So, silence SAL regarding these issues. */ +#pragma warning(push) +#pragma warning(disable: 28020) + + +#define cr_assert_status_eq(s1, s2, ...) cr_assert_eq(dds_err_nr(s1), s2, __VA_ARGS__) + +/* Dummy callback */ +static void data_available_cb(dds_entity_t reader, void* arg) {} + + +Test(ddsc_publisher, create) +{ + const char *singlePartitions[] = { "partition" }; + const char *multiplePartitions[] = { "partition1", "partition2" }; + const char *duplicatePartitions[] = { "partition", "partition" }; + + dds_entity_t participant; + dds_entity_t publisher, publisher1; + dds_listener_t *listener; + dds_qos_t *qos; + + /* Use NULL participant */ + publisher = dds_create_publisher(0, NULL, NULL); + cr_assert_eq(dds_err_nr(publisher), DDS_RETCODE_BAD_PARAMETER, "dds_create_publisher(NULL,NULL,NULL)"); + + participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "dds_create_participant(DDS_DOMAIN_DEFAULT,NULL,NULL)"); + + /* Use non-null participant */ + publisher = dds_create_publisher(participant, NULL, NULL); + cr_assert_gt(publisher, 0, "dds_create_publisher(participant,NULL,NULL)"); + + /* Use entity that is not a participant */ + publisher1 = dds_create_publisher(publisher, NULL, NULL); + cr_assert_eq(dds_err_nr(publisher1), DDS_RETCODE_ILLEGAL_OPERATION, "dds_create_publisher(publisher,NULL,NULL)"); + dds_delete(publisher); + + /* Create a non-null qos */ + qos = dds_qos_create(); + cr_assert_neq(qos, NULL, "dds_qos_create()"); + + /* Use qos without partition; in that case the default partition should be used */ + publisher = dds_create_publisher(participant, qos, NULL); + cr_assert_gt(publisher, 0, "dds_create_publisher(participant,qos,NULL) where qos with default partition"); + dds_delete(publisher); + +/* Somehow, the compiler thinks the char arrays might not be zero-terminated... */ +#pragma warning(push) +#pragma warning(disable: 6054) + + /* Use qos with single partition */ + dds_qset_partition (qos, 1, singlePartitions); + publisher = dds_create_publisher(participant, qos, NULL); + cr_assert_gt(publisher, 0, "dds_create_publisher(participant,qos,NULL) where qos with single partition"); + dds_delete(publisher); + + /* Use qos with multiple partitions */ + dds_qset_partition (qos, 2, multiplePartitions); + publisher = dds_create_publisher(participant, qos, NULL); + cr_assert_gt(publisher, 0, "dds_create_publisher(participant,qos,NULL) where qos with multiple partitions"); + dds_delete(publisher); + + /* Use qos with multiple partitions */ + dds_qset_partition (qos, 2, duplicatePartitions); + publisher = dds_create_publisher(participant, qos, NULL); + cr_assert_gt(publisher, 0, "dds_create_publisher(participant,qos,NULL) where qos with duplicate partitions"); + dds_delete(publisher); + +#pragma warning(pop) + + /* Use listener(NULL) */ + listener = dds_listener_create(NULL); + cr_assert_neq(listener, NULL, "dds_listener_create(NULL)"); + publisher = dds_create_publisher(participant, NULL, listener); + cr_assert_gt(publisher, 0, "dds_create_publisher(participant,NULL,listener(NULL))"); + dds_delete(publisher); + + dds_listener_reset(listener); + + /* Use listener for data_available */ + dds_lset_data_available(listener, NULL); + publisher = dds_create_publisher(participant, NULL, listener); + cr_assert_gt(publisher, 0, "dds_create_publisher(participant,NULL,listener) with dds_lset_data_available(listener, NULL)"); + dds_delete(publisher); + + dds_listener_reset(listener); + + /* Use DDS_LUNSET for data_available */ + dds_lset_data_available(listener, DDS_LUNSET); + publisher = dds_create_publisher(participant, NULL, listener); + cr_assert_gt(publisher, 0, "dds_create_publisher(participant,NULL,listener) with dds_lset_data_available(listener, DDS_LUNSET)"); + dds_delete(publisher); + + dds_listener_reset(listener); + + /* Use callback for data_available */ + dds_lset_data_available(listener, data_available_cb); + publisher = dds_create_publisher(participant, NULL, listener); + cr_assert_gt(publisher, 0, "dds_create_publisher(participant,NULL,listener) with dds_lset_data_available(listener, data_available_cb)"); + dds_delete(publisher); + + /* Use both qos setting and callback listener */ + dds_lset_data_available(listener, data_available_cb); + publisher = dds_create_publisher(participant, qos, listener); + cr_assert_gt(publisher, 0, "dds_create_publisher(participant,qos,listener) with dds_lset_data_available(listener, data_available_cb)"); + dds_delete(publisher); + + dds_listener_delete(listener); + dds_qos_delete(qos); + dds_delete (participant); +} + +Test(ddsc_publisher, suspend_resume) +{ + + dds_entity_t participant, publisher; + dds_return_t status; + + /* Suspend a 0 publisher */ + status = dds_suspend(0); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_suspend(NULL)"); + + /* Resume a 0 publisher */ + status = dds_resume(0); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_resume(NULL)"); + + /* Uae dds_suspend on something else than a publisher */ + participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "dds_create_participant(DDS_DOMAIN_DEFAULT,NULL,NULL)"); + status = dds_suspend(participant); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_suspend(participant)"); + + /* Use dds_resume on something else than a publisher */ + status = dds_resume(participant); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_resume(participant)"); + + /* Use dds_resume without calling dds_suspend */ + publisher = dds_create_publisher(participant, NULL, NULL); + cr_assert_gt(publisher, 0, "dds_create_publisher(participant,NULL,NULL)"); + status = dds_resume(publisher); /* Should be precondition not met? */ + cr_assert_status_eq(status, DDS_RETCODE_UNSUPPORTED, "dds_resume(publisher) without prior suspend"); + + /* Use dds_suspend on non-null publisher */ + status = dds_suspend(publisher); + cr_assert_status_eq(status, DDS_RETCODE_UNSUPPORTED, "dds_suspend(publisher)"); + + /* Use dds_resume on non-null publisher */ + status = dds_resume(publisher); + cr_assert_status_eq(status, DDS_RETCODE_UNSUPPORTED, "dds_resume(publisher)"); + + dds_delete(publisher); + dds_delete(participant); + + return; +} + +Test(ddsc_publisher, wait_for_acks) +{ + dds_entity_t participant, publisher; + dds_return_t status; + dds_duration_t zeroSec = ((dds_duration_t)DDS_SECS(0)); + dds_duration_t oneSec = ((dds_duration_t)DDS_SECS(1)); + dds_duration_t minusOneSec = ((dds_duration_t)DDS_SECS(-1)); + + /* Wait_for_acks on 0 publisher or writer and minusOneSec timeout */ + status = dds_wait_for_acks(0, minusOneSec); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_wait_for_acks(NULL,-1)"); + + /* Wait_for_acks on NULL publisher or writer and zeroSec timeout */ + status = dds_wait_for_acks(0, zeroSec); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_wait_for_acks(NULL,0)"); + + /* wait_for_acks on NULL publisher or writer and oneSec timeout */ + status = dds_wait_for_acks(0, oneSec); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_wait_for_acks(NULL,1)"); + + /* wait_for_acks on NULL publisher or writer and DDS_INFINITE timeout */ + status = dds_wait_for_acks(0, DDS_INFINITY); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_wait_for_acks(NULL,DDS_INFINITY)"); + + participant = dds_create_participant (DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "dds_create_participant(DDS_DOMAIN_DEFAULT,NULL,NULL)"); + + /* Wait_for_acks on participant and minusOneSec timeout */ + status = dds_wait_for_acks(participant, minusOneSec); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_wait_for_acks(participant,-1)"); + + /* Wait_for_acks on participant and zeroSec timeout */ + status = dds_wait_for_acks(participant, zeroSec); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_wait_for_acks(participant,0)"); + + /* Wait_for_acks on participant and oneSec timeout */ + status = dds_wait_for_acks(participant, oneSec); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_wait_for_acks(participant,1)"); + + /* Wait_for_acks on participant and DDS_INFINITE timeout */ + status = dds_wait_for_acks(participant, DDS_INFINITY); + cr_assert_status_eq(status, DDS_RETCODE_BAD_PARAMETER, "dds_wait_for_acks(participant,DDS_INFINITY)"); + + publisher = dds_create_publisher(participant, NULL, NULL); + cr_assert_gt(publisher, 0, "dds_create_publisher(participant,NULL,NULL)"); + + /* Wait_for_acks on publisher and minusOneSec timeout */ + status = dds_wait_for_acks(publisher, minusOneSec); + cr_assert_status_eq(status, DDS_RETCODE_UNSUPPORTED, "dds_wait_for_acks(publisher,-1)"); + + /* Wait_for_acks on publisher and zeroSec timeout */ + status = dds_wait_for_acks(publisher, zeroSec); + cr_assert_status_eq(status, DDS_RETCODE_UNSUPPORTED, "dds_wait_for_acks(publisher,0)"); + + /* Wait_for_acks on publisher and oneSec timeout */ + status = dds_wait_for_acks(publisher, oneSec); + cr_assert_status_eq(status, DDS_RETCODE_UNSUPPORTED, "dds_wait_for_acks(publisher,1)"); + + /* Wait_for_acks on publisher and DDS_INFINITE timeout */ + status = dds_wait_for_acks(publisher, DDS_INFINITY); + cr_assert_status_eq(status, DDS_RETCODE_UNSUPPORTED, "dds_wait_for_acks(publisher,DDS_INFINITY)"); + + /* TODO: create tests by calling dds_qwait_for_acks on writers */ + + status = dds_suspend(publisher); + cr_assert_status_eq(status, DDS_RETCODE_UNSUPPORTED, "dds_suspend(publisher)"); + + /* Wait_for_acks on suspended publisher and minusOneSec timeout */ + status = dds_wait_for_acks(publisher, minusOneSec); + cr_assert_status_eq(status, DDS_RETCODE_UNSUPPORTED, "dds_wait_for_acks(suspended_publisher,-1)"); + + /* Wait_for_acks on suspended publisher and zeroSec timeout */ + status = dds_wait_for_acks(publisher, zeroSec); + cr_assert_status_eq(status, DDS_RETCODE_UNSUPPORTED, "dds_wait_for_acks(suspended_publisher,0)"); + + /* Wait_for_acks on suspended publisher and oneSec timeout */ + status = dds_wait_for_acks(publisher, oneSec); + cr_assert_status_eq(status, DDS_RETCODE_UNSUPPORTED, "dds_wait_for_acks(suspended_publisher,1)"); + + /* Wait_for_acks on suspended publisher and DDS_INFINITE timeout */ + status = dds_wait_for_acks(publisher, DDS_INFINITY); + cr_assert_status_eq(status, DDS_RETCODE_UNSUPPORTED, "dds_wait_for_acks(suspended_publisher,DDS_INFINITY)"); + + dds_delete(publisher); + dds_delete(participant); + + return; +} + +Test(ddsc_publisher, coherency) +{ + return; +} + +#pragma warning(pop) diff --git a/src/core/ddsc/tests/qos.c b/src/core/ddsc/tests/qos.c new file mode 100644 index 0000000..4c396d5 --- /dev/null +++ b/src/core/ddsc/tests/qos.c @@ -0,0 +1,621 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include "os/os.h" +#include +#include + + +#if 0 +#else +/* We are deliberately testing some bad arguments that SAL will complain about. + * So, silence SAL regarding these issues. */ +#pragma warning(push) +#pragma warning(disable: 6387 28020) + + + +/**************************************************************************** + * Convenience global policies + ****************************************************************************/ +struct pol_userdata { + void *value; + size_t sz; +}; + +struct pol_topicdata { + void *value; + size_t sz; +}; + +struct pol_groupdata { + void *value; + size_t sz; +}; + +struct pol_durability { + dds_durability_kind_t kind; +}; + +struct pol_history { + dds_history_kind_t kind; + int32_t depth; +}; + +struct pol_resource_limits { + int32_t max_samples; + int32_t max_instances; + int32_t max_samples_per_instance; +}; + +struct pol_presentation { + dds_presentation_access_scope_kind_t access_scope; + bool coherent_access; + bool ordered_access; +}; + +struct pol_lifespan { + dds_duration_t lifespan; +}; + +struct pol_deadline { + dds_duration_t deadline; +}; + +struct pol_latency_budget { + dds_duration_t duration; +}; + +struct pol_ownership { + dds_ownership_kind_t kind; +}; + +struct pol_ownership_strength { + int32_t value; +}; + +struct pol_liveliness { + dds_liveliness_kind_t kind; + dds_duration_t lease_duration; +}; + +struct pol_time_based_filter { + dds_duration_t minimum_separation; +}; + +struct pol_partition { + uint32_t n; + char **ps; +}; + +struct pol_reliability { + dds_reliability_kind_t kind; + dds_duration_t max_blocking_time; +}; + +struct pol_transport_priority { + int32_t value; +}; + +struct pol_destination_order { + dds_destination_order_kind_t kind; +}; + +struct pol_writer_data_lifecycle { + bool autodispose; +}; + +struct pol_reader_data_lifecycle { + dds_duration_t autopurge_nowriter_samples_delay; + dds_duration_t autopurge_disposed_samples_delay; +}; + +struct pol_durability_service { + dds_duration_t service_cleanup_delay; + dds_history_kind_t history_kind; + int32_t history_depth; + int32_t max_samples; + int32_t max_instances; + int32_t max_samples_per_instance; +}; + + + +static struct pol_userdata g_pol_userdata; +static struct pol_topicdata g_pol_topicdata; +static struct pol_groupdata g_pol_groupdata; +static struct pol_durability g_pol_durability; +static struct pol_history g_pol_history; +static struct pol_resource_limits g_pol_resource_limits; +static struct pol_presentation g_pol_presentation; +static struct pol_lifespan g_pol_lifespan; +static struct pol_deadline g_pol_deadline; +static struct pol_latency_budget g_pol_latency_budget; +static struct pol_ownership g_pol_ownership; +static struct pol_ownership_strength g_pol_ownership_strength; +static struct pol_liveliness g_pol_liveliness; +static struct pol_time_based_filter g_pol_time_based_filter; +static struct pol_partition g_pol_partition; +static struct pol_reliability g_pol_reliability; +static struct pol_transport_priority g_pol_transport_priority; +static struct pol_destination_order g_pol_destination_order; +static struct pol_writer_data_lifecycle g_pol_writer_data_lifecycle; +static struct pol_reader_data_lifecycle g_pol_reader_data_lifecycle; +static struct pol_durability_service g_pol_durability_service; + + + +static const char* c_userdata = "user_key"; +static const char* c_topicdata = "topic_key"; +static const char* c_groupdata = "group_key"; +static const char* c_partitions[] = {"Partition1", "Partition2"}; + + + +/**************************************************************************** + * Test initializations and teardowns. + ****************************************************************************/ +static dds_qos_t *g_qos = NULL; + +static void +qos_init(void) +{ + g_qos = dds_qos_create(); + cr_assert_not_null(g_qos); + + g_pol_userdata.value = (void*)c_userdata; + g_pol_userdata.sz = strlen((char*)g_pol_userdata.value) + 1; + + g_pol_topicdata.value = (void*)c_topicdata; + g_pol_topicdata.sz = strlen((char*)g_pol_topicdata.value) + 1; + + g_pol_groupdata.value = (void*)c_groupdata; + g_pol_groupdata.sz = strlen((char*)g_pol_groupdata.value) + 1; + + g_pol_durability.kind = DDS_DURABILITY_TRANSIENT; + + g_pol_history.kind = DDS_HISTORY_KEEP_LAST; + g_pol_history.depth = 1; + + g_pol_resource_limits.max_samples = 1; + g_pol_resource_limits.max_instances = 1; + g_pol_resource_limits.max_samples_per_instance = 1; + + g_pol_presentation.access_scope = DDS_PRESENTATION_INSTANCE; + g_pol_presentation.coherent_access = true; + g_pol_presentation.ordered_access = true; + + g_pol_lifespan.lifespan = 10000; + + g_pol_deadline.deadline = 20000; + + g_pol_latency_budget.duration = 30000; + + g_pol_ownership.kind = DDS_OWNERSHIP_EXCLUSIVE; + + g_pol_ownership_strength.value = 10; + + g_pol_liveliness.kind = DDS_LIVELINESS_AUTOMATIC; + g_pol_liveliness.lease_duration = 40000; + + g_pol_time_based_filter.minimum_separation = 50000; + + g_pol_partition.ps = (char**)c_partitions; + g_pol_partition.n = 2; + + g_pol_reliability.kind = DDS_RELIABILITY_RELIABLE; + g_pol_reliability.max_blocking_time = 60000; + + g_pol_transport_priority.value = 42; + + g_pol_destination_order.kind = DDS_DESTINATIONORDER_BY_SOURCE_TIMESTAMP; + + g_pol_writer_data_lifecycle.autodispose = true; + + g_pol_reader_data_lifecycle.autopurge_disposed_samples_delay = 70000; + g_pol_reader_data_lifecycle.autopurge_nowriter_samples_delay = 80000; + + g_pol_durability_service.history_depth = 1; + g_pol_durability_service.history_kind = DDS_HISTORY_KEEP_LAST; + g_pol_durability_service.max_samples = 2; + g_pol_durability_service.max_instances = 3; + g_pol_durability_service.max_samples_per_instance = 4; + g_pol_durability_service.service_cleanup_delay = 90000; +} + +static void +qos_fini(void) +{ + dds_qos_delete(g_qos); +} + +/**************************************************************************** + * API tests + ****************************************************************************/ +Test(ddsc_qos, userdata, .init=qos_init, .fini=qos_fini) +{ + struct pol_userdata p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_userdata(NULL, g_pol_userdata.value, g_pol_userdata.sz); + dds_qget_userdata(NULL, &p.value, &p.sz); + dds_qget_userdata(g_qos, NULL, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_userdata(g_qos, g_pol_userdata.value, g_pol_userdata.sz); + dds_qget_userdata(g_qos, &p.value, &p.sz); + cr_assert_eq(p.sz, g_pol_userdata.sz); + cr_assert_str_eq(p.value, g_pol_userdata.value); + + dds_free(p.value); +} + +Test(ddsc_qos, topicdata, .init=qos_init, .fini=qos_fini) +{ + struct pol_topicdata p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_topicdata(NULL, g_pol_topicdata.value, g_pol_topicdata.sz); + dds_qget_topicdata(NULL, &p.value, &p.sz); + dds_qget_topicdata(g_qos, NULL, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_topicdata(g_qos, g_pol_topicdata.value, g_pol_topicdata.sz); + dds_qget_topicdata(g_qos, &p.value, &p.sz); + cr_assert_eq(p.sz, g_pol_topicdata.sz); + cr_assert_str_eq(p.value, g_pol_topicdata.value); + + dds_free(p.value); +} + +Test(ddsc_qos, groupdata, .init=qos_init, .fini=qos_fini) +{ + struct pol_groupdata p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_groupdata(NULL, g_pol_groupdata.value, g_pol_groupdata.sz); + dds_qget_groupdata(NULL, &p.value, &p.sz); + dds_qget_groupdata(g_qos, NULL, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_groupdata(g_qos, g_pol_groupdata.value, g_pol_groupdata.sz); + dds_qget_groupdata(g_qos, &p.value, &p.sz); + cr_assert_eq(p.sz, g_pol_groupdata.sz); + cr_assert_str_eq(p.value, g_pol_groupdata.value); + + dds_free(p.value); +} + +Test(ddsc_qos, durability, .init=qos_init, .fini=qos_fini) +{ + struct pol_durability p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_durability(NULL, g_pol_durability.kind); + dds_qget_durability(NULL, &p.kind); + dds_qget_durability(g_qos, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_durability(g_qos, g_pol_durability.kind); + dds_qget_durability(g_qos, &p.kind); + cr_assert_eq(p.kind, g_pol_durability.kind); +} + +Test(ddsc_qos, history, .init=qos_init, .fini=qos_fini) +{ + struct pol_history p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_history(NULL, g_pol_history.kind, g_pol_history.depth); + dds_qget_history(NULL, &p.kind, &p.depth); + dds_qget_history(g_qos, NULL, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_history(g_qos, g_pol_history.kind, g_pol_history.depth); + dds_qget_history(g_qos, &p.kind, &p.depth); + cr_assert_eq(p.kind, g_pol_history.kind); + cr_assert_eq(p.depth, g_pol_history.depth); +} + +Test(ddsc_qos, resource_limits, .init=qos_init, .fini=qos_fini) +{ + struct pol_resource_limits p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_resource_limits(NULL, g_pol_resource_limits.max_samples, g_pol_resource_limits.max_instances, g_pol_resource_limits.max_samples_per_instance); + dds_qget_resource_limits(NULL, &p.max_samples, &p.max_instances, &p.max_samples_per_instance); + dds_qget_resource_limits(g_qos, NULL, NULL, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_resource_limits(g_qos, g_pol_resource_limits.max_samples, g_pol_resource_limits.max_instances, g_pol_resource_limits.max_samples_per_instance); + dds_qget_resource_limits(g_qos, &p.max_samples, &p.max_instances, &p.max_samples_per_instance); + cr_assert_eq(p.max_samples, g_pol_resource_limits.max_samples); + cr_assert_eq(p.max_instances, g_pol_resource_limits.max_instances); + cr_assert_eq(p.max_samples_per_instance, g_pol_resource_limits.max_samples_per_instance); +} + +Test(ddsc_qos, presentation, .init=qos_init, .fini=qos_fini) +{ + struct pol_presentation p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_presentation(NULL, g_pol_presentation.access_scope, g_pol_presentation.coherent_access, g_pol_presentation.ordered_access); + dds_qget_presentation(NULL, &p.access_scope, &p.coherent_access, &p.ordered_access); + dds_qget_presentation(g_qos, NULL, NULL, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_presentation(g_qos, g_pol_presentation.access_scope, g_pol_presentation.coherent_access, g_pol_presentation.ordered_access); + dds_qget_presentation(g_qos, &p.access_scope, &p.coherent_access, &p.ordered_access); + cr_assert_eq(p.access_scope, g_pol_presentation.access_scope); + cr_assert_eq(p.coherent_access, g_pol_presentation.coherent_access); + cr_assert_eq(p.ordered_access, g_pol_presentation.ordered_access); +} + +Test(ddsc_qos, lifespan, .init=qos_init, .fini=qos_fini) +{ + struct pol_lifespan p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_lifespan(NULL, g_pol_lifespan.lifespan); + dds_qget_lifespan(NULL, &p.lifespan); + dds_qget_lifespan(g_qos, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_lifespan(g_qos, g_pol_lifespan.lifespan); + dds_qget_lifespan(g_qos, &p.lifespan); + cr_assert_eq(p.lifespan, g_pol_lifespan.lifespan); +} + +Test(ddsc_qos, deadline, .init=qos_init, .fini=qos_fini) +{ + struct pol_deadline p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_deadline(NULL, g_pol_deadline.deadline); + dds_qget_deadline(NULL, &p.deadline); + dds_qget_deadline(g_qos, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_deadline(g_qos, g_pol_deadline.deadline); + dds_qget_deadline(g_qos, &p.deadline); + cr_assert_eq(p.deadline, g_pol_deadline.deadline); +} + +Test(ddsc_qos, latency_budget, .init=qos_init, .fini=qos_fini) +{ + struct pol_latency_budget p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_latency_budget(NULL, g_pol_latency_budget.duration); + dds_qget_latency_budget(NULL, &p.duration); + dds_qget_latency_budget(g_qos, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_latency_budget(g_qos, g_pol_latency_budget.duration); + dds_qget_latency_budget(g_qos, &p.duration); + cr_assert_eq(p.duration, g_pol_latency_budget.duration); +} + +Test(ddsc_qos, ownership, .init=qos_init, .fini=qos_fini) +{ + struct pol_ownership p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_ownership(NULL, g_pol_ownership.kind); + dds_qget_ownership(NULL, &p.kind); + dds_qget_ownership(g_qos, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_ownership(g_qos, g_pol_ownership.kind); + dds_qget_ownership(g_qos, &p.kind); + cr_assert_eq(p.kind, g_pol_ownership.kind); +} + +Test(ddsc_qos, ownership_strength, .init=qos_init, .fini=qos_fini) +{ + struct pol_ownership_strength p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_ownership_strength(NULL, g_pol_ownership_strength.value); + dds_qget_ownership_strength(NULL, &p.value); + dds_qget_ownership_strength(g_qos, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_ownership_strength(g_qos, g_pol_ownership_strength.value); + dds_qget_ownership_strength(g_qos, &p.value); + cr_assert_eq(p.value, g_pol_ownership_strength.value); +} + +Test(ddsc_qos, liveliness, .init=qos_init, .fini=qos_fini) +{ + struct pol_liveliness p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_liveliness(NULL, g_pol_liveliness.kind, g_pol_liveliness.lease_duration); + dds_qget_liveliness(NULL, &p.kind, &p.lease_duration); + dds_qget_liveliness(g_qos, NULL, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_liveliness(g_qos, g_pol_liveliness.kind, g_pol_liveliness.lease_duration); + dds_qget_liveliness(g_qos, &p.kind, &p.lease_duration); + cr_assert_eq(p.kind, g_pol_liveliness.kind); + cr_assert_eq(p.lease_duration, g_pol_liveliness.lease_duration); +} + +Test(ddsc_qos, time_base_filter, .init=qos_init, .fini=qos_fini) +{ + struct pol_time_based_filter p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_time_based_filter(NULL, g_pol_time_based_filter.minimum_separation); + dds_qget_time_based_filter(NULL, &p.minimum_separation); + dds_qget_time_based_filter(g_qos, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_time_based_filter(g_qos, g_pol_time_based_filter.minimum_separation); + dds_qget_time_based_filter(g_qos, &p.minimum_separation); + cr_assert_eq(p.minimum_separation, g_pol_time_based_filter.minimum_separation); +} + +Test(ddsc_qos, partition, .init=qos_init, .fini=qos_fini) +{ + struct pol_partition p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_partition(NULL, g_pol_partition.n, c_partitions); + dds_qget_partition(NULL, &p.n, &p.ps); + dds_qget_partition(g_qos, NULL, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_partition(g_qos, g_pol_partition.n, c_partitions); + dds_qget_partition(g_qos, &p.n, &p.ps); + cr_assert_eq(p.n, 2); + cr_assert_eq(p.n, g_pol_partition.n); + cr_assert_str_eq(p.ps[0], g_pol_partition.ps[0]); + cr_assert_str_eq(p.ps[1], g_pol_partition.ps[1]); + + dds_free(p.ps[0]); + dds_free(p.ps[1]); + dds_free(p.ps); +} + +Test(ddsc_qos, reliability, .init=qos_init, .fini=qos_fini) +{ + struct pol_reliability p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_reliability(NULL, g_pol_reliability.kind, g_pol_reliability.max_blocking_time); + dds_qget_reliability(NULL, &p.kind, &p.max_blocking_time); + dds_qget_reliability(g_qos, NULL, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_reliability(g_qos, g_pol_reliability.kind, g_pol_reliability.max_blocking_time); + dds_qget_reliability(g_qos, &p.kind, &p.max_blocking_time); + cr_assert_eq(p.kind, g_pol_reliability.kind); + cr_assert_eq(p.max_blocking_time, g_pol_reliability.max_blocking_time); +} + +Test(ddsc_qos, transport_priority, .init=qos_init, .fini=qos_fini) +{ + struct pol_transport_priority p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_transport_priority(NULL, g_pol_transport_priority.value); + dds_qget_transport_priority(NULL, &p.value); + dds_qget_transport_priority(g_qos, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_transport_priority(g_qos, g_pol_transport_priority.value); + dds_qget_transport_priority(g_qos, &p.value); + cr_assert_eq(p.value, g_pol_transport_priority.value); +} + +Test(ddsc_qos, destination_order, .init=qos_init, .fini=qos_fini) +{ + struct pol_destination_order p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_destination_order(NULL, g_pol_destination_order.kind); + dds_qget_destination_order(NULL, &p.kind); + dds_qget_destination_order(g_qos, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_destination_order(g_qos, g_pol_destination_order.kind); + dds_qget_destination_order(g_qos, &p.kind); + cr_assert_eq(p.kind, g_pol_destination_order.kind); +} + +Test(ddsc_qos, writer_data_lifecycle, .init=qos_init, .fini=qos_fini) +{ + struct pol_writer_data_lifecycle p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_writer_data_lifecycle(NULL, g_pol_writer_data_lifecycle.autodispose); + dds_qget_writer_data_lifecycle(NULL, &p.autodispose); + dds_qget_writer_data_lifecycle(g_qos, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_writer_data_lifecycle(g_qos, g_pol_writer_data_lifecycle.autodispose); + dds_qget_writer_data_lifecycle(g_qos, &p.autodispose); + cr_assert_eq(p.autodispose, g_pol_writer_data_lifecycle.autodispose); +} + +Test(ddsc_qos, reader_data_lifecycle, .init=qos_init, .fini=qos_fini) +{ + struct pol_reader_data_lifecycle p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_reader_data_lifecycle(NULL, g_pol_reader_data_lifecycle.autopurge_nowriter_samples_delay, g_pol_reader_data_lifecycle.autopurge_disposed_samples_delay); + dds_qget_reader_data_lifecycle(NULL, &p.autopurge_nowriter_samples_delay, &p.autopurge_disposed_samples_delay); + dds_qget_reader_data_lifecycle(g_qos, NULL, NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_reader_data_lifecycle(g_qos, g_pol_reader_data_lifecycle.autopurge_nowriter_samples_delay, g_pol_reader_data_lifecycle.autopurge_disposed_samples_delay); + dds_qget_reader_data_lifecycle(g_qos, &p.autopurge_nowriter_samples_delay, &p.autopurge_disposed_samples_delay); + cr_assert_eq(p.autopurge_nowriter_samples_delay, g_pol_reader_data_lifecycle.autopurge_nowriter_samples_delay); + cr_assert_eq(p.autopurge_disposed_samples_delay, g_pol_reader_data_lifecycle.autopurge_disposed_samples_delay); +} + +Test(ddsc_qos, durability_service, .init=qos_init, .fini=qos_fini) +{ + struct pol_durability_service p = { 0 }; + + /* NULLs shouldn't crash and be a noops. */ + dds_qset_durability_service(NULL, + g_pol_durability_service.service_cleanup_delay, + g_pol_durability_service.history_kind, + g_pol_durability_service.history_depth, + g_pol_durability_service.max_samples, + g_pol_durability_service.max_instances, + g_pol_durability_service.max_samples_per_instance); + dds_qget_durability_service(NULL, + &p.service_cleanup_delay, + &p.history_kind, + &p.history_depth, + &p.max_samples, + &p.max_instances, + &p.max_samples_per_instance); + dds_qget_durability_service(g_qos, + NULL, + NULL, + NULL, + NULL, + NULL, + NULL); + + /* Getting after setting, should yield the original input. */ + dds_qset_durability_service(g_qos, + g_pol_durability_service.service_cleanup_delay, + g_pol_durability_service.history_kind, + g_pol_durability_service.history_depth, + g_pol_durability_service.max_samples, + g_pol_durability_service.max_instances, + g_pol_durability_service.max_samples_per_instance); + dds_qget_durability_service(g_qos, + &p.service_cleanup_delay, + &p.history_kind, + &p.history_depth, + &p.max_samples, + &p.max_instances, + &p.max_samples_per_instance); + cr_assert_eq(p.service_cleanup_delay, g_pol_durability_service.service_cleanup_delay); + cr_assert_eq(p.history_kind, g_pol_durability_service.history_kind); + cr_assert_eq(p.history_depth, g_pol_durability_service.history_depth); + cr_assert_eq(p.max_samples, g_pol_durability_service.max_samples); + cr_assert_eq(p.max_instances, g_pol_durability_service.max_instances); + cr_assert_eq(p.max_samples_per_instance, g_pol_durability_service.max_samples_per_instance); +} + +#endif + + +#pragma warning(pop) diff --git a/src/core/ddsc/tests/querycondition.c b/src/core/ddsc/tests/querycondition.c new file mode 100644 index 0000000..0310744 --- /dev/null +++ b/src/core/ddsc/tests/querycondition.c @@ -0,0 +1,1521 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include "os/os.h" +#include +#include +#include +#include "Space.h" + +/* Add --verbose command line argument to get the cr_log_info traces (if there are any). */ + +//#define VERBOSE_INIT +#ifdef VERBOSE_INIT +#define PRINT_SAMPLE(info, sample) cr_log_info("%s (%d, %d, %d)\n", info, sample.long_1, sample.long_2, sample.long_3); +#else +#define PRINT_SAMPLE(info, sample) +#endif + + + +/************************************************************************************************** + * + * Test fixtures + * + *************************************************************************************************/ +#define MAX_SAMPLES 7 +/* + * By writing, disposing, unregistering, reading and re-writing, the following + * data will be available in the reader history and thus available for the + * condition that is under test. + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ +#define SAMPLE_ALIVE_IST_CNT (3) +#define SAMPLE_DISPOSED_IST_CNT (2) +#define SAMPLE_NO_WRITER_IST_CNT (2) +#define SAMPLE_LAST_READ_SST (2) +#define SAMPLE_LAST_OLD_VST (3) +#define SAMPLE_IST(idx) (((idx % 3) == 0) ? DDS_IST_ALIVE : \ + ((idx % 3) == 1) ? DDS_IST_NOT_ALIVE_DISPOSED : \ + DDS_IST_NOT_ALIVE_NO_WRITERS ) +#define SAMPLE_VST(idx) ((idx <= SAMPLE_LAST_OLD_VST ) ? DDS_VST_OLD : DDS_VST_NEW) +#define SAMPLE_SST(idx) ((idx <= SAMPLE_LAST_READ_SST) ? DDS_SST_READ : DDS_SST_NOT_READ) + + +static dds_entity_t g_participant = 0; +static dds_entity_t g_topic = 0; +static dds_entity_t g_reader = 0; +static dds_entity_t g_writer = 0; +static dds_entity_t g_waitset = 0; + +static void* g_samples[MAX_SAMPLES]; +static Space_Type1 g_data[MAX_SAMPLES]; +static dds_sample_info_t g_info[MAX_SAMPLES]; + +static bool +filter_mod2(const void * sample) +{ + const Space_Type1 *s = sample; + return (s->long_1 % 2 == 0); +} + +static char* +create_topic_name(const char *prefix, char *name, size_t size) +{ + /* Get semi random g_topic name. */ + os_procId pid = os_procIdSelf(); + uintmax_t tid = os_threadIdToInteger(os_threadIdSelf()); + (void) snprintf(name, size, "%s_pid%"PRIprocId"_tid%"PRIuMAX"", prefix, pid, tid); + return name; +} + +static void +querycondition_init(void) +{ + Space_Type1 sample = { 0 }; + dds_qos_t *qos = dds_qos_create (); + dds_attach_t triggered; + dds_return_t ret; + char name[100]; + + g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_participant, 0, "Failed to create prerequisite g_participant"); + + g_waitset = dds_create_waitset(g_participant); + cr_assert_gt(g_waitset, 0, "Failed to create g_waitset"); + + g_topic = dds_create_topic(g_participant, &Space_Type1_desc, create_topic_name("ddsc_querycondition_test", name, sizeof name), NULL, NULL); + cr_assert_gt(g_topic, 0, "Failed to create prerequisite g_topic"); + + /* Create a reader that keeps last sample of all instances. */ + dds_qset_history(qos, DDS_HISTORY_KEEP_LAST, 1); + g_reader = dds_create_reader(g_participant, g_topic, qos, NULL); + cr_assert_gt(g_reader, 0, "Failed to create prerequisite g_reader"); + + /* Create a reader that will not automatically dispose unregistered samples. */ + dds_qset_writer_data_lifecycle(qos, false); + g_writer = dds_create_writer(g_participant, g_topic, qos, NULL); + cr_assert_gt(g_writer, 0, "Failed to create prerequisite g_writer"); + + /* Sync g_reader to g_writer. */ + ret = dds_set_enabled_status(g_reader, DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_reader status"); + ret = dds_waitset_attach(g_waitset, g_reader, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_reader"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_reader r"); + cr_assert_eq(g_reader, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_reader a"); + ret = dds_waitset_detach(g_waitset, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_reader"); + + /* Sync g_writer to g_reader. */ + ret = dds_set_enabled_status(g_writer, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_writer status"); + ret = dds_waitset_attach(g_waitset, g_writer, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_writer"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_writer r"); + cr_assert_eq(g_writer, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_writer a"); + ret = dds_waitset_detach(g_waitset, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_writer"); + + /* Initialize reading buffers. */ + memset (g_data, 0, sizeof (g_data)); + for (int i = 0; i < MAX_SAMPLES; i++) { + g_samples[i] = &g_data[i]; + } + + /* Write all samples. */ + for (int i = 0; i < MAX_SAMPLES; i++) { + dds_instance_state_t ist = SAMPLE_IST(i); + sample.long_1 = i; + sample.long_2 = i/2; + sample.long_3 = i/3; + + PRINT_SAMPLE("INIT: Write ", sample); + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + + if (ist == DDS_IST_NOT_ALIVE_DISPOSED) { + PRINT_SAMPLE("INIT: Dispose ", sample); + ret = dds_dispose(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite dispose"); + } + if (ist == DDS_IST_NOT_ALIVE_NO_WRITERS) { + PRINT_SAMPLE("INIT: Unregister", sample); + ret = dds_unregister_instance(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite unregister"); + } + } + + /* Read samples to get read&old_view states. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, SAMPLE_LAST_OLD_VST + 1); + cr_assert_eq(ret, SAMPLE_LAST_OLD_VST + 1, "Failed prerequisite read"); +#ifdef VERBOSE_INIT + for(int i = 0; i < ret; i++) { + Space_Type1 *s = (Space_Type1*)g_samples[i]; + PRINT_SAMPLE("INIT: Read ", (*s)); + } +#endif + + /* Re-write the samples that should be not_read&old_view. */ + for (int i = SAMPLE_LAST_READ_SST + 1; i <= SAMPLE_LAST_OLD_VST; i++) { + dds_instance_state_t ist = SAMPLE_IST(i); + sample.long_1 = i; + sample.long_2 = i/2; + sample.long_3 = i/3; + + PRINT_SAMPLE("INIT: Rewrite ", sample); + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + + if ((ist == DDS_IST_NOT_ALIVE_DISPOSED) && (i != 4)) { + PRINT_SAMPLE("INIT: Dispose ", sample); + ret = dds_dispose(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite dispose"); + } + if (ist == DDS_IST_NOT_ALIVE_NO_WRITERS) { + PRINT_SAMPLE("INIT: Unregister", sample); + ret = dds_unregister_instance(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite unregister"); + } + } + + dds_qos_delete(qos); +} + +static void +querycondition_fini(void) +{ + dds_delete(g_reader); + dds_delete(g_writer); + dds_delete(g_waitset); + dds_delete(g_topic); + dds_delete(g_participant); +} + + +#if 0 +#else +/************************************************************************************************** + * + * These will check the querycondition creation in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_querycondition_create, second, .init=querycondition_init, .fini=querycondition_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t cond1; + dds_entity_t cond2; + dds_return_t ret; + + cond1 = dds_create_querycondition(g_reader, mask, filter_mod2); + cr_assert_gt(cond1, 0, "dds_create_querycondition(): returned %d", dds_err_nr(cond1)); + + cond2 = dds_create_querycondition(g_reader, mask, filter_mod2); + cr_assert_gt(cond2, 0, "dds_create_querycondition(): returned %d", dds_err_nr(cond2)); + + /* Also, we should be able to delete both. */ + ret = dds_delete(cond1); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_delete(): returned %d", dds_err_nr(ret)); + + /* And, of course, be able to delete the first one (return code isn't checked in the test fixtures). */ + ret = dds_delete(cond2); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_delete(): returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_create, deleted_reader, .init=querycondition_init, .fini=querycondition_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t cond; + dds_delete(g_reader); + cond = dds_create_querycondition(g_reader, mask, filter_mod2); + cr_assert_eq(dds_err_nr(cond), DDS_RETCODE_ALREADY_DELETED, "dds_create_querycondition(): returned %d", dds_err_nr(cond)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_querycondition_create, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_querycondition_create, invalid_readers, .init=querycondition_init, .fini=querycondition_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_entity_t cond; + + cond = dds_create_querycondition(rdr, mask, filter_mod2); + cr_assert_eq(dds_err_nr(cond), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(cond), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_querycondition_create, non_readers) = { + DataPoints(dds_entity_t*, &g_topic, &g_participant), +}; +Theory((dds_entity_t *rdr), ddsc_querycondition_create, non_readers, .init=querycondition_init, .fini=querycondition_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t cond; + cond = dds_create_querycondition(*rdr, mask, filter_mod2); + cr_assert_eq(dds_err_nr(cond), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(cond)); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the querycondition mask acquiring in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_querycondition_get_mask, deleted, .init=querycondition_init, .fini=querycondition_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t condition; + dds_return_t ret; + condition = dds_create_querycondition(g_reader, mask, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + dds_delete(condition); + mask = 0; + ret = dds_get_mask(condition, &mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_get_mask, null, .init=querycondition_init, .fini=querycondition_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t condition; + dds_return_t ret; + condition = dds_create_querycondition(g_reader, mask, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_get_mask(condition, NULL); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_querycondition_get_mask, invalid_conditions) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t cond), ddsc_querycondition_get_mask, invalid_conditions, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + uint32_t mask; + + ret = dds_get_mask(cond, &mask); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_querycondition_get_mask, non_conditions) = { + DataPoints(dds_entity_t*, &g_reader, &g_topic, &g_participant), +}; +Theory((dds_entity_t *cond), ddsc_querycondition_get_mask, non_conditions, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_return_t ret; + uint32_t mask; + ret = dds_get_mask(*cond, &mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_querycondition_get_mask, various_masks) = { + DataPoints(uint32_t, DDS_ANY_SAMPLE_STATE, DDS_READ_SAMPLE_STATE, DDS_NOT_READ_SAMPLE_STATE), + DataPoints(uint32_t, DDS_ANY_VIEW_STATE, DDS_NEW_VIEW_STATE, DDS_NOT_NEW_VIEW_STATE), + DataPoints(uint32_t, DDS_ANY_INSTANCE_STATE, DDS_ALIVE_INSTANCE_STATE, DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE, DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE), +}; +Theory((uint32_t ss, uint32_t vs, uint32_t is), ddsc_querycondition_get_mask, various_masks, .init=querycondition_init, .fini=querycondition_fini) +{ + uint32_t maskIn = ss | vs | is; + uint32_t maskOut = 0xFFFFFFFF; + dds_entity_t condition; + dds_return_t ret; + + condition = dds_create_querycondition(g_reader, maskIn, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + ret = dds_get_mask(condition, &maskOut); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %d", dds_err_nr(ret)); + cr_assert_eq(maskIn, maskOut); + + dds_delete(condition); +} +/*************************************************************************************************/ + + + + +/************************************************************************************************** + * + * These will check the querycondition reading in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_querycondition_read, any, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all samples that matches filter. */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 4, "# read %d, expected %d", ret, 4); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_querycondition_read::any: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i * 2; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_read, not_read_sample_state, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all non-read samples and matches filter. */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_querycondition_read::not_read_sample_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i == 0) ? 4 : 6; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_read, read_sample_state, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all already read samples and matches filter. */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_querycondition_read::read_sample_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i * 2; + dds_sample_state_t expected_sst = DDS_SST_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_read, new_view_state, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all new-view samples and matches filter. */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_querycondition_read::new_view_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i == 0) ? 4 : 6; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = DDS_VST_NEW; + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_read, not_new_view_state, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all old-view samples and matches filter. */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_querycondition_read::not_new_view_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i * 2; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = DDS_VST_OLD; + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_read, alive_instance_state, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all alive samples and matches filter. */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_querycondition_read::alive_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i == 0) ? 0 : 6; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_ALIVE; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_read, disposed_instance_state, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all disposed samples and matches filter. */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 1, "# read %d, expected %d", ret, 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_querycondition_read::disposed_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 4; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_NOT_ALIVE_DISPOSED; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_read, no_writers_instance_state, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all samples without a writer and matches filter. */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 1, "# read %d, expected %d", ret, 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_querycondition_read::no_writers_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 2; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_NOT_ALIVE_NO_WRITERS; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_read, combination_of_states, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all samples that match the mask and filter (should be only one). */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 1, "# read %d, expected %d", ret, 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_querycondition_read::combination_of_states: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 6; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = DDS_VST_NEW; + dds_instance_state_t expected_ist = DDS_IST_ALIVE; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_read, none, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all samples that match the mask AND filter (should be none). */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 0, "# read %d, expected %d", ret, 0); + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_read, with_mask, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all samples that match the or'd masks. */ + ret = dds_read_mask(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, + DDS_NOT_READ_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_querycondition_read::with_mask: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i == 0) ? 4 : 6; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_read, already_deleted, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Delete condition. */ + ret = dds_delete(condition); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to delete prerequisite condition"); + + /* Try to read with a deleted condition. */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_expect_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + + + + + + + + +/************************************************************************************************** + * + * These will check the querycondition taking in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_querycondition_take, any, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all samples that match the filter. */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 4, "# read %d, expected %d", ret, 4); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_querycondition_take::any: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = i * 2; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_take, not_read_sample_state, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all non-read samples that match the filter. */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_querycondition_take::not_read_sample_state: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i == 0) ? 4 : 6; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_take, read_sample_state, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all already read samples that match the filter. */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_querycondition_take::read_sample_state: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = i * 2; + dds_sample_state_t expected_sst = DDS_SST_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_take, new_view_state, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all new-view samples that match the filter. */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_querycondition_take::new_view_state: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i == 0) ? 4 : 6; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = DDS_VST_NEW; + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_take, not_new_view_state, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all old-view samples that match the filter. */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_querycondition_take::not_new_view_state: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = i * 2; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = DDS_VST_OLD; + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_take, alive_instance_state, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all alive samples that match the filter. */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_querycondition_take::alive_instance_state: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = i * 6; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_ALIVE; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_take, disposed_instance_state, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all disposed samples that match the filter. */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 1, "# read %d, expected %d", ret, 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_querycondition_take::disposed_instance_state: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = 4; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_NOT_ALIVE_DISPOSED; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_take, no_writers_instance_state, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all samples without a writer that match the filter. */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 1, "# read %d, expected %d", ret, 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_querycondition_take::no_writers_instance_state: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = 2; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_NOT_ALIVE_NO_WRITERS; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_take, combination_of_states, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all samples that match the mask and the filter. */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 1, "# read %d, expected %d", ret, 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_querycondition_take::combination_of_states: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = 6; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = DDS_VST_NEW; + dds_instance_state_t expected_ist = DDS_IST_ALIVE; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_take, none, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all samples that match the mask AND filter (should be none). */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 0, "# read %d, expected %d", ret, 0); + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_take, with_mask, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all samples that match the or'd masks and match the filter. */ + ret = dds_take_mask(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, + DDS_NOT_READ_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_querycondition_take::with_mask: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i == 0) ? 4 : 6; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_querycondition_take, already_deleted, .init=querycondition_init, .fini=querycondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE, filter_mod2); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Delete condition. */ + ret = dds_delete(condition); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to delete prerequisite condition"); + + /* Try to take with a deleted condition. */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_expect_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + + + +#endif diff --git a/src/core/ddsc/tests/read_instance.c b/src/core/ddsc/tests/read_instance.c new file mode 100644 index 0000000..cf76f0a --- /dev/null +++ b/src/core/ddsc/tests/read_instance.c @@ -0,0 +1,1258 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include + +#include "ddsc/dds.h" +#include "os/os.h" +#include "Space.h" +#include "RoundTrip.h" +#include +#include +#include + + +#if 0 +#define PRINT_SAMPLE(info, sample) cr_log_info("%s (%d, %d, %d)\n", info, sample.long_1, sample.long_2, sample.long_3); +#else +#define PRINT_SAMPLE(info, sample) +#endif + + + +/************************************************************************************************** + * + * Test fixtures + * + *************************************************************************************************/ +#define MAX_SAMPLES 5 +/* + * By writing, disposing, unregistering, reading and re-writing, the following + * data will be available in the reader history (but not in this order). + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ +#define SAMPLE_IST(idx) ((idx <= 2) ? DDS_IST_ALIVE : \ + (idx == 3) ? DDS_IST_NOT_ALIVE_DISPOSED : \ + DDS_IST_NOT_ALIVE_NO_WRITERS ) +#define SAMPLE_VST(idx) ((idx <= 1) ? DDS_VST_OLD : DDS_VST_NEW) +#define SAMPLE_SST(idx) ((idx == 0) ? DDS_SST_READ : DDS_SST_NOT_READ) + +static dds_entity_t g_participant = 0; +static dds_entity_t g_subscriber = 0; +static dds_entity_t g_publisher = 0; +static dds_entity_t g_topic = 0; +static dds_entity_t g_reader = 0; +static dds_entity_t g_writer = 0; +static dds_entity_t g_waitset = 0; +static dds_entity_t g_rcond = 0; +static dds_entity_t g_qcond = 0; + +static void* g_loans[MAX_SAMPLES]; +static void* g_samples[MAX_SAMPLES]; +static Space_Type1 g_data[MAX_SAMPLES]; +static dds_sample_info_t g_info[MAX_SAMPLES]; + +static dds_instance_handle_t g_hdl_valid; +static dds_instance_handle_t g_hdl_nil = DDS_HANDLE_NIL; + +static bool +filter_mod2(const void * sample) +{ + const Space_Type1 *s = sample; + return (s->long_2 % 2 == 0); +} + +static char* +create_topic_name(const char *prefix, char *name, size_t size) +{ + /* Get semi random g_topic name. */ + os_procId pid = os_procIdSelf(); + uintmax_t tid = os_threadIdToInteger(os_threadIdSelf()); + (void) snprintf(name, size, "%s_pid%"PRIprocId"_tid%"PRIuMAX"", prefix, pid, tid); + return name; +} + +static void +read_instance_init(void) +{ + Space_Type1 sample = { 0 }; + dds_attach_t triggered; + dds_return_t ret; + char name[100]; + dds_qos_t *qos; + + qos = dds_qos_create(); + cr_assert_not_null(qos, "Failed to create prerequisite qos"); + + g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_participant, 0, "Failed to create prerequisite g_participant"); + + g_subscriber = dds_create_subscriber(g_participant, NULL, NULL); + cr_assert_gt(g_subscriber, 0, "Failed to create prerequisite g_subscriber"); + + g_publisher = dds_create_publisher(g_participant, NULL, NULL); + cr_assert_gt(g_publisher, 0, "Failed to create prerequisite g_publisher"); + + g_waitset = dds_create_waitset(g_participant); + cr_assert_gt(g_waitset, 0, "Failed to create g_waitset"); + + g_topic = dds_create_topic(g_participant, &Space_Type1_desc, create_topic_name("ddsc_read_instance_test", name, sizeof name), NULL, NULL); + cr_assert_gt(g_topic, 0, "Failed to create prerequisite g_topic"); + + /* Create a writer that will not automatically dispose unregistered samples. */ + dds_qset_writer_data_lifecycle(qos, false); + g_writer = dds_create_writer(g_publisher, g_topic, qos, NULL); + cr_assert_gt(g_writer, 0, "Failed to create prerequisite g_writer"); + + /* Create a reader that keeps all samples when not taken. */ + dds_qset_history(qos, DDS_HISTORY_KEEP_ALL, DDS_LENGTH_UNLIMITED); + g_reader = dds_create_reader(g_subscriber, g_topic, qos, NULL); + cr_assert_gt(g_reader, 0, "Failed to create prerequisite g_reader"); + + /* Create a read condition that only reads not_read samples. */ + g_rcond = dds_create_readcondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(g_rcond, 0, "Failed to create prerequisite g_rcond"); + + /* Create a query condition that only reads not_read samples of instances mod2. */ + g_qcond = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE, filter_mod2); + cr_assert_gt(g_qcond, 0, "Failed to create prerequisite g_qcond"); + + /* Sync g_reader to g_writer. */ + ret = dds_set_enabled_status(g_reader, DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_reader status"); + ret = dds_waitset_attach(g_waitset, g_reader, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_reader"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_reader r"); + cr_assert_eq(g_reader, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_reader a"); + ret = dds_waitset_detach(g_waitset, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_reader"); + + /* Sync g_writer to g_reader. */ + ret = dds_set_enabled_status(g_writer, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_writer status"); + ret = dds_waitset_attach(g_waitset, g_writer, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_writer"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_writer r"); + cr_assert_eq(g_writer, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_writer a"); + ret = dds_waitset_detach(g_waitset, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_writer"); + + /* Initialize reading buffers. */ + memset (g_data, 0, sizeof (g_data)); + for (int i = 0; i < MAX_SAMPLES; i++) { + g_samples[i] = &g_data[i]; + } + for (int i = 0; i < MAX_SAMPLES; i++) { + g_loans[i] = NULL; + } + + /* Write the sample that will become {sst(read), vst(old), ist(alive)}. */ + sample.long_1 = 0; + sample.long_2 = 0; + sample.long_3 = 0; + PRINT_SAMPLE("INIT: Write ", sample); + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + /* | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | not_read | new | alive | + */ + + /* Read sample that will become {sst(read), vst(old), ist(alive)}. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 1, "Failed prerequisite read"); + for(int i = 0; i < ret; i++) { + Space_Type1 *s = (Space_Type1*)g_samples[i]; + PRINT_SAMPLE("INIT: Read ", (*s)); + } + /* | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + */ + + /* Write the sample that will become {sst(not_read), vst(old), ist(alive)}. */ + sample.long_1 = 0; + sample.long_2 = 1; + sample.long_3 = 2; + PRINT_SAMPLE("INIT: Write ", sample); + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + /* | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | + */ + + /* Write the samples that will become {sst(not_read), vst(new), ist(*)}. */ + for (int i = 2; i < MAX_SAMPLES; i++) { + sample.long_1 = i - 1; + sample.long_2 = i; + sample.long_3 = i*2; + PRINT_SAMPLE("INIT: Write ", sample); + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + } + /* | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | alive | + * | 3 | 4 | 8 | not_read | new | alive | + */ + + /* Dispose the sample that will become {sst(not_read), vst(new), ist(disposed)}. */ + sample.long_1 = 2; + sample.long_2 = 3; + sample.long_3 = 6; + PRINT_SAMPLE("INIT: Dispose ", sample); + ret = dds_dispose(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite dispose"); + /* | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | alive | + */ + + /* Unregister the sample that will become {sst(not_read), vst(new), ist(no_writers)}. */ + sample.long_1 = 3; + sample.long_2 = 4; + sample.long_3 = 8; + PRINT_SAMPLE("INIT: Unregister", sample); + ret = dds_unregister_instance(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite unregister"); + /* | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + + /* Get valid instance handle. */ + sample.long_1 = 0; + sample.long_2 = 0; + sample.long_3 = 0; + g_hdl_valid = dds_instance_lookup(g_reader, &sample); + cr_assert_neq(g_hdl_valid, DDS_HANDLE_NIL, "Failed prerequisite dds_instance_lookup"); + + dds_qos_delete(qos); +} + +static void +read_instance_fini(void) +{ + dds_delete(g_rcond); + dds_delete(g_qcond); + dds_delete(g_reader); + dds_delete(g_writer); + dds_delete(g_subscriber); + dds_delete(g_publisher); + dds_delete(g_waitset); + dds_delete(g_topic); + dds_delete(g_participant); +} + +static dds_return_t +samples_cnt(void) +{ + dds_return_t ret; + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_geq(ret, 0, "Failed samples count read: %d", dds_err_nr(ret)); + return ret; +} + + + + + +/************************************************************************************************** + * + * These will check the read_instance_* functions with invalid parameters. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance, invalid_params) = { + DataPoints(dds_entity_t*, &g_reader, &g_rcond, &g_qcond), + DataPoints(void**, g_samples, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0), + DataPoints(size_t, 0, 2, MAX_SAMPLES), + DataPoints(uint32_t, 0, 2, MAX_SAMPLES), +}; +Theory((dds_entity_t *ent, void **buf, dds_sample_info_t *si, size_t bufsz, uint32_t maxs), ddsc_read_instance, invalid_params, .init=read_instance_init, .fini=read_instance_fini) +{ + dds_return_t ret; + /* The only valid permutation is when non of the buffer values are + * invalid and neither is the handle. So, don't test that. */ + cr_assume((buf != g_samples) || (si != g_info) || (bufsz == 0) || (maxs == 0) || (bufsz < maxs)); + /* TODO: CHAM-306, currently, a buffer is automatically 'promoted' to a loan when a buffer is + * provided with NULL pointers. So, in fact, there's currently no real difference between calling + * dds_read() dds_read_wl() (except for the provided bufsz). This will change, which means that + * the given buffer should contain valid pointers, which again means that 'loan intended' buffer + * should result in bad_parameter. + * However, that's not the case yet. So don't test it. */ + cr_assume(buf != g_loans); + ret = dds_read_instance(*ent, buf, si, bufsz, maxs, g_hdl_valid); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance_wl, invalid_params) = { + DataPoints(dds_entity_t*, &g_reader, &g_rcond, &g_qcond), + DataPoints(void**, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0), + DataPoints(size_t, 0, 2, MAX_SAMPLES), +}; +Theory((dds_entity_t *ent, void **buf, dds_sample_info_t *si, uint32_t maxs), ddsc_read_instance_wl, invalid_params, .init=read_instance_init, .fini=read_instance_fini) +{ + dds_return_t ret; + /* The only valid permutation is when non of the buffer values are + * invalid and neither is the handle. So, don't test that. */ + cr_assume((buf != g_loans) || (si != g_info) || (maxs == 0)); + ret = dds_read_instance_wl(*ent, buf, si, maxs, g_hdl_valid); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance_mask, invalid_params) = { + DataPoints(dds_entity_t*, &g_reader, &g_rcond, &g_qcond), + DataPoints(void**, g_samples, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0), + DataPoints(size_t, 0, 2, MAX_SAMPLES), + DataPoints(uint32_t, 0, 2, MAX_SAMPLES), +}; +Theory((dds_entity_t *ent, void **buf, dds_sample_info_t *si, size_t bufsz, uint32_t maxs), ddsc_read_instance_mask, invalid_params, .init=read_instance_init, .fini=read_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + /* The only valid permutation is when non of the buffer values are + * invalid and neither is the handle. So, don't test that. */ + cr_assume((buf != g_samples) || (si != g_info) || (bufsz == 0) || (maxs == 0) || (bufsz < maxs)); + /* TODO: CHAM-306, currently, a buffer is automatically 'promoted' to a loan when a buffer is + * provided with NULL pointers. So, in fact, there's currently no real difference between calling + * dds_read() dds_read_wl() (except for the provided bufsz). This will change, which means that + * the given buffer should contain valid pointers, which again means that 'loan intended' buffer + * should result in bad_parameter. + * However, that's not the case yet. So don't test it. */ + cr_assume(buf != g_loans); + ret = dds_read_instance_mask(*ent, buf, si, bufsz, maxs, g_hdl_valid, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance_mask_wl, invalid_params) = { + DataPoints(dds_entity_t*, &g_reader, &g_rcond, &g_qcond), + DataPoints(void**, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0), + DataPoints(size_t, 0, 2, MAX_SAMPLES), +}; +Theory((dds_entity_t *ent, void **buf, dds_sample_info_t *si, uint32_t maxs), ddsc_read_instance_mask_wl, invalid_params, .init=read_instance_init, .fini=read_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + /* The only valid permutation is when non of the buffer values are + * invalid and neither is the handle. So, don't test that. */ + cr_assume((buf != g_loans) || (si != g_info) || (maxs == 0)); + ret = dds_read_instance_mask_wl(*ent, buf, si, maxs, g_hdl_valid, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the read_instance_* functions with invalid handles. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance, invalid_handles) = { + DataPoints(dds_entity_t*, &g_reader, &g_rcond, &g_qcond), + DataPoints(dds_instance_handle_t, DDS_HANDLE_NIL, 0, 1, 100, UINT64_MAX), +}; +Theory((dds_entity_t *rdr, dds_instance_handle_t hdl), ddsc_read_instance, invalid_handles, .init=read_instance_init, .fini=read_instance_fini) +{ + dds_return_t ret; + ret = dds_read_instance(*rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, hdl); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_PRECONDITION_NOT_MET, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance_wl, invalid_handles) = { + DataPoints(dds_entity_t*, &g_reader, &g_rcond, &g_qcond), + DataPoints(dds_instance_handle_t, DDS_HANDLE_NIL, 0, 1, 100, UINT64_MAX), +}; +Theory((dds_entity_t *rdr, dds_instance_handle_t hdl), ddsc_read_instance_wl, invalid_handles, .init=read_instance_init, .fini=read_instance_fini) +{ + dds_return_t ret; + ret = dds_read_instance_wl(*rdr, g_loans, g_info, MAX_SAMPLES, hdl); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_PRECONDITION_NOT_MET, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance_mask, invalid_handles) = { + DataPoints(dds_entity_t*, &g_reader, &g_rcond, &g_qcond), + DataPoints(dds_instance_handle_t, DDS_HANDLE_NIL, 0, 1, 100, UINT64_MAX), +}; +Theory((dds_entity_t *rdr, dds_instance_handle_t hdl), ddsc_read_instance_mask, invalid_handles, .init=read_instance_init, .fini=read_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + ret = dds_read_instance_mask(*rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, hdl, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_PRECONDITION_NOT_MET, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance_mask_wl, invalid_handles) = { + DataPoints(dds_entity_t*, &g_reader, &g_rcond, &g_qcond), + DataPoints(dds_instance_handle_t, DDS_HANDLE_NIL, 0, 1, 100, UINT64_MAX), +}; +Theory((dds_entity_t *rdr, dds_instance_handle_t hdl), ddsc_read_instance_mask_wl, invalid_handles, .init=read_instance_init, .fini=read_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + ret = dds_read_instance_mask_wl(*rdr, g_loans, g_info, MAX_SAMPLES, hdl, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_PRECONDITION_NOT_MET, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the read_instance_* functions with invalid readers. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_read_instance, invalid_readers, .init=read_instance_init, .fini=read_instance_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_read_instance(rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance_wl, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_read_instance_wl, invalid_readers, .init=read_instance_init, .fini=read_instance_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_read_instance_wl(rdr, g_loans, g_info, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance_mask, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_read_instance_mask, invalid_readers, .init=read_instance_init, .fini=read_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_read_instance_mask(rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance_mask_wl, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_read_instance_mask_wl, invalid_readers, .init=read_instance_init, .fini=read_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_read_instance_mask_wl(rdr, g_loans, g_info, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + + + + + + +/************************************************************************************************** + * + * These will check the read_instance_* functions with non readers. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_publisher, &g_waitset), +}; +Theory((dds_entity_t *rdr), ddsc_read_instance, non_readers, .init=read_instance_init, .fini=read_instance_fini) +{ + dds_return_t ret; + ret = dds_read_instance(*rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance_wl, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_publisher, &g_waitset), +}; +Theory((dds_entity_t *rdr), ddsc_read_instance_wl, non_readers, .init=read_instance_init, .fini=read_instance_fini) +{ + dds_return_t ret; + ret = dds_read_instance_wl(*rdr, g_loans, g_info, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance_mask, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_publisher, &g_waitset), +}; +Theory((dds_entity_t *rdr), ddsc_read_instance_mask, non_readers, .init=read_instance_init, .fini=read_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + ret = dds_read_instance_mask(*rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance_mask_wl, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_publisher, &g_waitset), +}; +Theory((dds_entity_t *rdr), ddsc_read_instance_mask_wl, non_readers, .init=read_instance_init, .fini=read_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + ret = dds_read_instance_mask_wl(*rdr, g_loans, g_info, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + + + + +/************************************************************************************************** + * + * These will check the read_instance_* functions with deleted readers. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance, already_deleted) = { + DataPoints(dds_entity_t*, &g_rcond, &g_qcond, &g_reader), +}; +Theory((dds_entity_t *rdr), ddsc_read_instance, already_deleted, .init=read_instance_init, .fini=read_instance_fini) +{ + dds_return_t ret; + ret = dds_delete(*rdr); + cr_assert_eq(ret, DDS_RETCODE_OK, "prerequisite delete failed: %d", dds_err_nr(ret)); + ret = dds_read_instance(*rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance_wl, already_deleted) = { + DataPoints(dds_entity_t*, &g_rcond, &g_qcond, &g_reader), +}; +Theory((dds_entity_t *rdr), ddsc_read_instance_wl, already_deleted, .init=read_instance_init, .fini=read_instance_fini) +{ + dds_return_t ret; + ret = dds_delete(*rdr); + cr_assert_eq(ret, DDS_RETCODE_OK, "prerequisite delete failed: %d", dds_err_nr(ret)); + ret = dds_read_instance_wl(*rdr, g_loans, g_info, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance_mask, already_deleted) = { + DataPoints(dds_entity_t*, &g_rcond, &g_qcond, &g_reader), +}; +Theory((dds_entity_t *rdr), ddsc_read_instance_mask, already_deleted, .init=read_instance_init, .fini=read_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + ret = dds_delete(*rdr); + cr_assert_eq(ret, DDS_RETCODE_OK, "prerequisite delete failed: %d", dds_err_nr(ret)); + ret = dds_read_instance_mask(*rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_instance_mask_wl, already_deleted) = { + DataPoints(dds_entity_t*, &g_rcond, &g_qcond, &g_reader), +}; +Theory((dds_entity_t *rdr), ddsc_read_instance_mask_wl, already_deleted, .init=read_instance_init, .fini=read_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + ret = dds_delete(*rdr); + cr_assert_eq(ret, DDS_RETCODE_OK, "prerequisite delete failed: %d", dds_err_nr(ret)); + ret = dds_read_instance_mask_wl(*rdr, g_loans, g_info, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + + + + +/************************************************************************************************** + * + * These will check the read_instance_* functions with a valid reader. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_read_instance, reader, .init=read_instance_init, .fini=read_instance_fini) +{ + dds_return_t expected_cnt = 2; + dds_return_t ret; + + ret = dds_read_instance(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 0 | 1 | 2 | not_read | old | alive | <--- + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_read_instance::reader: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_instance_wl, reader, .init=read_instance_init, .fini=read_instance_fini) +{ + dds_return_t expected_cnt = 2; + dds_return_t ret; + + ret = dds_read_instance_wl(g_reader, g_loans, g_info, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 0 | 1 | 2 | not_read | old | alive | <--- + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_read_instance_wl::reader: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_instance_mask, reader, .init=read_instance_init, .fini=read_instance_fini) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_read_instance_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | <--- + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_read_instance_mask::reader: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 1; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_instance_mask_wl, reader, .init=read_instance_init, .fini=read_instance_fini) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_read_instance_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | <--- + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_read_instance_mask_wl::reader: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 1; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + + + + + + +/************************************************************************************************** + * + * These will check the read_instance_* functions with a valid readcondition. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_read_instance, readcondition, .init=read_instance_init, .fini=read_instance_fini) +{ + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_read_instance(g_rcond, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | <--- + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_read_instance::readcondition: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 1; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_instance_wl, readcondition, .init=read_instance_init, .fini=read_instance_fini) +{ + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_read_instance_wl(g_rcond, g_loans, g_info, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | <--- + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_read_instance_wl::readcondition: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 1; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_instance_mask, readcondition, .init=read_instance_init, .fini=read_instance_fini) +{ + uint32_t mask = DDS_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 2; + dds_return_t ret; + + ret = dds_read_instance_mask(g_rcond, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 0 | 1 | 2 | not_read | old | alive | <--- + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_read_instance_mask::readcondition: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_instance_mask_wl, readcondition, .init=read_instance_init, .fini=read_instance_fini) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_read_instance_mask_wl(g_rcond, g_loans, g_info, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | <--- + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_read_instance_mask_wl::readcondition: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 1; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + + + + + + +/************************************************************************************************** + * + * These will check the read_instance_* functions with a valid querycondition. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_read_instance, querycondition, .init=read_instance_init, .fini=read_instance_fini) +{ + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_read_instance(g_qcond, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 0 | 1 | 2 | not_read | old | alive | + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_read_instance::querycondition: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 0; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_instance_wl, querycondition, .init=read_instance_init, .fini=read_instance_fini) +{ + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_read_instance_wl(g_qcond, g_loans, g_info, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 0 | 1 | 2 | not_read | old | alive | + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_read_instance_wl::querycondition: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 0; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_instance_mask, querycondition, .init=read_instance_init, .fini=read_instance_fini) +{ + uint32_t mask = DDS_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_read_instance_mask(g_qcond, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 0 | 1 | 2 | not_read | old | alive | + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_read_instance_mask::querycondition: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 0; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_instance_mask_wl, querycondition, .init=read_instance_init, .fini=read_instance_fini) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_read_instance_mask_wl(g_qcond, g_loans, g_info, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 0 | 1 | 2 | not_read | old | alive | + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_read_instance_mask_wl::querycondition: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 0; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ diff --git a/src/core/ddsc/tests/readcondition.c b/src/core/ddsc/tests/readcondition.c new file mode 100644 index 0000000..fccf732 --- /dev/null +++ b/src/core/ddsc/tests/readcondition.c @@ -0,0 +1,1514 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include "os/os.h" +#include +#include +#include +#include "Space.h" + +/* Add --verbose command line argument to get the cr_log_info traces (if there are any). */ + +//#define VERBOSE_INIT +#ifdef VERBOSE_INIT +#define PRINT_SAMPLE(info, sample) cr_log_info("%s (%d, %d, %d)\n", info, sample.long_1, sample.long_2, sample.long_3); +#else +#define PRINT_SAMPLE(info, sample) +#endif + + + +/************************************************************************************************** + * + * Test fixtures + * + *************************************************************************************************/ +#define MAX_SAMPLES 7 +/* + * By writing, disposing, unregistering, reading and re-writing, the following + * data will be available in the reader history and thus available for the + * condition that is under test. + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ +#define SAMPLE_ALIVE_IST_CNT (3) +#define SAMPLE_DISPOSED_IST_CNT (2) +#define SAMPLE_NO_WRITER_IST_CNT (2) +#define SAMPLE_LAST_READ_SST (2) +#define SAMPLE_LAST_OLD_VST (3) +#define SAMPLE_IST(idx) (((idx % 3) == 0) ? DDS_IST_ALIVE : \ + ((idx % 3) == 1) ? DDS_IST_NOT_ALIVE_DISPOSED : \ + DDS_IST_NOT_ALIVE_NO_WRITERS ) +#define SAMPLE_VST(idx) ((idx <= SAMPLE_LAST_OLD_VST ) ? DDS_VST_OLD : DDS_VST_NEW) +#define SAMPLE_SST(idx) ((idx <= SAMPLE_LAST_READ_SST) ? DDS_SST_READ : DDS_SST_NOT_READ) + + +static dds_entity_t g_participant = 0; +static dds_entity_t g_topic = 0; +static dds_entity_t g_reader = 0; +static dds_entity_t g_writer = 0; +static dds_entity_t g_waitset = 0; + +static void* g_samples[MAX_SAMPLES]; +static Space_Type1 g_data[MAX_SAMPLES]; +static dds_sample_info_t g_info[MAX_SAMPLES]; + +static char* +create_topic_name(const char *prefix, char *name, size_t size) +{ + /* Get semi random g_topic name. */ + os_procId pid = os_procIdSelf(); + uintmax_t tid = os_threadIdToInteger(os_threadIdSelf()); + (void) snprintf(name, size, "%s_pid%"PRIprocId"_tid%"PRIuMAX"", prefix, pid, tid); + return name; +} + +static void +readcondition_init(void) +{ + Space_Type1 sample = { 0 }; + dds_qos_t *qos = dds_qos_create (); + dds_attach_t triggered; + dds_return_t ret; + char name[100]; + + g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_participant, 0, "Failed to create prerequisite g_participant"); + + g_waitset = dds_create_waitset(g_participant); + cr_assert_gt(g_waitset, 0, "Failed to create g_waitset"); + + g_topic = dds_create_topic(g_participant, &Space_Type1_desc, create_topic_name("ddsc_readcondition_test", name, 100), NULL, NULL); + cr_assert_gt(g_topic, 0, "Failed to create prerequisite g_topic"); + + /* Create a reader that keeps last sample of all instances. */ + dds_qset_history(qos, DDS_HISTORY_KEEP_LAST, 1); + g_reader = dds_create_reader(g_participant, g_topic, qos, NULL); + cr_assert_gt(g_reader, 0, "Failed to create prerequisite g_reader"); + + /* Create a reader that will not automatically dispose unregistered samples. */ + dds_qset_writer_data_lifecycle(qos, false); + g_writer = dds_create_writer(g_participant, g_topic, qos, NULL); + cr_assert_gt(g_writer, 0, "Failed to create prerequisite g_writer"); + + /* Sync g_reader to g_writer. */ + ret = dds_set_enabled_status(g_reader, DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_reader status"); + ret = dds_waitset_attach(g_waitset, g_reader, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_reader"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_reader r"); + cr_assert_eq(g_reader, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_reader a"); + ret = dds_waitset_detach(g_waitset, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_reader"); + + /* Sync g_writer to g_reader. */ + ret = dds_set_enabled_status(g_writer, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_writer status"); + ret = dds_waitset_attach(g_waitset, g_writer, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_writer"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_writer r"); + cr_assert_eq(g_writer, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_writer a"); + ret = dds_waitset_detach(g_waitset, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_writer"); + + /* Initialize reading buffers. */ + memset (g_data, 0, sizeof (g_data)); + for (int i = 0; i < MAX_SAMPLES; i++) { + g_samples[i] = &g_data[i]; + } + + /* Write all samples. */ + for (int i = 0; i < MAX_SAMPLES; i++) { + dds_instance_state_t ist = SAMPLE_IST(i); + sample.long_1 = i; + sample.long_2 = i/2; + sample.long_3 = i/3; + + PRINT_SAMPLE("INIT: Write ", sample); + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + + if (ist == DDS_IST_NOT_ALIVE_DISPOSED) { + PRINT_SAMPLE("INIT: Dispose ", sample); + ret = dds_dispose(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite dispose"); + } + if (ist == DDS_IST_NOT_ALIVE_NO_WRITERS) { + PRINT_SAMPLE("INIT: Unregister", sample); + ret = dds_unregister_instance(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite unregister"); + } + } + + /* Read samples to get read&old_view states. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, SAMPLE_LAST_OLD_VST + 1); + cr_assert_eq(ret, SAMPLE_LAST_OLD_VST + 1, "Failed prerequisite read"); +#ifdef VERBOSE_INIT + for(int i = 0; i < ret; i++) { + Space_Type1 *s = (Space_Type1*)g_samples[i]; + PRINT_SAMPLE("INIT: Read ", (*s)); + } +#endif + + /* Re-write the samples that should be not_read&old_view. */ + for (int i = SAMPLE_LAST_READ_SST + 1; i <= SAMPLE_LAST_OLD_VST; i++) { + dds_instance_state_t ist = SAMPLE_IST(i); + sample.long_1 = i; + sample.long_2 = i/2; + sample.long_3 = i/3; + + PRINT_SAMPLE("INIT: Rewrite ", sample); + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + + if ((ist == DDS_IST_NOT_ALIVE_DISPOSED) && (i != 4)) { + PRINT_SAMPLE("INIT: Dispose ", sample); + ret = dds_dispose(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite dispose"); + } + if (ist == DDS_IST_NOT_ALIVE_NO_WRITERS) { + PRINT_SAMPLE("INIT: Unregister", sample); + ret = dds_unregister_instance(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite unregister"); + } + } + + dds_qos_delete(qos); +} + +static void +readcondition_fini(void) +{ + dds_delete(g_reader); + dds_delete(g_writer); + dds_delete(g_waitset); + dds_delete(g_topic); + dds_delete(g_participant); +} + + +#if 0 +#else +/************************************************************************************************** + * + * These will check the readcondition creation in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_readcondition_create, second, .init=readcondition_init, .fini=readcondition_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t cond1; + dds_entity_t cond2; + dds_return_t ret; + + cond1 = dds_create_readcondition(g_reader, mask); + cr_assert_gt(cond1, 0, "dds_create_readcondition(): returned %d", dds_err_nr(cond1)); + + cond2 = dds_create_readcondition(g_reader, mask); + cr_assert_gt(cond2, 0, "dds_create_readcondition(): returned %d", dds_err_nr(cond2)); + + /* Also, we should be able to delete both. */ + ret = dds_delete(cond1); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_delete(): returned %d", dds_err_nr(ret)); + + /* And, of course, be able to delete the first one (return code isn't checked in the test fixtures). */ + ret = dds_delete(cond2); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_delete(): returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_create, deleted_reader, .init=readcondition_init, .fini=readcondition_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t cond; + dds_delete(g_reader); + cond = dds_create_readcondition(g_reader, mask); + cr_assert_eq(dds_err_nr(cond), DDS_RETCODE_ALREADY_DELETED, "dds_create_readcondition(): returned %d", dds_err_nr(cond)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_readcondition_create, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_readcondition_create, invalid_readers, .init=readcondition_init, .fini=readcondition_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_entity_t cond; + + cond = dds_create_readcondition(rdr, mask); + cr_assert_eq(dds_err_nr(cond), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(cond), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_readcondition_create, non_readers) = { + DataPoints(dds_entity_t*, &g_topic, &g_participant), +}; +Theory((dds_entity_t *rdr), ddsc_readcondition_create, non_readers, .init=readcondition_init, .fini=readcondition_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t cond; + cond = dds_create_readcondition(*rdr, mask); + cr_assert_eq(dds_err_nr(cond), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(cond)); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the readcondition mask acquiring in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_readcondition_get_mask, deleted, .init=readcondition_init, .fini=readcondition_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t condition; + dds_return_t ret; + condition = dds_create_readcondition(g_reader, mask); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + dds_delete(condition); + mask = 0; + ret = dds_get_mask(condition, &mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_get_mask, null, .init=readcondition_init, .fini=readcondition_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t condition; + dds_return_t ret; + condition = dds_create_readcondition(g_reader, mask); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_get_mask(condition, NULL); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_readcondition_get_mask, invalid_conditions) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t cond), ddsc_readcondition_get_mask, invalid_conditions, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + uint32_t mask; + + ret = dds_get_mask(cond, &mask); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_readcondition_get_mask, non_conditions) = { + DataPoints(dds_entity_t*, &g_reader, &g_topic, &g_participant), +}; +Theory((dds_entity_t *cond), ddsc_readcondition_get_mask, non_conditions, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_return_t ret; + uint32_t mask; + ret = dds_get_mask(*cond, &mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_readcondition_get_mask, various_masks) = { + DataPoints(uint32_t, DDS_ANY_SAMPLE_STATE, DDS_READ_SAMPLE_STATE, DDS_NOT_READ_SAMPLE_STATE), + DataPoints(uint32_t, DDS_ANY_VIEW_STATE, DDS_NEW_VIEW_STATE, DDS_NOT_NEW_VIEW_STATE), + DataPoints(uint32_t, DDS_ANY_INSTANCE_STATE, DDS_ALIVE_INSTANCE_STATE, DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE, DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE), +}; +Theory((uint32_t ss, uint32_t vs, uint32_t is), ddsc_readcondition_get_mask, various_masks, .init=readcondition_init, .fini=readcondition_fini) +{ + uint32_t maskIn = ss | vs | is; + uint32_t maskOut = 0xFFFFFFFF; + dds_entity_t condition; + dds_return_t ret; + + condition = dds_create_readcondition(g_reader, maskIn); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + ret = dds_get_mask(condition, &maskOut); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_OK, "returned %d", dds_err_nr(ret)); + cr_assert_eq(maskIn, maskOut); + + dds_delete(condition); +} +/*************************************************************************************************/ + + + + +/************************************************************************************************** + * + * These will check the readcondition reading in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_readcondition_read, any, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all samples. */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, MAX_SAMPLES, "# read %d, expected %d", ret, MAX_SAMPLES); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_readcondition_read::any: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_read, not_read_sample_state, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all non-read samples (should be last part). */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, MAX_SAMPLES - (SAMPLE_LAST_READ_SST + 1), "# read %d, expected %d", ret, MAX_SAMPLES - (SAMPLE_LAST_READ_SST + 1)); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_readcondition_read::not_read_sample_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = SAMPLE_LAST_READ_SST + 1 + i; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_read, read_sample_state, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all already read samples (should be first part). */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, SAMPLE_LAST_READ_SST + 1, "# read %d, expected %d", ret, SAMPLE_LAST_READ_SST + 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_readcondition_read::read_sample_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = DDS_SST_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_read, new_view_state, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all new-view samples (should be last part). */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, MAX_SAMPLES - (SAMPLE_LAST_OLD_VST + 1), "# read %d, expected %d", ret, MAX_SAMPLES - (SAMPLE_LAST_OLD_VST + 1)); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_readcondition_read::new_view_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = SAMPLE_LAST_OLD_VST + 1 + i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = DDS_VST_NEW; + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_read, not_new_view_state, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all old-view samples (should be first part). */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, SAMPLE_LAST_OLD_VST + 1, "# read %d, expected %d", ret, SAMPLE_LAST_OLD_VST + 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_readcondition_read::not_new_view_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = DDS_VST_OLD; + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_read, alive_instance_state, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all alive samples. */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, SAMPLE_ALIVE_IST_CNT, "# read %d, expected %d", ret, SAMPLE_ALIVE_IST_CNT); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_readcondition_read::alive_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i * 3; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_ALIVE; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_read, disposed_instance_state, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all disposed samples. */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, SAMPLE_DISPOSED_IST_CNT, "# read %d, expected %d", ret, SAMPLE_DISPOSED_IST_CNT); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_readcondition_read::disposed_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i * 3) + 1; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_NOT_ALIVE_DISPOSED; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_read, no_writers_instance_state, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all samples without a writer. */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, SAMPLE_NO_WRITER_IST_CNT, "# read %d, expected %d", ret, SAMPLE_NO_WRITER_IST_CNT); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_readcondition_read::no_writers_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i * 3) + 2; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_NOT_ALIVE_NO_WRITERS; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_read, combination_of_states, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all samples that match the mask (should be only one). */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 1, "# read %d, expected %d", ret, 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_readcondition_read::combination_of_states: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 3; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = DDS_VST_OLD; + dds_instance_state_t expected_ist = DDS_IST_ALIVE; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_read, none, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_READ_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all samples that match the mask (should be none). */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 0, "# read %d, expected %d", ret, 0); + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_read, with_mask, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Read all samples that match the or'd masks. */ + ret = dds_read_mask(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, + DDS_NOT_READ_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE); + cr_assert_eq(ret, 3, "# read %d, expected %d", ret, 3); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_readcondition_read::with_mask: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i == 0) ? 3 : (i == 1) ? 4 : 6; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_read, already_deleted, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Delete condition. */ + ret = dds_delete(condition); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to delete prerequisite condition"); + + /* Try to read with a deleted condition. */ + ret = dds_read(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_expect_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + + + + + + + + +/************************************************************************************************** + * + * These will check the readcondition taking in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_readcondition_take, any, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all non-read samples (should be last part). */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, MAX_SAMPLES, "# read %d, expected %d", ret, MAX_SAMPLES); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_readcondition_take::any: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_take, not_read_sample_state, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all non-read samples (should be last part). */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, MAX_SAMPLES - (SAMPLE_LAST_READ_SST + 1), "# read %d, expected %d", ret, MAX_SAMPLES - (SAMPLE_LAST_READ_SST + 1)); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_readcondition_take::not_read_sample_state: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = SAMPLE_LAST_READ_SST + 1 + i; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_take, read_sample_state, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all already read samples (should be first part). */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, SAMPLE_LAST_READ_SST + 1, "# read %d, expected %d", ret, SAMPLE_LAST_READ_SST + 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_readcondition_take::read_sample_state: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = DDS_SST_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_take, new_view_state, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all new-view samples (should be last part). */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, MAX_SAMPLES - (SAMPLE_LAST_OLD_VST + 1), "# read %d, expected %d", ret, MAX_SAMPLES - (SAMPLE_LAST_OLD_VST + 1)); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_readcondition_take::new_view_state: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = SAMPLE_LAST_OLD_VST + 1 + i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = DDS_VST_NEW; + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_take, not_new_view_state, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all old-view samples (should be first part). */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, SAMPLE_LAST_OLD_VST + 1, "# read %d, expected %d", ret, SAMPLE_LAST_OLD_VST + 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_readcondition_take::not_new_view_state: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = DDS_VST_OLD; + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_take, alive_instance_state, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all alive samples. */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, SAMPLE_ALIVE_IST_CNT, "# read %d, expected %d", ret, SAMPLE_ALIVE_IST_CNT); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_readcondition_take::alive_instance_state: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = i * 3; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_ALIVE; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_take, disposed_instance_state, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all disposed samples. */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, SAMPLE_DISPOSED_IST_CNT, "# read %d, expected %d", ret, SAMPLE_DISPOSED_IST_CNT); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_readcondition_take::disposed_instance_state: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i * 3) + 1; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_NOT_ALIVE_DISPOSED; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_take, no_writers_instance_state, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all samples without a writer. */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, SAMPLE_NO_WRITER_IST_CNT, "# read %d, expected %d", ret, SAMPLE_NO_WRITER_IST_CNT); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_readcondition_take::no_writers_instance_state: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i * 3) + 2; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_NOT_ALIVE_NO_WRITERS; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_take, combination_of_states, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all samples that match the mask (should be only one). */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 1, "# read %d, expected %d", ret, 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_readcondition_take::combination_of_states: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = 3; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = DDS_VST_OLD; + dds_instance_state_t expected_ist = DDS_IST_ALIVE; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_take, none, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_READ_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all samples that match the mask (should be none). */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 0, "# read %d, expected %d", ret, 0); + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_take, with_mask, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Take all samples that match the or'd masks. */ + ret = dds_take_mask(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, + DDS_NOT_READ_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE); + cr_assert_eq(ret, 3, "# read %d, expected %d", ret, 3); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_readcondition_take::with_mask: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i == 0) ? 3 : (i == 1) ? 4 : 6; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + dds_delete(condition); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_readcondition_take, already_deleted, .init=readcondition_init, .fini=readcondition_fini) +{ + dds_entity_t condition; + dds_return_t ret; + + /* Create condition. */ + condition = dds_create_readcondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(condition, 0, "Failed to create prerequisite condition"); + + /* Delete condition. */ + ret = dds_delete(condition); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to delete prerequisite condition"); + + /* Try to take with a deleted condition. */ + ret = dds_take(condition, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_expect_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + + + +#endif diff --git a/src/core/ddsc/tests/reader.c b/src/core/ddsc/tests/reader.c new file mode 100644 index 0000000..cb34915 --- /dev/null +++ b/src/core/ddsc/tests/reader.c @@ -0,0 +1,3208 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include + +#include "ddsc/dds.h" +#include "os/os.h" +#include "Space.h" +#include "RoundTrip.h" +#include +#include +#include + + +#if 0 +#define PRINT_SAMPLE(info, sample) cr_log_info("%s (%d, %d, %d)\n", info, sample.long_1, sample.long_2, sample.long_3); +#else +#define PRINT_SAMPLE(info, sample) +#endif + + + +/************************************************************************************************** + * + * Test fixtures + * + *************************************************************************************************/ +#define MAX_SAMPLES 7 +/* + * By writing, disposing, unregistering, reading and re-writing, the following + * data will be available in the reader history. + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ +#define SAMPLE_LAST_READ_SST (2) +#define SAMPLE_LAST_OLD_VST (3) +#define SAMPLE_IST(idx) (((idx % 3) == 0) ? DDS_IST_ALIVE : \ + ((idx % 3) == 1) ? DDS_IST_NOT_ALIVE_DISPOSED : \ + DDS_IST_NOT_ALIVE_NO_WRITERS ) +#define SAMPLE_VST(idx) ((idx <= SAMPLE_LAST_OLD_VST ) ? DDS_VST_OLD : DDS_VST_NEW) +#define SAMPLE_SST(idx) ((idx <= SAMPLE_LAST_READ_SST) ? DDS_SST_READ : DDS_SST_NOT_READ) + +static dds_entity_t g_participant = 0; +static dds_entity_t g_subscriber = 0; +static dds_entity_t g_topic = 0; +static dds_entity_t g_reader = 0; +static dds_entity_t g_writer = 0; +static dds_entity_t g_waitset = 0; +static dds_qos_t *g_qos = NULL; +static dds_qos_t *g_qos_null = NULL; +static dds_listener_t *g_listener = NULL; +static dds_listener_t *g_list_null= NULL; + +static void* g_loans[MAX_SAMPLES]; +static void* g_samples[MAX_SAMPLES]; +static Space_Type1 g_data[MAX_SAMPLES]; +static dds_sample_info_t g_info[MAX_SAMPLES]; + +static char* +create_topic_name(const char *prefix, char *name, size_t size) +{ + /* Get semi random g_topic name. */ + os_procId pid = os_procIdSelf(); + uintmax_t tid = os_threadIdToInteger(os_threadIdSelf()); + (void) snprintf(name, size, "%s_pid%"PRIprocId"_tid%"PRIuMAX"", prefix, pid, tid); + return name; +} + +static void +reader_init(void) +{ + Space_Type1 sample = { 0 }; + dds_attach_t triggered; + dds_return_t ret; + char name[100]; + + g_qos = dds_qos_create(); + g_listener = dds_listener_create(NULL); + + g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_participant, 0, "Failed to create prerequisite g_participant"); + + g_subscriber = dds_create_subscriber(g_participant, NULL, NULL); + cr_assert_gt(g_subscriber, 0, "Failed to create prerequisite g_subscriber"); + + g_waitset = dds_create_waitset(g_participant); + cr_assert_gt(g_waitset, 0, "Failed to create g_waitset"); + + g_topic = dds_create_topic(g_participant, &Space_Type1_desc, create_topic_name("ddsc_reader_test", name, sizeof name), NULL, NULL); + cr_assert_gt(g_topic, 0, "Failed to create prerequisite g_topic"); + + /* Create a reader that keeps last sample of all instances. */ + dds_qset_history(g_qos, DDS_HISTORY_KEEP_LAST, 1); + g_reader = dds_create_reader(g_participant, g_topic, g_qos, NULL); + cr_assert_gt(g_reader, 0, "Failed to create prerequisite g_reader"); + + /* Create a reader that will not automatically dispose unregistered samples. */ + dds_qset_writer_data_lifecycle(g_qos, false); + g_writer = dds_create_writer(g_participant, g_topic, g_qos, NULL); + cr_assert_gt(g_writer, 0, "Failed to create prerequisite g_writer"); + + /* Sync g_reader to g_writer. */ + ret = dds_set_enabled_status(g_reader, DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_reader status"); + ret = dds_waitset_attach(g_waitset, g_reader, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_reader"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_reader r"); + cr_assert_eq(g_reader, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_reader a"); + ret = dds_waitset_detach(g_waitset, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_reader"); + + /* Sync g_writer to g_reader. */ + ret = dds_set_enabled_status(g_writer, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_writer status"); + ret = dds_waitset_attach(g_waitset, g_writer, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_writer"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_writer r"); + cr_assert_eq(g_writer, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_writer a"); + ret = dds_waitset_detach(g_waitset, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_writer"); + + /* Initialize reading buffers. */ + memset (g_data, 0, sizeof (g_data)); + for (int i = 0; i < MAX_SAMPLES; i++) { + g_samples[i] = &g_data[i]; + } + for (int i = 0; i < MAX_SAMPLES; i++) { + g_loans[i] = NULL; + } + + /* Write all samples. */ + for (int i = 0; i < MAX_SAMPLES; i++) { + dds_instance_state_t ist = SAMPLE_IST(i); + sample.long_1 = i; + sample.long_2 = i/2; + sample.long_3 = i/3; + + PRINT_SAMPLE("INIT: Write ", sample); + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + + if (ist == DDS_IST_NOT_ALIVE_DISPOSED) { + PRINT_SAMPLE("INIT: Dispose ", sample); + ret = dds_dispose(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite dispose"); + } + if (ist == DDS_IST_NOT_ALIVE_NO_WRITERS) { + PRINT_SAMPLE("INIT: Unregister", sample); + ret = dds_unregister_instance(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite unregister"); + } + } + + /* Read samples to get read&old_view states. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, SAMPLE_LAST_OLD_VST + 1); + cr_assert_eq(ret, SAMPLE_LAST_OLD_VST + 1, "Failed prerequisite read"); + for(int i = 0; i < ret; i++) { + Space_Type1 *s = (Space_Type1*)g_samples[i]; + PRINT_SAMPLE("INIT: Read ", (*s)); + } + + /* Re-write the samples that should be not_read&old_view. */ + for (int i = SAMPLE_LAST_READ_SST + 1; i <= SAMPLE_LAST_OLD_VST; i++) { + dds_instance_state_t ist = SAMPLE_IST(i); + sample.long_1 = i; + sample.long_2 = i/2; + sample.long_3 = i/3; + + PRINT_SAMPLE("INIT: Rewrite ", sample); + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + + if ((ist == DDS_IST_NOT_ALIVE_DISPOSED) && (i != 4)) { + PRINT_SAMPLE("INIT: Dispose ", sample); + ret = dds_dispose(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite dispose"); + } + if (ist == DDS_IST_NOT_ALIVE_NO_WRITERS) { + PRINT_SAMPLE("INIT: Unregister", sample); + ret = dds_unregister_instance(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite unregister"); + } + } + +} + +static void +reader_fini(void) +{ + dds_qos_delete(g_qos); + dds_listener_delete(g_listener); + dds_delete(g_reader); + dds_delete(g_writer); + dds_delete(g_waitset); + dds_delete(g_topic); + dds_delete(g_participant); +} + +static dds_return_t +samples_cnt(void) +{ + dds_return_t ret; + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_geq(ret, 0, "Failed samples count read: %d", dds_err_nr(ret)); + return ret; +} + +/************************************************************************************************** + * + * These will check the reader creation in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_reader_create, valid) = { + DataPoints(dds_entity_t*, &g_subscriber, &g_participant), + DataPoints(dds_qos_t**, &g_qos_null, &g_qos ), + DataPoints(dds_listener_t**, &g_list_null, &g_listener ), +}; +Theory((dds_entity_t *ent, dds_qos_t **qos, dds_listener_t **listener), ddsc_reader_create, valid, .init=reader_init, .fini=reader_fini) +{ + dds_entity_t rdr; + dds_return_t ret; + rdr = dds_create_reader(*ent, g_topic, *qos, *listener); + cr_assert_gt(rdr, 0, "Failed dds_create_reader(%p, %p, %p): %d", ent, *qos, *listener, dds_err_nr(rdr)); + ret = dds_delete(rdr); + cr_assert_eq(ret, DDS_RETCODE_OK); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_reader_create, invalid_qos_participant, .init=reader_init, .fini=reader_fini) +{ + dds_entity_t rdr; + dds_qos_t *qos = dds_qos_create(); + /* Set invalid reader data lifecycle policy */ + OS_WARNING_MSVC_OFF(28020); /* Disable SAL warning on intentional misuse of the API */ + dds_qset_reader_data_lifecycle(qos, DDS_SECS(-1), DDS_SECS(-1)); + OS_WARNING_MSVC_ON(28020); + rdr = dds_create_reader(g_participant, g_topic, qos, NULL); + cr_assert_eq(dds_err_nr(rdr), DDS_RETCODE_INCONSISTENT_POLICY, "returned %d", dds_err_nr(rdr)); + dds_qos_delete(qos); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_reader_create, invalid_qos_subscriber, .init=reader_init, .fini=reader_fini) +{ + dds_entity_t rdr; + dds_qos_t *qos = dds_qos_create(); + /* Set invalid reader data lifecycle policy */ + OS_WARNING_MSVC_OFF(28020); /* Disable SAL warning on intentional misuse of the API */ + dds_qset_reader_data_lifecycle(qos, DDS_SECS(-1), DDS_SECS(-1)); + OS_WARNING_MSVC_ON(28020); + rdr = dds_create_reader(g_subscriber, g_topic, qos, NULL); + cr_assert_eq(dds_err_nr(rdr), DDS_RETCODE_INCONSISTENT_POLICY, "returned %d", dds_err_nr(rdr)); + dds_qos_delete(qos); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_reader_create, non_participants_non_topics) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_reader, &g_waitset), + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_reader, &g_waitset), +}; +Theory((dds_entity_t *par, dds_entity_t *top), ddsc_reader_create, non_participants_non_topics, .init=reader_init, .fini=reader_fini) +{ + dds_entity_t rdr; + /* The only valid permutation is when par is actual the participant and top is + * actually the topic. So, don't test that permutation. */ + cr_assume((par != &g_participant) || (top != &g_topic)); + rdr = dds_create_reader(*par, *top, NULL, NULL); + cr_assert_eq(dds_err_nr(rdr), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(rdr)); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the read in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read, invalid_buffers) = { + DataPoints(void**, g_samples, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0 ), + DataPoints(size_t, 0, 3, MAX_SAMPLES ), + DataPoints(uint32_t, 0, 3, MAX_SAMPLES ), +}; +Theory((void **buf, dds_sample_info_t *si, size_t bufsz, uint32_t maxs), ddsc_read, invalid_buffers, .init=reader_init, .fini=reader_fini) +{ + dds_return_t ret; + /* The only valid permutation is when non of the buffer values are + * invalid. So, don't test that. */ + cr_assume((buf != g_samples) || (si != g_info) || (bufsz == 0) || (maxs == 0) || (bufsz < maxs)); + /* TODO: CHAM-306, currently, a buffer is automatically 'promoted' to a loan when a buffer is + * provided with NULL pointers. So, in fact, there's currently no real difference between calling + * dds_read() dds_read_wl() (except for the provided bufsz). This will change, which means that + * the given buffer should contain valid pointers, which again means that 'loan intended' buffer + * should result in bad_parameter. + * However, that's not the case yet. So don't test it. */ + cr_assume(buf != g_loans); + ret = dds_read(g_reader, buf, si, bufsz, maxs); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_read, invalid_readers, .init=reader_init, .fini=reader_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_read(rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_waitset), +}; +Theory((dds_entity_t *rdr), ddsc_read, non_readers, .init=reader_init, .fini=reader_fini) +{ + dds_return_t ret; + ret = dds_read(*rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read, already_deleted, .init=reader_init, .fini=reader_fini) +{ + dds_return_t ret; + /* Try to read with a deleted reader. */ + dds_delete(g_reader); + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_expect_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read, valid, .init=reader_init, .fini=reader_fini) +{ + dds_return_t ret; + + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, MAX_SAMPLES, "# read %d, expected %d", ret, MAX_SAMPLES); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_read::valid: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + + + + + + +/************************************************************************************************** + * + * These will check the read_wl in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_wl, invalid_buffers) = { + DataPoints(void**, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0 ), + DataPoints(uint32_t, 0, 3, MAX_SAMPLES ), +}; +Theory((void **buf, dds_sample_info_t *si, uint32_t maxs), ddsc_read_wl, invalid_buffers, .init=reader_init, .fini=reader_fini) +{ + dds_return_t ret; + /* The only valid permutation is when non of the buffer values are + * invalid. So, don't test that. */ + cr_assume((buf != g_loans) || (si != g_info) || (maxs == 0)); + ret = dds_read_wl(g_reader, buf, si, maxs); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_wl, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_read_wl, invalid_readers, .init=reader_init, .fini=reader_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_read_wl(rdr, g_loans, g_info, MAX_SAMPLES); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_wl, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_waitset), +}; +Theory((dds_entity_t *rdr), ddsc_read_wl, non_readers, .init=reader_init, .fini=reader_fini) +{ + dds_return_t ret; + ret = dds_read_wl(*rdr, g_loans, g_info, MAX_SAMPLES); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_wl, already_deleted, .init=reader_init, .fini=reader_fini) +{ + dds_return_t ret; + /* Try to read with a deleted reader. */ + dds_delete(g_reader); + ret = dds_read_wl(g_reader, g_loans, g_info, MAX_SAMPLES); + cr_expect_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_wl, valid, .init=reader_init, .fini=reader_fini) +{ + dds_return_t ret; + + ret = dds_read_wl(g_reader, g_loans, g_info, MAX_SAMPLES); + cr_assert_eq(ret, MAX_SAMPLES, "# read %d, expected %d", ret, MAX_SAMPLES); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_read::valid: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the read_mask in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_mask, invalid_buffers) = { + DataPoints(void**, g_samples, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0 ), + DataPoints(size_t, 0, 3, MAX_SAMPLES ), + DataPoints(uint32_t, 0, 3, MAX_SAMPLES ), +}; +Theory((void **buf, dds_sample_info_t *si, size_t bufsz, uint32_t maxs), ddsc_read_mask, invalid_buffers, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + /* The only valid permutation is when non of the buffer values are + * invalid. So, don't test that. */ + cr_assume((buf != g_samples) || (si != g_info) || (bufsz == 0) || (maxs == 0) || (bufsz < maxs)); + /* TODO: CHAM-306, currently, a buffer is automatically 'promoted' to a loan when a buffer is + * provided with NULL pointers. So, in fact, there's currently no real difference between calling + * dds_read_mask() dds_read_mask_wl() (except for the provided bufsz). This will change, which means that + * the given buffer should contain valid pointers, which again means that 'loan intended' buffer + * should result in bad_parameter. + * However, that's not the case yet. So don't test it. */ + cr_assume(buf != g_loans); + ret = dds_read_mask(g_reader, buf, si, bufsz, maxs, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_mask, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_read_mask, invalid_readers, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_read_mask(rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_mask, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_waitset), +}; +Theory((dds_entity_t *rdr), ddsc_read_mask, non_readers, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + ret = dds_read_mask(*rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask, already_deleted, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + /* Try to read with a deleted reader. */ + dds_delete(g_reader); + ret = dds_read_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_expect_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask, any, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, MAX_SAMPLES, "# read %d, expected %d", ret, MAX_SAMPLES); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_read_mask::any: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask, not_read_sample_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, MAX_SAMPLES - (SAMPLE_LAST_READ_SST + 1), "# read %d, expected %d", ret, MAX_SAMPLES - (SAMPLE_LAST_READ_SST + 1)); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_read_mask::not_read_sample_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = SAMPLE_LAST_READ_SST + 1 + i; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask, read_sample_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, SAMPLE_LAST_READ_SST + 1, "# read %d, expected %d", ret, SAMPLE_LAST_READ_SST + 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_read_mask::read_sample_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = DDS_SST_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask, new_view_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, MAX_SAMPLES - (SAMPLE_LAST_OLD_VST + 1), "# read %d, expected %d", ret, MAX_SAMPLES - (SAMPLE_LAST_OLD_VST + 1)); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_read_mask::new_view_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = SAMPLE_LAST_OLD_VST + 1 + i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = DDS_VST_NEW; + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask, not_new_view_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, SAMPLE_LAST_OLD_VST + 1, "# read %d, expected %d", ret, SAMPLE_LAST_OLD_VST + 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_read_mask::not_new_view_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = DDS_VST_OLD; + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask, alive_instance_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, 3, "# read %d, expected %d", ret, 3); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_read_mask::alive_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i * 3; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_ALIVE; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask, not_alive_instance_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE | DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, 4, "# read %d, expected %d", ret, 4); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_read_mask::alive_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i <= 1) ? i + 1 : i + 2; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + cr_assert_neq(g_info[i].instance_state, DDS_IST_ALIVE); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask, disposed_instance_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_read_mask::disposed_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i * 3) + 1; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_NOT_ALIVE_DISPOSED; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask, no_writers_instance_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_read_mask::no_writers_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i * 3) + 2; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_NOT_ALIVE_NO_WRITERS; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask, combination_of_states, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, 1, "# read %d, expected %d", ret, 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_read_mask::combination_of_states: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 3; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = DDS_VST_OLD; + dds_instance_state_t expected_ist = DDS_IST_ALIVE; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the read_mask_wl in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_mask_wl, invalid_buffers) = { + DataPoints(void**, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0 ), + DataPoints(uint32_t, 0, 3, MAX_SAMPLES ), +}; +Theory((void **buf, dds_sample_info_t *si, uint32_t maxs), ddsc_read_mask_wl, invalid_buffers, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + /* The only valid permutation is when non of the buffer values are + * invalid. So, don't test that. */ + cr_assume((buf != g_loans) || (si != g_info) || (maxs == 0)); + ret = dds_read_mask_wl(g_reader, buf, si, maxs, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_mask_wl, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_read_mask_wl, invalid_readers, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_read_mask_wl(rdr, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_mask_wl, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_waitset), +}; +Theory((dds_entity_t *rdr), ddsc_read_mask_wl, non_readers, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + ret = dds_read_mask_wl(*rdr, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask_wl, already_deleted, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + /* Try to read with a deleted reader. */ + dds_delete(g_reader); + ret = dds_read_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_expect_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask_wl, any, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, MAX_SAMPLES, "# read %d, expected %d", ret, MAX_SAMPLES); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_read_mask_wl::any: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask_wl, not_read_sample_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, MAX_SAMPLES - (SAMPLE_LAST_READ_SST + 1), "# read %d, expected %d", ret, MAX_SAMPLES - (SAMPLE_LAST_READ_SST + 1)); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_read_mask_wl::not_read_sample_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = SAMPLE_LAST_READ_SST + 1 + i; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask_wl, read_sample_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, SAMPLE_LAST_READ_SST + 1, "# read %d, expected %d", ret, SAMPLE_LAST_READ_SST + 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_read_mask_wl::read_sample_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = DDS_SST_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask_wl, new_view_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, MAX_SAMPLES - (SAMPLE_LAST_OLD_VST + 1), "# read %d, expected %d", ret, MAX_SAMPLES - (SAMPLE_LAST_OLD_VST + 1)); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_read_mask_wl::new_view_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = SAMPLE_LAST_OLD_VST + 1 + i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = DDS_VST_NEW; + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask_wl, not_new_view_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, SAMPLE_LAST_OLD_VST + 1, "# read %d, expected %d", ret, SAMPLE_LAST_OLD_VST + 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_read_mask_wl::not_new_view_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = DDS_VST_OLD; + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask_wl, alive_instance_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, 3, "# read %d, expected %d", ret, 3); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_read_mask_wl::alive_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i * 3; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_ALIVE; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask_wl, not_alive_instance_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE | DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, 4, "# read %d, expected %d", ret, 4); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_read_mask_wl::alive_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i <= 1) ? i + 1 : i + 2; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + cr_assert_neq(g_info[i].instance_state, DDS_IST_ALIVE); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask_wl, disposed_instance_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_read_mask_wl::disposed_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i * 3) + 1; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_NOT_ALIVE_DISPOSED; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask_wl, no_writers_instance_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_read_mask_wl::no_writers_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i * 3) + 2; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_NOT_ALIVE_NO_WRITERS; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_read_mask_wl, combination_of_states, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_read_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, 1, "# read %d, expected %d", ret, 1); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_read_mask_wl::combination_of_states: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 3; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = DDS_VST_OLD; + dds_instance_state_t expected_ist = DDS_IST_ALIVE; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the take in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take, invalid_buffers) = { + DataPoints(void**, g_samples, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0 ), + DataPoints(size_t, 0, 3, MAX_SAMPLES ), + DataPoints(uint32_t, 0, 3, MAX_SAMPLES ), +}; +Theory((void **buf, dds_sample_info_t *si, size_t bufsz, uint32_t maxs), ddsc_take, invalid_buffers, .init=reader_init, .fini=reader_fini) +{ + dds_return_t ret; + /* The only valid permutation is when non of the buffer values are + * invalid. So, don't test that. */ + cr_assume((buf != g_samples) || (si != g_info) || (bufsz == 0) || (maxs == 0) || (bufsz < maxs)); + /* TODO: CHAM-306, currently, a buffer is automatically 'promoted' to a loan when a buffer is + * provided with NULL pointers. So, in fact, there's currently no real difference between calling + * dds_take() dds_take_wl() (except for the provided bufsz). This will change, which means that + * the given buffer should contain valid pointers, which again means that 'loan intended' buffer + * should result in bad_parameter. + * However, that's not the case yet. So don't test it. */ + cr_assume(buf != g_loans); + ret = dds_take(g_reader, buf, si, bufsz, maxs); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_take, invalid_readers, .init=reader_init, .fini=reader_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_take(rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_waitset), +}; +Theory((dds_entity_t *rdr), ddsc_take, non_readers, .init=reader_init, .fini=reader_fini) +{ + dds_return_t ret; + ret = dds_take(*rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take, already_deleted, .init=reader_init, .fini=reader_fini) +{ + dds_return_t ret; + /* Try to take with a deleted reader. */ + dds_delete(g_reader); + ret = dds_take(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_expect_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take, valid, .init=reader_init, .fini=reader_fini) +{ + dds_return_t expected_cnt = MAX_SAMPLES; + dds_return_t ret; + + ret = dds_take(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, MAX_SAMPLES, "# read %d, expected %d", ret, MAX_SAMPLES); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_read::valid: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the read_wl in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_wl, invalid_buffers) = { + DataPoints(void**, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0 ), + DataPoints(uint32_t, 0, 3, MAX_SAMPLES ), +}; +Theory((void **buf, dds_sample_info_t *si, uint32_t maxs), ddsc_take_wl, invalid_buffers, .init=reader_init, .fini=reader_fini) +{ + dds_return_t ret; + /* The only valid permutation is when non of the buffer values are + * invalid. So, don't test that. */ + cr_assume((buf != g_loans) || (si != g_info) || (maxs == 0)); + ret = dds_take_wl(g_reader, buf, si, maxs); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_wl, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_take_wl, invalid_readers, .init=reader_init, .fini=reader_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_take_wl(rdr, g_loans, g_info, MAX_SAMPLES); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_wl, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_waitset), +}; +Theory((dds_entity_t *rdr), ddsc_take_wl, non_readers, .init=reader_init, .fini=reader_fini) +{ + dds_return_t ret; + ret = dds_take_wl(*rdr, g_loans, g_info, MAX_SAMPLES); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_wl, already_deleted, .init=reader_init, .fini=reader_fini) +{ + dds_return_t ret; + /* Try to read with a deleted reader. */ + dds_delete(g_reader); + ret = dds_take_wl(g_reader, g_loans, g_info, MAX_SAMPLES); + cr_expect_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_wl, valid, .init=reader_init, .fini=reader_fini) +{ + dds_return_t expected_cnt = MAX_SAMPLES; + dds_return_t ret; + + ret = dds_take_wl(g_reader, g_loans, g_info, MAX_SAMPLES); + cr_assert_eq(ret, MAX_SAMPLES, "# read %d, expected %d", ret, MAX_SAMPLES); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_take::valid: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the read_mask in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_mask, invalid_buffers) = { + DataPoints(void**, g_samples, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0 ), + DataPoints(size_t, 0, 3, MAX_SAMPLES ), + DataPoints(uint32_t, 0, 3, MAX_SAMPLES ), +}; +Theory((void **buf, dds_sample_info_t *si, size_t bufsz, uint32_t maxs), ddsc_take_mask, invalid_buffers, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + /* The only valid permutation is when non of the buffer values are + * invalid. So, don't test that. */ + cr_assume((buf != g_samples) || (si != g_info) || (bufsz == 0) || (maxs == 0) || (bufsz < maxs)); + /* TODO: CHAM-306, currently, a buffer is automatically 'promoted' to a loan when a buffer is + * provided with NULL pointers. So, in fact, there's currently no real difference between calling + * dds_take_mask() dds_take_mask_wl() (except for the provided bufsz). This will change, which means that + * the given buffer should contain valid pointers, which again means that 'loan intended' buffer + * should result in bad_parameter. + * However, that's not the case yet. So don't test it. */ + cr_assume(buf != g_loans); + ret = dds_take_mask(g_reader, buf, si, bufsz, maxs, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_mask, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_take_mask, invalid_readers, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_take_mask(rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_mask, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_waitset), +}; +Theory((dds_entity_t *rdr), ddsc_take_mask, non_readers, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + ret = dds_take_mask(*rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask, already_deleted, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + /* Try to read with a deleted reader. */ + dds_delete(g_reader); + ret = dds_take_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_expect_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask, any, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = MAX_SAMPLES; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_take_mask::any: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask, not_read_sample_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 4; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_take_mask::not_read_sample_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = SAMPLE_LAST_READ_SST + 1 + i; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask, read_sample_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 3; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_take_mask::read_sample_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = DDS_SST_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask, new_view_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 3; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_take_mask::new_view_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = SAMPLE_LAST_OLD_VST + 1 + i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = DDS_VST_NEW; + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask, not_new_view_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 4; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_take_mask::not_new_view_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = DDS_VST_OLD; + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask, alive_instance_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE; + dds_return_t expected_cnt = 3; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_take_mask::alive_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i * 3; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_ALIVE; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask, not_alive_instance_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE | DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE; + dds_return_t expected_cnt = 4; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_take_mask::alive_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i <= 1) ? i + 1 : i + 2; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + cr_assert_neq(g_info[i].instance_state, DDS_IST_ALIVE); + } + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask, disposed_instance_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE; + dds_return_t expected_cnt = 2; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_take_mask::disposed_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i * 3) + 1; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_NOT_ALIVE_DISPOSED; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask, no_writers_instance_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE; + dds_return_t expected_cnt = 2; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_take_mask::no_writers_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i * 3) + 2; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_NOT_ALIVE_NO_WRITERS; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask, combination_of_states, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE; + dds_return_t expected_cnt = 1; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_take_mask::combination_of_states: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 3; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = DDS_VST_OLD; + dds_instance_state_t expected_ist = DDS_IST_ALIVE; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask, take_instance_last_sample) +{ +#define WOULD_CRASH +#ifdef WOULD_CRASH + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + int expected_long_3 = 3; +#else + uint32_t mask = DDS_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE; + dds_sample_state_t expected_sst = DDS_SST_READ; + int expected_long_3 = 2; +#endif + dds_return_t expected_cnt = 1; + Space_Type1 sample = { 0 }; + dds_attach_t triggered; + dds_return_t ret; + char name[100]; + + /* We need other readers/writers/data to force the crash. */ + g_qos = dds_qos_create(); + dds_qset_history(g_qos, DDS_HISTORY_KEEP_ALL, DDS_LENGTH_UNLIMITED); + g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_participant, 0, "Failed to create prerequisite g_participant"); + g_waitset = dds_create_waitset(g_participant); + cr_assert_gt(g_waitset, 0, "Failed to create g_waitset"); + g_topic = dds_create_topic(g_participant, &Space_Type1_desc, create_topic_name("ddsc_reader_test", name, 100), NULL, NULL); + cr_assert_gt(g_topic, 0, "Failed to create prerequisite g_topic"); + g_reader = dds_create_reader(g_participant, g_topic, g_qos, NULL); + cr_assert_gt(g_reader, 0, "Failed to create prerequisite g_reader"); + g_writer = dds_create_writer(g_participant, g_topic, NULL, NULL); + cr_assert_gt(g_writer, 0, "Failed to create prerequisite g_writer"); + + /* Sync g_reader to g_writer. */ + ret = dds_set_enabled_status(g_reader, DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_reader status"); + ret = dds_waitset_attach(g_waitset, g_reader, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_reader"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_reader r"); + cr_assert_eq(g_reader, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_reader a"); + ret = dds_waitset_detach(g_waitset, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_reader"); + + /* Sync g_writer to g_reader. */ + ret = dds_set_enabled_status(g_writer, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_writer status"); + ret = dds_waitset_attach(g_waitset, g_writer, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_writer"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_writer r"); + cr_assert_eq(g_writer, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_writer a"); + ret = dds_waitset_detach(g_waitset, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_writer"); + + /* Initialize reading buffers. */ + memset (g_data, 0, sizeof (g_data)); + for (int i = 0; i < MAX_SAMPLES; i++) { + g_samples[i] = &g_data[i]; + } + + /* Generate following data: + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 1 | 2 | read | old | alive | + * | 0 | 1 | 3 | not_read | old | alive | + */ + sample.long_1 = 0; + sample.long_2 = 1; + sample.long_3 = 2; + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 1, "Failed prerequisite read"); + sample.long_3 = 3; + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + + /* Take just one sample of the instance (the last one). */ + ret = dds_take_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *s = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 1 | 2 | read | old | alive | <--- no worries + * | 0 | 1 | 3 | not_read | old | alive | <--- crashed + */ + PRINT_SAMPLE("ddsc_take_mask::crash: Take", (*s)); + + /* Check data. */ + cr_assert_eq(s->long_1, 0); + cr_assert_eq(s->long_2, 1); + cr_assert_eq(s->long_3, expected_long_3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, DDS_VST_OLD); + cr_assert_eq(g_info[i].instance_state, DDS_IST_ALIVE); + } + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, 1, "# samples %d, expected %d", ret, 1); + + /* + * So far so good. + * But now the problem appeared: + * The reader crashed when deleting.... + */ + dds_delete(g_reader); + + /* Before the crash was fixed, we didn't come here. */ + dds_delete(g_writer); + dds_delete(g_waitset); + dds_delete(g_topic); + dds_delete(g_participant); + dds_qos_delete(g_qos); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the read_mask_wl in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_mask_wl, invalid_buffers) = { + DataPoints(void**, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0 ), + DataPoints(uint32_t, 0, 3, MAX_SAMPLES ), +}; +Theory((void **buf, dds_sample_info_t *si, uint32_t maxs), ddsc_take_mask_wl, invalid_buffers, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + /* The only valid permutation is when non of the buffer values are + * invalid. So, don't test that. */ + cr_assume((buf != g_loans) || (si != g_info) || (maxs == 0)); + ret = dds_take_mask_wl(g_reader, buf, si, maxs, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_mask_wl, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_take_mask_wl, invalid_readers, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_take_mask_wl(rdr, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_mask_wl, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_waitset), +}; +Theory((dds_entity_t *rdr), ddsc_take_mask_wl, non_readers, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + ret = dds_take_mask_wl(*rdr, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask_wl, already_deleted, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + /* Try to read with a deleted reader. */ + dds_delete(g_reader); + ret = dds_take_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_expect_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask_wl, any, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = MAX_SAMPLES; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_take_mask_wl::any: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask_wl, not_read_sample_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 4; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_take_mask_wl::not_read_sample_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = SAMPLE_LAST_READ_SST + 1 + i; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask_wl, read_sample_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 3; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_take_mask_wl::read_sample_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = DDS_SST_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask_wl, new_view_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 3; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_take_mask_wl::new_view_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = SAMPLE_LAST_OLD_VST + 1 + i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = DDS_VST_NEW; + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask_wl, not_new_view_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 4; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_take_mask_wl::not_new_view_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = DDS_VST_OLD; + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask_wl, alive_instance_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE; + dds_return_t expected_cnt = 3; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | <--- + */ + PRINT_SAMPLE("ddsc_take_mask_wl::alive_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = i * 3; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_ALIVE; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask_wl, not_alive_instance_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE | DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE; + dds_return_t expected_cnt = 4; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_take_mask_wl::alive_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i <= 1) ? i + 1 : i + 2; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + cr_assert_neq(g_info[i].instance_state, DDS_IST_ALIVE); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask_wl, disposed_instance_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE; + dds_return_t expected_cnt = 2; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | <--- + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | <--- + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_take_mask_wl::disposed_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i * 3) + 1; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_NOT_ALIVE_DISPOSED; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask_wl, no_writers_instance_state, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE; + dds_return_t expected_cnt = 2; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | <--- + * | 3 | 1 | 1 | not_read | old | alive | + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | <--- + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_take_mask_wl::no_writers_instance_state: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = (i * 3) + 2; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_1); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_1); + dds_instance_state_t expected_ist = DDS_IST_NOT_ALIVE_NO_WRITERS; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_mask_wl, combination_of_states, .init=reader_init, .fini=reader_fini) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ALIVE_INSTANCE_STATE; + dds_return_t expected_cnt = 1; + dds_return_t ret; + + /* Read all samples that matches the mask. */ + ret = dds_take_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 1 | 0 | 0 | read | old | disposed | + * | 2 | 1 | 0 | read | old | no_writers | + * | 3 | 1 | 1 | not_read | old | alive | <--- + * | 4 | 2 | 1 | not_read | new | disposed | + * | 5 | 2 | 1 | not_read | new | no_writers | + * | 6 | 3 | 2 | not_read | new | alive | + */ + PRINT_SAMPLE("ddsc_take_mask_wl::combination_of_states: Read", (*sample)); + + /* Expected states. */ + int expected_long_1 = 3; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = DDS_VST_OLD; + dds_instance_state_t expected_ist = DDS_IST_ALIVE; + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_1/2); + cr_assert_eq(sample->long_3, expected_long_1/3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Only samples that weren't taken should be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ diff --git a/src/core/ddsc/tests/reader_iterator.c b/src/core/ddsc/tests/reader_iterator.c new file mode 100644 index 0000000..7058ece --- /dev/null +++ b/src/core/ddsc/tests/reader_iterator.c @@ -0,0 +1,769 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include + +#include "ddsc/dds.h" +#include "os/os.h" +#include "Space.h" +#include "RoundTrip.h" +#include +#include +#include + +#if 0 +#define PRINT_SAMPLE(info, sample) cr_log_info("%s (%d, %d, %d)\n", info, sample.long_1, sample.long_2, sample.long_3); +#else +#define PRINT_SAMPLE(info, sample) +#endif + + +/************************************************************************************************** + * + * Test fixtures + * + *************************************************************************************************/ + +/* + * By writing, disposing, unregistering, reading and re-writing, the following + * data will be available in the reader history (but not in this order). + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | not_read | new | alive | + * | 0 | 1 | 2 | not_read | new | alive | + * | 0 | 2 | 4 | not_read | new | alive | + * | 1 | 3 | 6 | read | old | alive | + * | 1 | 4 | 8 | read | old | alive | + * | 1 | 5 | 10 | read | old | alive | + * | 2 | 6 | 12 | not_read | old | alive | + * | 2 | 7 | 14 | not_read | old | alive | + * | 2 | 8 | 16 | read | old | alive | + * | 3 | 9 | 18 | not_read | old | alive | + * | 3 | 10 | 20 | read | old | alive | + * | 3 | 11 | 22 | not_read | old | alive | + * | 4 | 12 | 24 | read | old | alive | + * | 4 | 13 | 26 | not_read | old | alive | + * | 4 | 14 | 28 | not_read | old | alive | + * | 5 | 15 | 30 | read | old | disposed | + * | 5 | 16 | 32 | not_read | old | disposed | + * | 5 | 17 | 34 | read | old | disposed | + * | 6 | 18 | 36 | read | old | no_writers | + * | 6 | 19 | 38 | not_read | old | no_writers | + * | 6 | 20 | 40 | read | old | no_writers | + * + */ +#define MAX_SAMPLES 21 + +#define RDR_NOT_READ_CNT 11 +int rdr_expected_long_2[RDR_NOT_READ_CNT] = { 0, 1, 2, 6, 7, 9, 11, 13, 14, 16, 19 }; + +/* Because we only read one sample at a time, only the first sample of an instance + * can be new. This turns out to be only the very first sample. */ +#define SAMPLE_VST(long_2) ((long_2 == 0) ? DDS_VST_NEW : DDS_VST_OLD) + +#define SAMPLE_IST(long_1) ((long_1 == 5) ? DDS_IST_NOT_ALIVE_DISPOSED : \ + (long_1 == 6) ? DDS_IST_NOT_ALIVE_NO_WRITERS : \ + DDS_IST_ALIVE ) + +static dds_entity_t g_participant = 0; +static dds_entity_t g_subscriber = 0; +static dds_entity_t g_publisher = 0; +static dds_entity_t g_topic = 0; +static dds_entity_t g_reader = 0; +static dds_entity_t g_writer = 0; +static dds_entity_t g_waitset = 0; +static dds_entity_t g_rcond = 0; +static dds_entity_t g_qcond = 0; + +static void* g_loans[MAX_SAMPLES]; +static void* g_samples[MAX_SAMPLES]; +static Space_Type1 g_data[MAX_SAMPLES]; +static dds_sample_info_t g_info[MAX_SAMPLES]; + +static dds_instance_handle_t g_hdl_valid; +static dds_instance_handle_t g_hdl_nil = DDS_HANDLE_NIL; + +static char* +create_topic_name(const char *prefix, char *name, size_t size) +{ + /* Get semi random g_topic name. */ + os_procId pid = os_procIdSelf(); + uintmax_t tid = os_threadIdToInteger(os_threadIdSelf()); + (void) snprintf(name, size, "%s_pid%"PRIprocId"_tid%"PRIuMAX"", prefix, pid, tid); + return name; +} + +static bool +filter_init(const void * sample) +{ + const Space_Type1 *s = sample; + return ((s->long_2 == 3) || + (s->long_2 == 4) || + (s->long_2 == 5) || + (s->long_2 == 8) || + (s->long_2 == 10) || + (s->long_2 == 12) || + (s->long_2 == 15) || + (s->long_2 == 17) || + (s->long_2 == 18) || + (s->long_2 == 20)); +} + +static bool +filter_mod2(const void * sample) +{ + const Space_Type1 *s = sample; + return (s->long_2 % 2 == 0); +} + +static void +reader_iterator_init(void) +{ + Space_Type1 sample = { 0 }; + dds_attach_t triggered; + dds_return_t ret; + char name[100]; + dds_qos_t *qos; + + qos = dds_qos_create(); + cr_assert_not_null(qos, "Failed to create prerequisite qos"); + + g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_participant, 0, "Failed to create prerequisite g_participant"); + + g_subscriber = dds_create_subscriber(g_participant, NULL, NULL); + cr_assert_gt(g_subscriber, 0, "Failed to create prerequisite g_subscriber"); + + g_publisher = dds_create_publisher(g_participant, NULL, NULL); + cr_assert_gt(g_publisher, 0, "Failed to create prerequisite g_publisher"); + + g_waitset = dds_create_waitset(g_participant); + cr_assert_gt(g_waitset, 0, "Failed to create g_waitset"); + + g_topic = dds_create_topic(g_participant, &Space_Type1_desc, create_topic_name("ddsc_read_iterator_test", name, sizeof name), NULL, NULL); + cr_assert_gt(g_topic, 0, "Failed to create prerequisite g_topic"); + + /* Create a writer that will not automatically dispose unregistered samples. */ + dds_qset_writer_data_lifecycle(qos, false); + g_writer = dds_create_writer(g_publisher, g_topic, qos, NULL); + cr_assert_gt(g_writer, 0, "Failed to create prerequisite g_writer"); + + /* Create a reader that keeps all samples when not taken. */ + dds_qset_history(qos, DDS_HISTORY_KEEP_ALL, DDS_LENGTH_UNLIMITED); + g_reader = dds_create_reader(g_subscriber, g_topic, qos, NULL); + cr_assert_gt(g_reader, 0, "Failed to create prerequisite g_reader"); + + /* Create a read condition that only reads old samples. */ + g_rcond = dds_create_readcondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_NOT_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(g_rcond, 0, "Failed to create prerequisite g_rcond"); + + /* Create a query condition that only reads of instances mod2. */ + g_qcond = dds_create_querycondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE, filter_mod2); + cr_assert_gt(g_qcond, 0, "Failed to create prerequisite g_qcond"); + + /* Sync g_reader to g_writer. */ + ret = dds_set_enabled_status(g_reader, DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_reader status"); + ret = dds_waitset_attach(g_waitset, g_reader, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_reader"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_reader r"); + cr_assert_eq(g_reader, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_reader a"); + ret = dds_waitset_detach(g_waitset, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_reader"); + + /* Sync g_writer to g_reader. */ + ret = dds_set_enabled_status(g_writer, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_writer status"); + ret = dds_waitset_attach(g_waitset, g_writer, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_writer"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_writer r"); + cr_assert_eq(g_writer, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_writer a"); + ret = dds_waitset_detach(g_waitset, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_writer"); + + /* Initialize reading buffers. */ + memset (g_data, 0, sizeof (g_data)); + for (int i = 0; i < MAX_SAMPLES; i++) { + g_samples[i] = &g_data[i]; + } + for (int i = 0; i < MAX_SAMPLES; i++) { + g_loans[i] = NULL; + } + + + /* Write the samples. */ + for (int i = 0; i < MAX_SAMPLES; i++) { + sample.long_1 = i/3; + sample.long_2 = i; + sample.long_3 = i*2; + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + } + /* | long_1 | long_2 | long_3 | sst | vst | ist | + * ----------------------------------------------------- + * | 0 | 0 | 0 | not_read | new | alive | + * | 0 | 1 | 2 | not_read | new | alive | + * | 0 | 2 | 4 | not_read | new | alive | + * | 1 | 3 | 6 | not_read | new | alive | + * | 1 | 4 | 8 | not_read | new | alive | + * | 1 | 5 | 10 | not_read | new | alive | + * | 2 | 6 | 12 | not_read | new | alive | + * | 2 | 7 | 14 | not_read | new | alive | + * | 2 | 8 | 16 | not_read | new | alive | + * | 3 | 9 | 18 | not_read | new | alive | + * | 3 | 10 | 20 | not_read | new | alive | + * | 3 | 11 | 22 | not_read | new | alive | + * | 4 | 12 | 24 | not_read | new | alive | + * | 4 | 13 | 26 | not_read | new | alive | + * | 4 | 14 | 28 | not_read | new | alive | + * | 5 | 15 | 30 | not_read | new | alive | + * | 5 | 16 | 32 | not_read | new | alive | + * | 5 | 17 | 34 | not_read | new | alive | + * | 6 | 18 | 36 | not_read | new | alive | + * | 6 | 19 | 38 | not_read | new | alive | + * | 6 | 20 | 40 | not_read | new | alive | + */ + + /* Set the sst to read for the proper samples by using a query + * condition that filters for these specific samples. */ + { + dds_entity_t qcond = 0; + + /* Create a query condition that reads the specific sample to get a set of 'read' samples after init. */ + qcond = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE, filter_init); + cr_assert_gt(g_qcond, 0, "Failed to create prerequisite qcond"); + + ret = dds_read(qcond, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_gt(ret, 0, "Failed prerequisite read: %d", dds_err_nr(ret)); + + dds_delete(qcond); + } + /* | long_1 | long_2 | long_3 | sst | vst | ist | + * ----------------------------------------------------- + * | 0 | 0 | 0 | not_read | new | alive | + * | 0 | 1 | 2 | not_read | new | alive | + * | 0 | 2 | 4 | not_read | new | alive | + * | 1 | 3 | 6 | read | old | alive | + * | 1 | 4 | 8 | read | old | alive | + * | 1 | 5 | 10 | read | old | alive | + * | 2 | 6 | 12 | not_read | old | alive | + * | 2 | 7 | 14 | not_read | old | alive | + * | 2 | 8 | 16 | read | old | alive | + * | 3 | 9 | 18 | not_read | old | alive | + * | 3 | 10 | 20 | read | old | alive | + * | 3 | 11 | 22 | not_read | old | alive | + * | 4 | 12 | 24 | read | old | alive | + * | 4 | 13 | 26 | not_read | old | alive | + * | 4 | 14 | 28 | not_read | old | alive | + * | 5 | 15 | 30 | read | old | alive | + * | 5 | 16 | 32 | not_read | old | alive | + * | 5 | 17 | 34 | read | old | alive | + * | 6 | 18 | 36 | read | old | alive | + * | 6 | 19 | 38 | not_read | old | alive | + * | 6 | 20 | 40 | read | old | alive | + */ + + + /* Dispose and unregister the last two samples. */ + sample.long_1 = 5; + sample.long_2 = 15; + sample.long_3 = 30; + ret = dds_dispose(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite dispose"); + sample.long_1 = 6; + sample.long_2 = 16; + sample.long_3 = 32; + ret = dds_unregister_instance(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite unregister"); + /* | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | not_read | new | alive | + * | 0 | 1 | 2 | not_read | new | alive | + * | 0 | 2 | 4 | not_read | new | alive | + * | 1 | 3 | 6 | read | old | alive | + * | 1 | 4 | 8 | read | old | alive | + * | 1 | 5 | 10 | read | old | alive | + * | 2 | 6 | 12 | not_read | old | alive | + * | 2 | 7 | 14 | not_read | old | alive | + * | 2 | 8 | 16 | read | old | alive | + * | 3 | 9 | 18 | not_read | old | alive | + * | 3 | 10 | 20 | read | old | alive | + * | 3 | 11 | 22 | not_read | old | alive | + * | 4 | 12 | 24 | read | old | alive | + * | 4 | 13 | 26 | not_read | old | alive | + * | 4 | 14 | 28 | not_read | old | alive | + * | 5 | 15 | 30 | read | old | disposed | + * | 5 | 16 | 32 | not_read | old | disposed | + * | 5 | 17 | 34 | read | old | disposed | + * | 6 | 18 | 36 | read | old | no_writers | + * | 6 | 19 | 38 | not_read | old | no_writers | + * | 6 | 20 | 40 | read | old | no_writers | + */ + + dds_qos_delete(qos); +} + +static void +reader_iterator_fini(void) +{ + dds_delete(g_rcond); + dds_delete(g_qcond); + dds_delete(g_reader); + dds_delete(g_writer); + dds_delete(g_subscriber); + dds_delete(g_publisher); + dds_delete(g_waitset); + dds_delete(g_topic); + dds_delete(g_participant); +} + +static dds_return_t +samples_cnt(void) +{ + dds_return_t ret; + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_geq(ret, 0, "Failed samples count read: %d", dds_err_nr(ret)); + return ret; +} + +/************************************************************************************************** + * + * These will check the dds_read_next() in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_read_next, reader, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_return_t cnt = 0; + dds_return_t ret = 1; + + while (ret == 1){ + ret = dds_read_next(g_reader, g_samples, g_info); + cr_assert_geq(ret, 0 , "# read %d", ret); + if(ret == 1){ + Space_Type1 *sample = (Space_Type1*)g_samples[0]; + PRINT_SAMPLE("ddsc_read_next::reader: Read", (*sample)); + + /* Expected states. */ + int expected_long_2 = rdr_expected_long_2[cnt]; + int expected_long_1 = expected_long_2/3; + int expected_long_3 = expected_long_2*2; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2 *2); + + /* Check states. */ + cr_assert_eq(g_info[0].valid_data, true); + cr_assert_eq(g_info[0].sample_state, expected_sst); + cr_assert_eq(g_info[0].view_state, expected_vst); + cr_assert_eq(g_info[0].instance_state, expected_ist); + cnt ++; + } + } + + cr_assert_eq(cnt, RDR_NOT_READ_CNT); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); + +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_next, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_read_next, invalid_readers, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_read_next(rdr, g_samples, g_info); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_str(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_next, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_publisher, &g_waitset, &g_rcond, &g_qcond), +}; +Theory((dds_entity_t *rdr), ddsc_read_next, non_readers, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_return_t ret; + ret = dds_read_next(*rdr, g_samples, g_info); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_next, already_deleted) = { + DataPoints(dds_entity_t*, &g_rcond, &g_qcond, &g_reader), +}; +Theory((dds_entity_t *rdr), ddsc_read_next, already_deleted, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_return_t ret; + ret = dds_delete(*rdr); + cr_assert_eq(ret, DDS_RETCODE_OK, "prerequisite delete failed: %d", dds_err_nr(ret)); + ret = dds_read_next(*rdr, g_samples, g_info); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} + +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_next, invalid_buffers) = { + DataPoints(void**, g_samples, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0 ), +}; +Theory((void **buf, dds_sample_info_t *si), ddsc_read_next, invalid_buffers, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_return_t ret; + cr_assume((buf != g_samples) || (si != g_info)); + cr_assume(buf != g_loans); + ret = dds_read_next(g_reader, buf, si); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the dds_read_next_wl() in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_read_next_wl, reader, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_return_t cnt = 0; + dds_return_t ret = 1; + + while (ret == 1){ + ret = dds_read_next_wl(g_reader, g_loans, g_info); + cr_assert_geq(ret, 0 , "# read %d", ret); + if(ret == 1){ + Space_Type1 *sample = (Space_Type1*)g_loans[0]; + PRINT_SAMPLE("ddsc_read_next_wl::reader: Read", (*sample)); + + /* Expected states. */ + int expected_long_2 = rdr_expected_long_2[cnt]; + int expected_long_1 = expected_long_2/3; + int expected_long_3 = expected_long_2*2; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_2/3 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2 *2); + + /* Check states. */ + cr_assert_eq(g_info[0].valid_data, true); + cr_assert_eq(g_info[0].sample_state, expected_sst); + cr_assert_eq(g_info[0].view_state, expected_vst); + cr_assert_eq(g_info[0].instance_state, expected_ist); + cnt ++; + } + } + + cr_assert_eq(cnt, RDR_NOT_READ_CNT); + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES, "# samples %d, expected %d", ret, MAX_SAMPLES); + +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_next_wl, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_read_next_wl, invalid_readers, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_read_next_wl(rdr, g_loans, g_info); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_next_wl, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_publisher, &g_waitset, &g_rcond, &g_qcond), +}; +Theory((dds_entity_t *rdr), ddsc_read_next_wl, non_readers, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_return_t ret; + ret = dds_read_next_wl(*rdr, g_loans, g_info); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_next_wl, already_deleted) = { + DataPoints(dds_entity_t*, &g_rcond, &g_qcond, &g_reader), +}; +Theory((dds_entity_t *rdr), ddsc_read_next_wl, already_deleted, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_return_t ret; + ret = dds_delete(*rdr); + cr_assert_eq(ret, DDS_RETCODE_OK, "prerequisite delete failed: %d", dds_err_nr(ret)); + ret = dds_read_next_wl(*rdr, g_loans, g_info); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} + +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_read_next_wl, invalid_buffers) = { + DataPoints(void**, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0 ), +}; +Theory((void **buf, dds_sample_info_t *si), ddsc_read_next_wl, invalid_buffers, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_return_t ret; + cr_assume((buf != g_loans) || (si != g_info)); + ret = dds_read_next_wl(g_reader, buf, si); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the dds_take_next() in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_take_next, reader, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_return_t cnt = 0; + dds_return_t ret = 1; + + while (ret == 1){ + ret = dds_take_next(g_reader, g_samples, g_info); + cr_assert_geq(ret, 0 , "# read %d", ret); + if(ret == 1){ + Space_Type1 *sample = (Space_Type1*)g_samples[0]; + PRINT_SAMPLE("ddsc_take_next::reader: Read", (*sample)); + + /* Expected states. */ + int expected_long_2 = rdr_expected_long_2[cnt]; + int expected_long_1 = expected_long_2/3; + int expected_long_3 = expected_long_2*2; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2 *2); + + /* Check states. */ + cr_assert_eq(g_info[0].valid_data, true); + cr_assert_eq(g_info[0].sample_state, expected_sst); + cr_assert_eq(g_info[0].view_state, expected_vst); + cr_assert_eq(g_info[0].instance_state, expected_ist); + cnt ++; + } + } + + cr_assert_eq(cnt, RDR_NOT_READ_CNT); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, (MAX_SAMPLES - RDR_NOT_READ_CNT), "# samples %d, expected %d", ret, MAX_SAMPLES); + +} +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_next, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_take_next, invalid_readers, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_take_next(rdr, g_samples, g_info); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_next, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_publisher, &g_waitset, &g_rcond, &g_qcond), +}; +Theory((dds_entity_t *rdr), ddsc_take_next, non_readers, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_return_t ret; + ret = dds_take_next(*rdr, g_samples, g_info); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_next, already_deleted) = { + DataPoints(dds_entity_t*, &g_rcond, &g_qcond, &g_reader), +}; +Theory((dds_entity_t *rdr), ddsc_take_next, already_deleted, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_return_t ret; + ret = dds_delete(*rdr); + cr_assert_eq(ret, DDS_RETCODE_OK, "prerequisite delete failed: %d", dds_err_nr(ret)); + ret = dds_take_next(*rdr, g_samples, g_info); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} + +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_next, invalid_buffers) = { + DataPoints(void**, g_samples, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0 ), +}; +Theory((void **buf, dds_sample_info_t *si), ddsc_take_next, invalid_buffers, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_return_t ret; + cr_assume((buf != g_samples) || (si != g_info)); + cr_assume(buf != g_loans); + ret = dds_take_next(g_reader, buf, si); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the dds_take_next_wl() in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_take_next_wl, reader, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_return_t cnt = 0; + dds_return_t ret = 1; + + while (ret == 1){ + ret = dds_take_next_wl(g_reader, g_loans, g_info); + cr_assert_geq(ret, 0 , "# read %d", ret); + if(ret == 1){ + Space_Type1 *sample = (Space_Type1*)g_loans[0]; + PRINT_SAMPLE("ddsc_read_next_wl::reader: Read", (*sample)); + + /* Expected states. */ + int expected_long_2 = rdr_expected_long_2[cnt]; + int expected_long_1 = expected_long_2/3; + int expected_long_3 = expected_long_2*2; + dds_sample_state_t expected_sst = DDS_SST_NOT_READ; + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_1); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_2/3 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2 *2); + + /* Check states. */ + cr_assert_eq(g_info[0].valid_data, true); + cr_assert_eq(g_info[0].sample_state, expected_sst); + cr_assert_eq(g_info[0].view_state, expected_vst); + cr_assert_eq(g_info[0].instance_state, expected_ist); + cnt ++; + } + } + + cr_assert_eq(cnt, RDR_NOT_READ_CNT); + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* All samples should still be available. */ + ret = samples_cnt(); + cr_assert_eq(ret, (MAX_SAMPLES - RDR_NOT_READ_CNT), "# samples %d, expected %d", ret, MAX_SAMPLES); + +} +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_next_wl, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_take_next_wl, invalid_readers, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_take_next_wl(rdr, g_loans, g_info); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_next_wl, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_publisher, &g_waitset, &g_rcond, &g_qcond), +}; +Theory((dds_entity_t *rdr), ddsc_take_next_wl, non_readers, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_return_t ret; + ret = dds_take_next_wl(*rdr, g_loans, g_info); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_next_wl, already_deleted) = { + DataPoints(dds_entity_t*, &g_rcond, &g_qcond, &g_reader), +}; +Theory((dds_entity_t *rdr), ddsc_take_next_wl, already_deleted, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_return_t ret; + ret = dds_delete(*rdr); + cr_assert_eq(ret, DDS_RETCODE_OK, "prerequisite delete failed: %d", dds_err_nr(ret)); + ret = dds_take_next_wl(*rdr, g_loans, g_info); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_next_wl, invalid_buffers) = { + DataPoints(void**, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0 ), +}; +Theory((void **buf, dds_sample_info_t *si), ddsc_take_next_wl, invalid_buffers, .init=reader_iterator_init, .fini=reader_iterator_fini) +{ + dds_return_t ret; + cr_assume((buf != g_loans) || (si != g_info)); + ret = dds_take_next_wl(g_reader, buf, si); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + diff --git a/src/core/ddsc/tests/register.c b/src/core/ddsc/tests/register.c new file mode 100644 index 0000000..7a2ae65 --- /dev/null +++ b/src/core/ddsc/tests/register.c @@ -0,0 +1,227 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include + +#include "ddsc/dds.h" +#include "os/os.h" +#include +#include +#include +#include "Space.h" + + +/************************************************************************************************** + * + * Test fixtures + * + *************************************************************************************************/ +#define MAX_SAMPLES 7 +#define INITIAL_SAMPLES 2 + + +static dds_entity_t g_participant = 0; +static dds_entity_t g_topic = 0; +static dds_entity_t g_reader = 0; +static dds_entity_t g_writer = 0; +static dds_entity_t g_waitset = 0; + +static dds_time_t g_past = 0; +static dds_time_t g_present = 0; + +static void* g_samples[MAX_SAMPLES]; +static Space_Type1 g_data[MAX_SAMPLES]; +static dds_sample_info_t g_info[MAX_SAMPLES]; + +static char* +create_topic_name(const char *prefix, char *name, size_t size) +{ + /* Get semi random g_topic name. */ + os_procId pid = os_procIdSelf(); + uintmax_t tid = os_threadIdToInteger(os_threadIdSelf()); + (void) snprintf(name, size, "%s_pid%"PRIprocId"_tid%"PRIuMAX"", prefix, pid, tid); + return name; +} + +static void +registering_init(void) +{ + Space_Type1 sample = { 0 }; + dds_qos_t *qos = dds_qos_create (); + dds_attach_t triggered; + dds_return_t ret; + char name[100]; + + /* Use by source timestamp to be able to check the time related funtions. */ + dds_qset_destination_order(qos, DDS_DESTINATIONORDER_BY_SOURCE_TIMESTAMP); + + g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_participant, 0, "Failed to create prerequisite g_participant"); + + g_waitset = dds_create_waitset(g_participant); + cr_assert_gt(g_waitset, 0, "Failed to create g_waitset"); + + g_topic = dds_create_topic(g_participant, &Space_Type1_desc, create_topic_name("ddsc_registering_test", name, sizeof name), qos, NULL); + cr_assert_gt(g_topic, 0, "Failed to create prerequisite g_topic"); + + /* Create a reader that keeps one sample on three instances. */ + dds_qset_reliability(qos, DDS_RELIABILITY_RELIABLE, DDS_MSECS(100)); + dds_qset_resource_limits(qos, DDS_LENGTH_UNLIMITED, 3, 1); + g_reader = dds_create_reader(g_participant, g_topic, qos, NULL); + cr_assert_gt(g_reader, 0, "Failed to create prerequisite g_reader"); + + /* Create a writer that will not automatically dispose unregistered samples. */ + dds_qset_writer_data_lifecycle(qos, false); + g_writer = dds_create_writer(g_participant, g_topic, qos, NULL); + cr_assert_gt(g_writer, 0, "Failed to create prerequisite g_writer"); + + /* Sync g_writer to g_reader. */ + ret = dds_set_enabled_status(g_writer, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_writer status"); + ret = dds_waitset_attach(g_waitset, g_writer, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_writer"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_writer r"); + cr_assert_eq(g_writer, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_writer a"); + ret = dds_waitset_detach(g_waitset, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_writer"); + + /* Sync g_reader to g_writer. */ + ret = dds_set_enabled_status(g_reader, DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_reader status"); + ret = dds_waitset_attach(g_waitset, g_reader, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_reader"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_reader r"); + cr_assert_eq(g_reader, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_reader a"); + ret = dds_waitset_detach(g_waitset, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_reader"); + + /* Write initial samples. */ + for (int i = 0; i < INITIAL_SAMPLES; i++) { + sample.long_1 = i; + sample.long_2 = i*2; + sample.long_3 = i*3; + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + } + + /* Initialize reading buffers. */ + memset (g_data, 0, sizeof (g_data)); + for (int i = 0; i < MAX_SAMPLES; i++) { + g_samples[i] = &g_data[i]; + } + + /* Initialize times. */ + g_present = dds_time(); + g_past = g_present - DDS_SECS(1); + + dds_qos_delete(qos); +} + +static void +registering_fini(void) +{ + dds_delete(g_reader); + dds_delete(g_writer); + dds_delete(g_waitset); + dds_delete(g_topic); + dds_delete(g_participant); +} + + +#if 0 +#else +/************************************************************************************************** + * + * These will check the dds_register_instance() in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_register_instance, deleted_entity, .init=registering_init, .fini=registering_fini) +{ + dds_return_t ret; + dds_instance_handle_t handle; + dds_delete(g_writer); + ret = dds_register_instance(g_writer, &handle, g_data); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} + +static dds_instance_handle_t hndle = 0; +static Space_Type1 data; +TheoryDataPoints(ddsc_register_instance, invalid_params) = { + DataPoints(dds_instance_handle_t *, &hndle, NULL), + DataPoints(void*, &data, NULL) +}; +Theory((dds_instance_handle_t *hndl2, void *datap), ddsc_register_instance, invalid_params/*, .init=registering_init, .fini=registering_fini*/) +{ + dds_return_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + /* Only test when the combination of parameters is actually invalid.*/ + cr_assume((hndl2 == NULL) || (datap == NULL)); + + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_register_instance(g_writer, hndl2, datap); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d != expected %d", dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER); +} + +TheoryDataPoints(ddsc_register_instance, invalid_writers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t writer), ddsc_register_instance, invalid_writers, .init=registering_init, .fini=registering_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + dds_instance_handle_t handle; + + ret = dds_register_instance(writer, &handle, g_data); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} + +TheoryDataPoints(ddsc_register_instance, non_writers) = { + DataPoints(dds_entity_t*, &g_waitset, &g_reader, &g_topic, &g_participant), +}; +Theory((dds_entity_t *writer), ddsc_register_instance, non_writers, .init=registering_init, .fini=registering_fini) +{ + dds_return_t ret; + dds_instance_handle_t handle; + ret = dds_register_instance(*writer, &handle, g_data); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} + +Test(ddsc_register_instance, registering_new_instance, .init=registering_init, .fini=registering_fini) +{ + dds_instance_handle_t instHndl, instHndl2; + dds_return_t ret; + Space_Type1 newInstance = { INITIAL_SAMPLES, 0, 0 }; + instHndl = dds_instance_lookup(g_writer, &newInstance); + cr_assert_eq(instHndl, DDS_HANDLE_NIL); + ret = dds_register_instance(g_writer, &instHndl2, &newInstance); + cr_assert_eq(ret, DDS_RETCODE_OK); + instHndl = dds_instance_lookup(g_writer, &newInstance); + cr_assert_eq(instHndl, instHndl2); +} + +Test(ddsc_register_instance, data_already_available, .init=registering_init, .fini=registering_fini) +{ + dds_instance_handle_t instHndl, instHndl2; + dds_return_t ret; + instHndl = dds_instance_lookup(g_writer, &g_data); + cr_assert_neq(instHndl, DDS_HANDLE_NIL); + ret = dds_register_instance(g_writer, &instHndl2, &g_data); + cr_assert_eq(ret, DDS_RETCODE_OK); + cr_assert_eq(instHndl2, instHndl); + +} + +#endif diff --git a/src/core/ddsc/tests/return_loan.c b/src/core/ddsc/tests/return_loan.c new file mode 100644 index 0000000..e295b2a --- /dev/null +++ b/src/core/ddsc/tests/return_loan.c @@ -0,0 +1,150 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include "RoundTrip.h" +#include "os/os.h" + +#include +#include +#include + +dds_entity_t participant = 0, topic = 0, reader = 0, read_condition = 0; + +void create_entities(void) +{ + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0); + + topic = dds_create_topic(participant, &RoundTripModule_DataType_desc, "ddsc_reader_return_loan_RoundTrip", NULL, NULL); + cr_assert_gt(topic, 0); + + reader = dds_create_reader(participant, topic, NULL, NULL); + cr_assert_gt(reader, 0); + + read_condition = dds_create_readcondition(reader, DDS_ANY_STATE); + cr_assert_gt(read_condition, 0); +} + +void delete_entities(void) +{ + dds_return_t result; + result = dds_delete(participant); + cr_assert_eq(dds_err_nr(result), DDS_RETCODE_OK, "Recursively delete entities, Expected(%s) Returned(%s)", DDS_TO_STRING(DDS_RETCODE_OK), dds_err_str(result)); + dds_delete(read_condition); +} + +static void** create_loan_buf(size_t sz, bool empty) +{ + size_t i; + void **buf = NULL; + buf = dds_alloc(sz * sizeof(*buf)); + for (i = 0; i < sz; i++) { + buf[i] = dds_alloc(sizeof(RoundTripModule_DataType)); + if (empty) { + memset(buf[i], 0, sizeof(RoundTripModule_DataType)); + } else { + RoundTripModule_DataType *s = buf[i]; + s->payload._maximum = 0; + s->payload._length = 25; + s->payload._buffer = dds_alloc(25); + memset(s->payload._buffer, 'z', 25); + s->payload._release = true; + } + } + return buf; +} + +static void delete_loan_buf(void **buf, size_t sz, bool empty) +{ + size_t i; + for (i = 0; i < sz; i++) { + RoundTripModule_DataType *s = buf[i]; + if (!empty) { + cr_expect_gt(s->payload._length, 0, "Expected allocated 'payload'-sequence in sample-contents of loan"); + if (s->payload._length > 0) { + /* Freed by a successful dds_return_loan */ + dds_free(s->payload._buffer); + } + } + /* dds_return_loan only free's sample contents */ + dds_free(s); + } + dds_free(buf); +} + +/* Verify DDS_RETCODE_BAD_PARAMETER is returned */ +Test(ddsc_reader, return_loan_bad_params, .init = create_entities, .fini = delete_entities) +{ + dds_return_t result; + void **buf = NULL; + + result = dds_return_loan(reader, NULL, 0); + cr_expect_eq(dds_err_nr(result), DDS_RETCODE_BAD_PARAMETER, "Invalid buffer(null), Expected(%s) Returned(%s)", + DDS_TO_STRING(DDS_RETCODE_BAD_PARAMETER), + dds_err_str(result)); + +#pragma warning(push) +#pragma warning(disable: 6387) + result = dds_return_loan(reader, buf, 10); +#pragma warning(pop) + cr_expect_eq(dds_err_nr(result), DDS_RETCODE_BAD_PARAMETER, "Invalid buffer size, Expected(%s) Returned(%s)", + DDS_TO_STRING(DDS_RETCODE_BAD_PARAMETER), + dds_err_str(result)); + + buf = create_loan_buf(10, false); +#pragma warning(push) +#pragma warning(disable: 28020) + result = dds_return_loan(0, buf, 10); +#pragma warning(pop) + cr_expect_eq(dds_err_nr(result), DDS_RETCODE_BAD_PARAMETER, "Invalid entity, Expected(%s) Returned(%s)", + DDS_TO_STRING(DDS_RETCODE_BAD_PARAMETER), + dds_err_str(result)); + + result = dds_return_loan(participant, buf, 0); + cr_expect_eq(dds_err_nr(result), DDS_RETCODE_ILLEGAL_OPERATION, "Invalid entity-kind, Expected(%s) Returned(%s)", + DDS_TO_STRING(DDS_RETCODE_ILLEGAL_OPERATION), + dds_err_str(result)); + + delete_loan_buf(buf, 10, false); +} + +/* Verify DDS_RETCODE_OK is returned */ +Test(ddsc_reader, return_loan_success, .init = create_entities, .fini = delete_entities) +{ + void **buf; + void *buf2 = NULL; + dds_return_t result; + + buf = create_loan_buf(10, false); + result = dds_return_loan(reader, buf, 10); + cr_expect_eq(dds_err_nr(result), DDS_RETCODE_OK, "Return loan of size 10 via reader entity, Expected(%s) Returned(%s)", + DDS_TO_STRING(DDS_RETCODE_OK), + dds_err_str(result)); + + result = dds_return_loan(reader, &buf2, 0); + cr_expect_eq(dds_err_nr(result), DDS_RETCODE_OK, "Return empty loan via reader entity, Expected(%s) Returned(%s)", + DDS_TO_STRING(DDS_RETCODE_OK), + dds_err_str(result)); + delete_loan_buf(buf, 10, true); + + buf = create_loan_buf(10, false); + result = dds_return_loan(read_condition, buf, 10); + cr_expect_eq(dds_err_nr(result), DDS_RETCODE_OK, "Return loan of size 10 via read-condition entity, Expected(%s) Returned(%s)", + DDS_TO_STRING(DDS_RETCODE_OK), + dds_err_str(result)); + + result = dds_return_loan(read_condition, &buf2, 0); + cr_expect_eq(dds_err_nr(result), DDS_RETCODE_OK, "Return empty loan via read-condition entity, Expected(%s) Returned(%s)", + DDS_TO_STRING(DDS_RETCODE_OK), + dds_err_str(result)); + delete_loan_buf(buf, 10, true); +} diff --git a/src/core/ddsc/tests/subscriber.c b/src/core/ddsc/tests/subscriber.c new file mode 100644 index 0000000..3ffcf49 --- /dev/null +++ b/src/core/ddsc/tests/subscriber.c @@ -0,0 +1,111 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" + +#include +#include +#include + +/* We are deliberately testing some bad arguments that SAL will complain about. + * So, silence SAL regarding these issues. */ +#pragma warning(push) +#pragma warning(disable: 6387 28020) + +static void on_data_available(dds_entity_t reader, void* arg) {} +static void on_publication_matched(dds_entity_t writer, const dds_publication_matched_status_t status, void* arg) {} + +Test(ddsc_subscriber, notify_readers) { + dds_entity_t participant; + dds_entity_t subscriber; + dds_return_t ret; + + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "Failed to create prerequisite participant"); + + + subscriber = dds_create_subscriber(participant, NULL, NULL); + cr_assert_gt(subscriber, 0, "Failed to create prerequisite subscriber"); + + /* todo implement tests */ + ret = dds_notify_readers(subscriber); + cr_expect_eq(dds_err_nr(ret), DDS_RETCODE_UNSUPPORTED, "Invalid return code %d", ret); + + dds_delete(subscriber); + dds_delete(participant); +} + +Test(ddsc_subscriber, create) { + + dds_entity_t participant; + dds_entity_t subscriber; + dds_listener_t *listener; + dds_qos_t *sqos; + + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "Failed to create prerequisite participant"); + + /*** Verify participant parameter ***/ + + subscriber = dds_create_subscriber(0, NULL, NULL); + cr_assert_eq(dds_err_nr(subscriber), DDS_RETCODE_BAD_PARAMETER, "dds_create_subscriber: invalid participant parameter"); + + subscriber = dds_create_subscriber(participant, NULL, NULL); + cr_assert_gt(subscriber, 0, "dds_create_subscriber: valid participant parameter"); + dds_delete(subscriber); + + /*** Verify qos parameter ***/ + + sqos = dds_qos_create(); /* Use defaults (no user-defined policies) */ + subscriber = dds_create_subscriber(participant, sqos, NULL); + cr_assert_gt(subscriber, 0, "dds_create_subscriber: default QoS parameter"); + dds_delete(subscriber); + dds_qos_delete(sqos); + + sqos = dds_qos_create(); + dds_qset_destination_order(sqos, 3); /* Set invalid dest. order (ignored, not applicable for subscriber) */ + subscriber = dds_create_subscriber(participant, sqos, NULL); + cr_assert_gt(subscriber, 0, "dds_create_subscriber: invalid non-applicable QoS parameter"); + dds_delete(subscriber); + dds_qos_delete(sqos); + + sqos = dds_qos_create(); + dds_qset_presentation(sqos, 123, 1, 1); /* Set invalid presentation policy */ + subscriber = dds_create_subscriber(participant, sqos, NULL); + cr_assert_eq(dds_err_nr(subscriber), DDS_RETCODE_INCONSISTENT_POLICY, "dds_create_subscriber: invalid presentation access_scope QoS parameter"); + dds_qos_delete(sqos); + + /*** Verify listener parameter ***/ + + listener = dds_listener_create(NULL); /* Use defaults (all listeners unset) */ + subscriber = dds_create_subscriber(participant, NULL, listener); + cr_assert_gt(subscriber, 0, "dds_create_subscriber: unset listeners"); + dds_delete(subscriber); + dds_listener_delete(listener); + + listener = dds_listener_create(NULL); + dds_lset_data_available(listener, &on_data_available); /* Set on_data_available listener */ + subscriber = dds_create_subscriber(participant, NULL, listener); + cr_assert_gt(subscriber, 0, "dds_create_subscriber: on_data_available listener"); + dds_delete(subscriber); + dds_listener_delete(listener); + + listener = dds_listener_create(NULL); + dds_lset_publication_matched(listener, &on_publication_matched); /* Set on_publication_matched listener (ignored, not applicable for subscriber) */ + subscriber = dds_create_subscriber(participant, NULL, listener); + cr_assert_gt(subscriber, 0, "dds_create_subscriber: on_publication_matched listener"); + dds_delete(subscriber); + dds_listener_delete(listener); + + dds_delete(participant); +} + +#pragma warning(pop) diff --git a/src/core/ddsc/tests/take_instance.c b/src/core/ddsc/tests/take_instance.c new file mode 100644 index 0000000..6ae38a6 --- /dev/null +++ b/src/core/ddsc/tests/take_instance.c @@ -0,0 +1,1258 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include + +#include "ddsc/dds.h" +#include "os/os.h" +#include "Space.h" +#include "RoundTrip.h" +#include +#include +#include + + +#if 0 +#define PRINT_SAMPLE(info, sample) cr_log_info("%s (%d, %d, %d)\n", info, sample.long_1, sample.long_2, sample.long_3); +#else +#define PRINT_SAMPLE(info, sample) +#endif + + + +/************************************************************************************************** + * + * Test fixtures + * + *************************************************************************************************/ +#define MAX_SAMPLES 5 +/* + * By writing, disposing, unregistering, reading and re-writing, the following + * data will be available in the reader history (but not in this order). + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ +#define SAMPLE_IST(idx) ((idx <= 2) ? DDS_IST_ALIVE : \ + (idx == 3) ? DDS_IST_NOT_ALIVE_DISPOSED : \ + DDS_IST_NOT_ALIVE_NO_WRITERS ) +#define SAMPLE_VST(idx) ((idx <= 1) ? DDS_VST_OLD : DDS_VST_NEW) +#define SAMPLE_SST(idx) ((idx == 0) ? DDS_SST_READ : DDS_SST_NOT_READ) + +static dds_entity_t g_participant = 0; +static dds_entity_t g_subscriber = 0; +static dds_entity_t g_publisher = 0; +static dds_entity_t g_topic = 0; +static dds_entity_t g_reader = 0; +static dds_entity_t g_writer = 0; +static dds_entity_t g_waitset = 0; +static dds_entity_t g_rcond = 0; +static dds_entity_t g_qcond = 0; + +static void* g_loans[MAX_SAMPLES]; +static void* g_samples[MAX_SAMPLES]; +static Space_Type1 g_data[MAX_SAMPLES]; +static dds_sample_info_t g_info[MAX_SAMPLES]; + +static dds_instance_handle_t g_hdl_valid; +static dds_instance_handle_t g_hdl_nil = DDS_HANDLE_NIL; + +static bool +filter_mod2(const void * sample) +{ + const Space_Type1 *s = sample; + return (s->long_2 % 2 == 0); +} + +static char* +create_topic_name(const char *prefix, char *name, size_t size) +{ + /* Get semi random g_topic name. */ + os_procId pid = os_procIdSelf(); + uintmax_t tid = os_threadIdToInteger(os_threadIdSelf()); + (void) snprintf(name, size, "%s_pid%"PRIprocId"_tid%"PRIuMAX"", prefix, pid, tid); + return name; +} + +static void +take_instance_init(void) +{ + Space_Type1 sample = { 0 }; + dds_attach_t triggered; + dds_return_t ret; + char name[100]; + dds_qos_t *qos; + + qos = dds_qos_create(); + cr_assert_not_null(qos, "Failed to create prerequisite qos"); + + g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_participant, 0, "Failed to create prerequisite g_participant"); + + g_subscriber = dds_create_subscriber(g_participant, NULL, NULL); + cr_assert_gt(g_subscriber, 0, "Failed to create prerequisite g_subscriber"); + + g_publisher = dds_create_publisher(g_participant, NULL, NULL); + cr_assert_gt(g_publisher, 0, "Failed to create prerequisite g_publisher"); + + g_waitset = dds_create_waitset(g_participant); + cr_assert_gt(g_waitset, 0, "Failed to create g_waitset"); + + g_topic = dds_create_topic(g_participant, &Space_Type1_desc, create_topic_name("ddsc_take_instance_test", name, sizeof name), NULL, NULL); + cr_assert_gt(g_topic, 0, "Failed to create prerequisite g_topic"); + + /* Create a writer that will not automatically dispose unregistered samples. */ + dds_qset_writer_data_lifecycle(qos, false); + g_writer = dds_create_writer(g_publisher, g_topic, qos, NULL); + cr_assert_gt(g_writer, 0, "Failed to create prerequisite g_writer"); + + /* Create a reader that keeps all samples when not taken. */ + dds_qset_history(qos, DDS_HISTORY_KEEP_ALL, DDS_LENGTH_UNLIMITED); + g_reader = dds_create_reader(g_subscriber, g_topic, qos, NULL); + cr_assert_gt(g_reader, 0, "Failed to create prerequisite g_reader"); + + /* Create a read condition that only reads not_read samples. */ + g_rcond = dds_create_readcondition(g_reader, DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE); + cr_assert_gt(g_rcond, 0, "Failed to create prerequisite g_rcond"); + + /* Create a query condition that only reads not_read samples of instances mod2. */ + g_qcond = dds_create_querycondition(g_reader, DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE, filter_mod2); + cr_assert_gt(g_qcond, 0, "Failed to create prerequisite g_qcond"); + + /* Sync g_reader to g_writer. */ + ret = dds_set_enabled_status(g_reader, DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_reader status"); + ret = dds_waitset_attach(g_waitset, g_reader, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_reader"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_reader r"); + cr_assert_eq(g_reader, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_reader a"); + ret = dds_waitset_detach(g_waitset, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_reader"); + + /* Sync g_writer to g_reader. */ + ret = dds_set_enabled_status(g_writer, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_writer status"); + ret = dds_waitset_attach(g_waitset, g_writer, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_writer"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_writer r"); + cr_assert_eq(g_writer, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_writer a"); + ret = dds_waitset_detach(g_waitset, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_writer"); + + /* Initialize reading buffers. */ + memset (g_data, 0, sizeof (g_data)); + for (int i = 0; i < MAX_SAMPLES; i++) { + g_samples[i] = &g_data[i]; + } + for (int i = 0; i < MAX_SAMPLES; i++) { + g_loans[i] = NULL; + } + + /* Write the sample that will become {sst(read), vst(old), ist(alive)}. */ + sample.long_1 = 0; + sample.long_2 = 0; + sample.long_3 = 0; + PRINT_SAMPLE("INIT: Write ", sample); + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + /* | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | not_read | new | alive | + */ + + /* Read sample that will become {sst(read), vst(old), ist(alive)}. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 1, "Failed prerequisite read"); + for(int i = 0; i < ret; i++) { + Space_Type1 *s = (Space_Type1*)g_samples[i]; + PRINT_SAMPLE("INIT: Read ", (*s)); + } + /* | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + */ + + /* Write the sample that will become {sst(not_read), vst(old), ist(alive)}. */ + sample.long_1 = 0; + sample.long_2 = 1; + sample.long_3 = 2; + PRINT_SAMPLE("INIT: Write ", sample); + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + /* | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | + */ + + /* Write the samples that will become {sst(not_read), vst(new), ist(*)}. */ + for (int i = 2; i < MAX_SAMPLES; i++) { + sample.long_1 = i - 1; + sample.long_2 = i; + sample.long_3 = i*2; + PRINT_SAMPLE("INIT: Write ", sample); + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + } + /* | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | alive | + * | 3 | 4 | 8 | not_read | new | alive | + */ + + /* Dispose the sample that will become {sst(not_read), vst(new), ist(disposed)}. */ + sample.long_1 = 2; + sample.long_2 = 3; + sample.long_3 = 6; + PRINT_SAMPLE("INIT: Dispose ", sample); + ret = dds_dispose(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite dispose"); + /* | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | alive | + */ + + /* Unregister the sample that will become {sst(not_read), vst(new), ist(no_writers)}. */ + sample.long_1 = 3; + sample.long_2 = 4; + sample.long_3 = 8; + PRINT_SAMPLE("INIT: Unregister", sample); + ret = dds_unregister_instance(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite unregister"); + /* | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + + /* Get valid instance handle. */ + sample.long_1 = 0; + sample.long_2 = 0; + sample.long_3 = 0; + g_hdl_valid = dds_instance_lookup(g_reader, &sample); + cr_assert_neq(g_hdl_valid, DDS_HANDLE_NIL, "Failed prerequisite dds_instance_lookup"); + + dds_qos_delete(qos); +} + +static void +take_instance_fini(void) +{ + dds_delete(g_rcond); + dds_delete(g_qcond); + dds_delete(g_reader); + dds_delete(g_writer); + dds_delete(g_subscriber); + dds_delete(g_publisher); + dds_delete(g_waitset); + dds_delete(g_topic); + dds_delete(g_participant); +} + +static dds_return_t +samples_cnt(void) +{ + dds_return_t ret; + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_geq(ret, 0, "Failed samples count read: %d", dds_err_nr(ret)); + return ret; +} + + + + + +/************************************************************************************************** + * + * These will check the take_instance_* functions with invalid parameters. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance, invalid_params) = { + DataPoints(dds_entity_t*, &g_reader, &g_rcond, &g_qcond), + DataPoints(void**, g_samples, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0), + DataPoints(size_t, 0, 2, MAX_SAMPLES), + DataPoints(uint32_t, 0, 2, MAX_SAMPLES), +}; +Theory((dds_entity_t *ent, void **buf, dds_sample_info_t *si, size_t bufsz, uint32_t maxs), ddsc_take_instance, invalid_params, .init=take_instance_init, .fini=take_instance_fini) +{ + dds_return_t ret; + /* The only valid permutation is when non of the buffer values are + * invalid and neither is the handle. So, don't test that. */ + cr_assume((buf != g_samples) || (si != g_info) || (bufsz == 0) || (maxs == 0) || (bufsz < maxs)); + /* TODO: CHAM-306, currently, a buffer is automatically 'promoted' to a loan when a buffer is + * provided with NULL pointers. So, in fact, there's currently no real difference between calling + * dds_take() dds_take_wl() (except for the provided bufsz). This will change, which means that + * the given buffer should contain valid pointers, which again means that 'loan intended' buffer + * should result in bad_parameter. + * However, that's not the case yet. So don't test it. */ + cr_assume(buf != g_loans); + ret = dds_take_instance(*ent, buf, si, bufsz, maxs, g_hdl_valid); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance_wl, invalid_params) = { + DataPoints(dds_entity_t*, &g_reader, &g_rcond, &g_qcond), + DataPoints(void**, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0), + DataPoints(size_t, 0, 2, MAX_SAMPLES), +}; +Theory((dds_entity_t *ent, void **buf, dds_sample_info_t *si, uint32_t maxs), ddsc_take_instance_wl, invalid_params, .init=take_instance_init, .fini=take_instance_fini) +{ + dds_return_t ret; + /* The only valid permutation is when non of the buffer values are + * invalid and neither is the handle. So, don't test that. */ + cr_assume((buf != g_loans) || (si != g_info) || (maxs == 0)); + ret = dds_take_instance_wl(*ent, buf, si, maxs, g_hdl_valid); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance_mask, invalid_params) = { + DataPoints(dds_entity_t*, &g_reader, &g_rcond, &g_qcond), + DataPoints(void**, g_samples, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0), + DataPoints(size_t, 0, 2, MAX_SAMPLES), + DataPoints(uint32_t, 0, 2, MAX_SAMPLES), +}; +Theory((dds_entity_t *ent, void **buf, dds_sample_info_t *si, size_t bufsz, uint32_t maxs), ddsc_take_instance_mask, invalid_params, .init=take_instance_init, .fini=take_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + /* The only valid permutation is when non of the buffer values are + * invalid and neither is the handle. So, don't test that. */ + cr_assume((buf != g_samples) || (si != g_info) || (bufsz == 0) || (maxs == 0) || (bufsz < maxs)); + /* TODO: CHAM-306, currently, a buffer is automatically 'promoted' to a loan when a buffer is + * provided with NULL pointers. So, in fact, there's currently no real difference between calling + * dds_take() dds_take_wl() (except for the provided bufsz). This will change, which means that + * the given buffer should contain valid pointers, which again means that 'loan intended' buffer + * should result in bad_parameter. + * However, that's not the case yet. So don't test it. */ + cr_assume(buf != g_loans); + ret = dds_take_instance_mask(*ent, buf, si, bufsz, maxs, g_hdl_valid, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance_mask_wl, invalid_params) = { + DataPoints(dds_entity_t*, &g_reader, &g_rcond, &g_qcond), + DataPoints(void**, g_loans, (void**)0), + DataPoints(dds_sample_info_t*, g_info, (dds_sample_info_t*)0), + DataPoints(size_t, 0, 2, MAX_SAMPLES), +}; +Theory((dds_entity_t *ent, void **buf, dds_sample_info_t *si, uint32_t maxs), ddsc_take_instance_mask_wl, invalid_params, .init=take_instance_init, .fini=take_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + /* The only valid permutation is when non of the buffer values are + * invalid and neither is the handle. So, don't test that. */ + cr_assume((buf != g_loans) || (si != g_info) || (maxs == 0)); + ret = dds_take_instance_mask_wl(*ent, buf, si, maxs, g_hdl_valid, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the take_instance_* functions with invalid handles. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance, invalid_handles) = { + DataPoints(dds_entity_t*, &g_reader, &g_rcond, &g_qcond), + DataPoints(dds_instance_handle_t, 1, DDS_HANDLE_NIL, 0, 1, 100, UINT64_MAX), +}; +Theory((dds_entity_t *rdr, dds_instance_handle_t hdl), ddsc_take_instance, invalid_handles, .init=take_instance_init, .fini=take_instance_fini) +{ + dds_return_t ret; + ret = dds_take_instance(*rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, hdl); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_PRECONDITION_NOT_MET, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance_wl, invalid_handles) = { + DataPoints(dds_entity_t*, &g_reader, &g_rcond, &g_qcond), + DataPoints(dds_instance_handle_t, DDS_HANDLE_NIL, 0, 1, 100, UINT64_MAX), +}; +Theory((dds_entity_t *rdr, dds_instance_handle_t hdl), ddsc_take_instance_wl, invalid_handles, .init=take_instance_init, .fini=take_instance_fini) +{ + dds_return_t ret; + ret = dds_take_instance_wl(*rdr, g_loans, g_info, MAX_SAMPLES, hdl); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_PRECONDITION_NOT_MET, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance_mask, invalid_handles) = { + DataPoints(dds_entity_t*, &g_reader, &g_rcond, &g_qcond), + DataPoints(dds_instance_handle_t, DDS_HANDLE_NIL, 0, 1, 100, UINT64_MAX), +}; +Theory((dds_entity_t *rdr, dds_instance_handle_t hdl), ddsc_take_instance_mask, invalid_handles, .init=take_instance_init, .fini=take_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + ret = dds_take_instance_mask(*rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, hdl, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_PRECONDITION_NOT_MET, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance_mask_wl, invalid_handles) = { + DataPoints(dds_entity_t*, &g_reader, &g_rcond, &g_qcond), + DataPoints(dds_instance_handle_t, DDS_HANDLE_NIL, 0, 1, 100, UINT64_MAX), +}; +Theory((dds_entity_t *rdr, dds_instance_handle_t hdl), ddsc_take_instance_mask_wl, invalid_handles, .init=take_instance_init, .fini=take_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + ret = dds_take_instance_mask_wl(*rdr, g_loans, g_info, MAX_SAMPLES, hdl, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_PRECONDITION_NOT_MET, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * These will check the take_instance_* functions with invalid readers. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_take_instance, invalid_readers, .init=take_instance_init, .fini=take_instance_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_take_instance(rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance_wl, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_take_instance_wl, invalid_readers, .init=take_instance_init, .fini=take_instance_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_take_instance_wl(rdr, g_loans, g_info, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance_mask, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_take_instance_mask, invalid_readers, .init=take_instance_init, .fini=take_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_take_instance_mask(rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance_mask_wl, invalid_readers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t rdr), ddsc_take_instance_mask_wl, invalid_readers, .init=take_instance_init, .fini=take_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_take_instance_mask_wl(rdr, g_loans, g_info, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + + + + + + +/************************************************************************************************** + * + * These will check the take_instance_* functions with non readers. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_publisher, &g_waitset), +}; +Theory((dds_entity_t *rdr), ddsc_take_instance, non_readers, .init=take_instance_init, .fini=take_instance_fini) +{ + dds_return_t ret; + ret = dds_take_instance(*rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance_wl, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_publisher, &g_waitset), +}; +Theory((dds_entity_t *rdr), ddsc_take_instance_wl, non_readers, .init=take_instance_init, .fini=take_instance_fini) +{ + dds_return_t ret; + ret = dds_take_instance_wl(*rdr, g_loans, g_info, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance_mask, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_publisher, &g_waitset), +}; +Theory((dds_entity_t *rdr), ddsc_take_instance_mask, non_readers, .init=take_instance_init, .fini=take_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + ret = dds_take_instance_mask(*rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance_mask_wl, non_readers) = { + DataPoints(dds_entity_t*, &g_participant, &g_topic, &g_writer, &g_subscriber, &g_publisher, &g_waitset), +}; +Theory((dds_entity_t *rdr), ddsc_take_instance_mask_wl, non_readers, .init=take_instance_init, .fini=take_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + ret = dds_take_instance_mask_wl(*rdr, g_loans, g_info, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + + + + +/************************************************************************************************** + * + * These will check the take_instance_* functions with deleted readers. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance, already_deleted) = { + DataPoints(dds_entity_t*, &g_rcond, &g_qcond, &g_reader), +}; +Theory((dds_entity_t *rdr), ddsc_take_instance, already_deleted, .init=take_instance_init, .fini=take_instance_fini) +{ + dds_return_t ret; + ret = dds_delete(*rdr); + cr_assert_eq(ret, DDS_RETCODE_OK, "prerequisite delete failed: %d", dds_err_nr(ret)); + ret = dds_take_instance(*rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance_wl, already_deleted) = { + DataPoints(dds_entity_t*, &g_rcond, &g_qcond, &g_reader), +}; +Theory((dds_entity_t *rdr), ddsc_take_instance_wl, already_deleted, .init=take_instance_init, .fini=take_instance_fini) +{ + dds_return_t ret; + ret = dds_delete(*rdr); + cr_assert_eq(ret, DDS_RETCODE_OK, "prerequisite delete failed: %d", dds_err_nr(ret)); + ret = dds_take_instance_wl(*rdr, g_loans, g_info, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance_mask, already_deleted) = { + DataPoints(dds_entity_t*, &g_rcond, &g_qcond, &g_reader), +}; +Theory((dds_entity_t *rdr), ddsc_take_instance_mask, already_deleted, .init=take_instance_init, .fini=take_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + ret = dds_delete(*rdr); + cr_assert_eq(ret, DDS_RETCODE_OK, "prerequisite delete failed: %d", dds_err_nr(ret)); + ret = dds_take_instance_mask(*rdr, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_take_instance_mask_wl, already_deleted) = { + DataPoints(dds_entity_t*, &g_rcond, &g_qcond, &g_reader), +}; +Theory((dds_entity_t *rdr), ddsc_take_instance_mask_wl, already_deleted, .init=take_instance_init, .fini=take_instance_fini) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t ret; + ret = dds_delete(*rdr); + cr_assert_eq(ret, DDS_RETCODE_OK, "prerequisite delete failed: %d", dds_err_nr(ret)); + ret = dds_take_instance_mask_wl(*rdr, g_loans, g_info, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + + + + +/************************************************************************************************** + * + * These will check the take_instance_* functions with a valid reader. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_take_instance, reader, .init=take_instance_init, .fini=take_instance_fini) +{ + dds_return_t expected_cnt = 2; + dds_return_t ret; + + ret = dds_take_instance(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 0 | 1 | 2 | not_read | old | alive | <--- + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_take_instance::reader: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* Taken samples should be gone. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_instance_wl, reader, .init=take_instance_init, .fini=take_instance_fini) +{ + dds_return_t expected_cnt = 2; + dds_return_t ret; + + ret = dds_take_instance_wl(g_reader, g_loans, g_info, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 0 | 1 | 2 | not_read | old | alive | <--- + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_take_instance_wl::reader: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Taken samples should be gone. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_instance_mask, reader, .init=take_instance_init, .fini=take_instance_fini) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_take_instance_mask(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | <--- + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_take_instance_mask::reader: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 1; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* Taken samples should be gone. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_instance_mask_wl, reader, .init=take_instance_init, .fini=take_instance_fini) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_take_instance_mask_wl(g_reader, g_loans, g_info, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | <--- + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_take_instance_mask_wl::reader: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 1; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Taken samples should be gone. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + + + + + + +/************************************************************************************************** + * + * These will check the take_instance_* functions with a valid readcondition. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_take_instance, readcondition, .init=take_instance_init, .fini=take_instance_fini) +{ + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_take_instance(g_rcond, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | <--- + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_take_instance::readcondition: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 1; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* Taken samples should be gone. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_instance_wl, readcondition, .init=take_instance_init, .fini=take_instance_fini) +{ + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_take_instance_wl(g_rcond, g_loans, g_info, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | <--- + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_take_instance_wl::readcondition: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 1; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Taken samples should be gone. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_instance_mask, readcondition, .init=take_instance_init, .fini=take_instance_fini) +{ + uint32_t mask = DDS_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 2; + dds_return_t ret; + + ret = dds_take_instance_mask(g_rcond, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 0 | 1 | 2 | not_read | old | alive | <--- + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_take_instance_mask::readcondition: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = i; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* Taken samples should be gone. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_instance_mask_wl, readcondition, .init=take_instance_init, .fini=take_instance_fini) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_NEW_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_take_instance_mask_wl(g_rcond, g_loans, g_info, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | + * | 0 | 1 | 2 | not_read | old | alive | <--- + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_take_instance_mask_wl::readcondition: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 1; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Taken samples should be gone. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + + + + + + +/************************************************************************************************** + * + * These will check the take_instance_* functions with a valid querycondition. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_take_instance, querycondition, .init=take_instance_init, .fini=take_instance_fini) +{ + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_take_instance(g_qcond, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 0 | 1 | 2 | not_read | old | alive | + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_take_instance::querycondition: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 0; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* Taken samples should be gone. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_instance_wl, querycondition, .init=take_instance_init, .fini=take_instance_fini) +{ + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_take_instance_wl(g_qcond, g_loans, g_info, MAX_SAMPLES, g_hdl_valid); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 0 | 1 | 2 | not_read | old | alive | + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_take_instance_wl::querycondition: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 0; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Taken samples should be gone. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_instance_mask, querycondition, .init=take_instance_init, .fini=take_instance_fini) +{ + uint32_t mask = DDS_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_take_instance_mask(g_qcond, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 0 | 1 | 2 | not_read | old | alive | + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_take_instance_mask::querycondition: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 0; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + /* Taken samples should be gone. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_take_instance_mask_wl, querycondition, .init=take_instance_init, .fini=take_instance_fini) +{ + uint32_t mask = DDS_NOT_READ_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + dds_return_t expected_cnt = 1; + dds_return_t ret; + + ret = dds_take_instance_mask_wl(g_qcond, g_loans, g_info, MAX_SAMPLES, g_hdl_valid, mask); + cr_assert_eq(ret, expected_cnt, "# read %d, expected %d", ret, expected_cnt); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_loans[i]; + + /* + * | long_1 | long_2 | long_3 | sst | vst | ist | + * ---------------------------------------------------------- + * | 0 | 0 | 0 | read | old | alive | <--- + * | 0 | 1 | 2 | not_read | old | alive | + * | 1 | 2 | 4 | not_read | new | alive | + * | 2 | 3 | 6 | not_read | new | disposed | + * | 3 | 4 | 8 | not_read | new | no_writers | + */ + PRINT_SAMPLE("ddsc_take_instance_mask_wl::querycondition: Take", (*sample)); + + /* Expected states. */ + int expected_long_1 = 0; + int expected_long_2 = 0; + dds_sample_state_t expected_sst = SAMPLE_SST(expected_long_2); + dds_view_state_t expected_vst = SAMPLE_VST(expected_long_2); + dds_instance_state_t expected_ist = SAMPLE_IST(expected_long_2); + + /* Check data. */ + cr_assert_eq(sample->long_1, expected_long_1 ); + cr_assert_eq(sample->long_2, expected_long_2 ); + cr_assert_eq(sample->long_3, expected_long_2*2); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, expected_sst); + cr_assert_eq(g_info[i].view_state, expected_vst); + cr_assert_eq(g_info[i].instance_state, expected_ist); + } + + ret = dds_return_loan(g_reader, g_loans, ret); + cr_assert_eq (ret, DDS_RETCODE_OK); + + /* Taken samples should be gone. */ + ret = samples_cnt(); + cr_assert_eq(ret, MAX_SAMPLES - expected_cnt, "# samples %d, expected %d", ret, MAX_SAMPLES - expected_cnt); +} +/*************************************************************************************************/ diff --git a/src/core/ddsc/tests/test-common.c b/src/core/ddsc/tests/test-common.c new file mode 100644 index 0000000..8f2dc4c --- /dev/null +++ b/src/core/ddsc/tests/test-common.c @@ -0,0 +1,31 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" + +const char* +entity_kind_str(dds_entity_t ent) { + if(ent <= 0) { + return "(ERROR)"; + } + switch(ent & DDS_ENTITY_KIND_MASK) { + case DDS_KIND_TOPIC: return "Topic"; + case DDS_KIND_PARTICIPANT: return "Participant"; + case DDS_KIND_READER: return "Reader"; + case DDS_KIND_WRITER: return "Writer"; + case DDS_KIND_SUBSCRIBER: return "Subscriber"; + case DDS_KIND_PUBLISHER: return "Publisher"; + case DDS_KIND_COND_READ: return "ReadCondition"; + case DDS_KIND_COND_QUERY: return "QueryCondition"; + case DDS_KIND_WAITSET: return "WaitSet"; + default: return "(INVALID_ENTITY)"; + } +} diff --git a/src/core/ddsc/tests/test-common.h b/src/core/ddsc/tests/test-common.h new file mode 100644 index 0000000..917e4b9 --- /dev/null +++ b/src/core/ddsc/tests/test-common.h @@ -0,0 +1,17 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _TEST_COMMON_H_ +#define _TEST_COMMON_H_ + +const char *entity_kind_str(dds_entity_t ent); + +#endif /* _TEST_COMMON_H_ */ diff --git a/src/core/ddsc/tests/topic.c b/src/core/ddsc/tests/topic.c new file mode 100644 index 0000000..68f82b0 --- /dev/null +++ b/src/core/ddsc/tests/topic.c @@ -0,0 +1,428 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsc/dds.h" +#include "os/os.h" +#include "RoundTrip.h" +#include +#include +#include + +/************************************************************************************************** + * + * Test fixtures + * + *************************************************************************************************/ +static dds_entity_t g_participant = 0; +static dds_entity_t g_topicRtmAddress = 0; +static dds_entity_t g_topicRtmDataType = 0; + +static dds_qos_t *g_qos = NULL; +static dds_qos_t *g_qos_null = NULL; +static dds_listener_t *g_listener = NULL; +static dds_listener_t *g_list_null= NULL; + +#define MAX_NAME_SIZE (100) +char g_topicRtmAddressName[MAX_NAME_SIZE]; +char g_topicRtmDataTypeName[MAX_NAME_SIZE]; +char g_nameBuffer[MAX_NAME_SIZE]; + +static char* +create_topic_name(const char *prefix, char *name, size_t size) +{ + /* Get semi random g_topic name. */ + os_procId pid = os_procIdSelf(); + uintmax_t tid = os_threadIdToInteger(os_threadIdSelf()); + (void) snprintf(name, size, "%s_pid%"PRIprocId"_tid%"PRIuMAX"", prefix, pid, tid); + return name; +} + +static void +ddsc_topic_init(void) +{ + create_topic_name("ddsc_topic_test_rtm_address", g_topicRtmAddressName, MAX_NAME_SIZE); + create_topic_name("ddsc_topic_test_rtm_datatype", g_topicRtmDataTypeName, MAX_NAME_SIZE); + + g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_participant, 0, "Failed to create prerequisite g_participant"); + + g_topicRtmAddress = dds_create_topic(g_participant, &RoundTripModule_Address_desc, g_topicRtmAddressName, NULL, NULL); + cr_assert_gt(g_topicRtmAddress, 0, "Failed to create prerequisite g_topicRtmAddress"); + + g_topicRtmDataType = dds_create_topic(g_participant, &RoundTripModule_DataType_desc, g_topicRtmDataTypeName, NULL, NULL); + cr_assert_gt(g_topicRtmDataType, 0, "Failed to create prerequisite g_topicRtmDataType"); + + g_qos = dds_qos_create(); + g_listener = dds_listener_create(NULL); +} + + +static void +ddsc_topic_fini(void) +{ + dds_qos_delete(g_qos); + dds_listener_delete(g_listener); + dds_delete(g_topicRtmDataType); + dds_delete(g_topicRtmAddress); + dds_delete(g_participant); +} + + +/************************************************************************************************** + * + * These will check the topic creation in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_topic_create, valid) = { + DataPoints(char *, "valid", "_VALID", "Val1d", "valid_", "vA_1d"), + DataPoints(dds_qos_t**, &g_qos_null, &g_qos ), + DataPoints(dds_listener_t**, &g_list_null, &g_listener ), +}; +Theory((char *name, dds_qos_t **qos, dds_listener_t **listener), ddsc_topic_create, valid, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_entity_t topic; + dds_return_t ret; + topic = dds_create_topic(g_participant, &RoundTripModule_DataType_desc, name, *qos, *listener); + cr_assert_gt(topic, 0, "Failed dds_create_topic(par, desc, %s, %p, %p): %s", name, *qos, *listener, dds_err_str(topic)); + ret = dds_delete(topic); + cr_assert_eq(ret, DDS_RETCODE_OK); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_create, invalid_qos, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_entity_t topic; + dds_qos_t *qos = dds_qos_create(); + OS_WARNING_MSVC_OFF(28020); /* Disable SAL warning on intentional misuse of the API */ + dds_qset_lifespan(qos, DDS_SECS(-1)); + OS_WARNING_MSVC_OFF(28020); + topic = dds_create_topic(g_participant, &RoundTripModule_DataType_desc, "inconsistent", qos, NULL); + cr_assert_eq(dds_err_nr(topic), DDS_RETCODE_INCONSISTENT_POLICY, "returned %s", dds_err_str(topic)); + dds_qos_delete(qos); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_create, non_participants, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_entity_t topic; + topic = dds_create_topic(g_topicRtmDataType, &RoundTripModule_DataType_desc, "non_participant", NULL, NULL); + cr_assert_eq(dds_err_nr(topic), DDS_RETCODE_ILLEGAL_OPERATION, "returned %s", dds_err_str(topic)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_create, duplicate, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_entity_t topic; + /* Creating the same topic should fail. */ + topic = dds_create_topic(g_participant, &RoundTripModule_DataType_desc, g_topicRtmDataTypeName, NULL, NULL); + cr_assert_eq(dds_err_nr(topic), DDS_RETCODE_PRECONDITION_NOT_MET, "returned %s", dds_err_str(topic)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_create, same_name, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_entity_t topic; + /* Creating the different topic with same name should fail. */ + topic = dds_create_topic(g_participant, &RoundTripModule_Address_desc, g_topicRtmDataTypeName, NULL, NULL); + cr_assert_eq(dds_err_nr(topic), DDS_RETCODE_PRECONDITION_NOT_MET, "returned %s", dds_err_str(topic)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_create, recreate, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_entity_t topic; + dds_return_t ret; + + /* Re-creating previously created topic should succeed. */ + ret = dds_delete(g_topicRtmDataType); + cr_assert_eq(ret, DDS_RETCODE_OK); + topic = dds_create_topic (g_participant, &RoundTripModule_DataType_desc, g_topicRtmDataTypeName, NULL, NULL); + cr_assert_gt(topic, 0, "returned %s", dds_err_str(topic)); + + ret = dds_delete(topic); + cr_assert_eq(ret, DDS_RETCODE_OK); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_create, desc_null, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_entity_t topic; + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + topic = dds_create_topic (g_participant, NULL, "desc_null", NULL, NULL); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(topic), DDS_RETCODE_BAD_PARAMETER, "returned %s", dds_err_str(topic)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ + + +TheoryDataPoints(ddsc_topic_create, invalid_names) = { + DataPoints(char *, NULL, "", "mi-dle", "-start", "end-", "1st", "Thus$", "pl+s", "t(4)"), +}; +Theory((char *name), ddsc_topic_create, invalid_names, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_entity_t topic; + topic = dds_create_topic(g_participant, &RoundTripModule_DataType_desc, name, NULL, NULL); + cr_assert_eq(dds_err_nr(topic), DDS_RETCODE_BAD_PARAMETER, "dds_create_topic(%s) returned %s", name, dds_err_str(topic)); +} +/*************************************************************************************************/ + + + +/************************************************************************************************** + * + * These will check the topic finding in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_topic_find, valid, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_entity_t topic; + dds_return_t ret; + + topic = dds_find_topic(g_participant, g_topicRtmDataTypeName); + cr_assert_eq(topic, g_topicRtmDataType, "returned %s", dds_err_str(topic)); + + ret = dds_delete(topic); + cr_assert_eq(ret, DDS_RETCODE_OK); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_find, non_participants, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_entity_t topic; + topic = dds_find_topic(g_topicRtmDataType, "non_participant"); + cr_assert_eq(dds_err_nr(topic), DDS_RETCODE_ILLEGAL_OPERATION, "returned %s", dds_err_str(topic)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_find, null, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_entity_t topic; + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + topic = dds_find_topic(g_participant, NULL); + OS_WARNING_MSVC_ON(6387); + cr_assert_eq(dds_err_nr(topic), DDS_RETCODE_BAD_PARAMETER, "returned %s", dds_err_str(topic)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_find, unknown, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_entity_t topic; + topic = dds_find_topic(g_participant, "unknown"); + cr_assert_eq(dds_err_nr(topic), DDS_RETCODE_PRECONDITION_NOT_MET, "returned %s", dds_err_str(topic)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_find, deleted, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_entity_t topic; + dds_delete(g_topicRtmDataType); + topic = dds_find_topic(g_participant, g_topicRtmDataTypeName); + cr_assert_eq(dds_err_nr(topic), DDS_RETCODE_PRECONDITION_NOT_MET, "returned %s", dds_err_str(topic)); +} +/*************************************************************************************************/ + + + +/************************************************************************************************** + * + * These will check getting the topic name in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_topic_get_name, valid, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + char name[MAX_NAME_SIZE]; + dds_return_t ret; + + ret = dds_get_name(g_topicRtmDataType, name, MAX_NAME_SIZE); + cr_assert_eq(ret, DDS_RETCODE_OK); + cr_assert_str_eq(name, g_topicRtmDataTypeName); + + ret = dds_get_name(g_topicRtmAddress, name, MAX_NAME_SIZE); + cr_assert_eq(ret, DDS_RETCODE_OK); + cr_assert_str_eq(name, g_topicRtmAddressName); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_get_name, too_small, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + char name[10]; + dds_return_t ret; + + ret = dds_get_name(g_topicRtmDataType, name, 10); + cr_assert_eq(ret, DDS_RETCODE_OK); + g_topicRtmDataTypeName[9] = '\0'; + cr_assert_str_eq(name, g_topicRtmDataTypeName); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_get_name, non_topic, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + char name[MAX_NAME_SIZE]; + dds_return_t ret; + ret = dds_get_name(g_participant, name, MAX_NAME_SIZE); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %s", dds_err_str(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_topic_get_name, invalid_params) = { + DataPoints(char*, (char*)0, g_nameBuffer), + DataPoints(size_t, 0, MAX_NAME_SIZE), +}; +Theory((char *name, size_t size), ddsc_topic_get_name, invalid_params, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_return_t ret; + cr_assume((name != g_nameBuffer) || (size != MAX_NAME_SIZE)); + ret = dds_get_name(g_topicRtmDataType, name, size); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %s", dds_err_str(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_get_name, deleted, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + char name[MAX_NAME_SIZE]; + dds_return_t ret; + dds_delete(g_topicRtmDataType); + ret = dds_get_name(g_topicRtmDataType, name, MAX_NAME_SIZE); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %s", dds_err_str(ret)); +} +/*************************************************************************************************/ + + + +/************************************************************************************************** + * + * These will check getting the type name in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_topic_get_type_name, valid, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + const char *rtmDataTypeType = "RoundTripModule::DataType"; + const char *rtmAddressType = "RoundTripModule::Address"; + char name[MAX_NAME_SIZE]; + dds_return_t ret; + + ret = dds_get_type_name(g_topicRtmDataType, name, MAX_NAME_SIZE); + cr_assert_eq(ret, DDS_RETCODE_OK); + cr_assert_str_eq(name, rtmDataTypeType); + + ret = dds_get_type_name(g_topicRtmAddress, name, MAX_NAME_SIZE); + cr_assert_eq(ret, DDS_RETCODE_OK); + cr_assert_str_eq(name, rtmAddressType); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_get_type_name, too_small, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + const char *rtmDataTypeType = "RoundTrip"; + char name[10]; + dds_return_t ret; + + ret = dds_get_type_name(g_topicRtmDataType, name, 10); + cr_assert_eq(ret, DDS_RETCODE_OK); + cr_assert_str_eq(name, rtmDataTypeType); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_get_type_name, non_topic, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + char name[MAX_NAME_SIZE]; + dds_return_t ret; + ret = dds_get_type_name(g_participant, name, MAX_NAME_SIZE); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %s", dds_err_str(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_topic_get_type_name, invalid_params) = { + DataPoints(char*, (char*)0, g_nameBuffer), + DataPoints(size_t, 0, MAX_NAME_SIZE), +}; +Theory((char *name, size_t size), ddsc_topic_get_type_name, invalid_params, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_return_t ret; + cr_assume((name != g_nameBuffer) || (size != MAX_NAME_SIZE)); + ret = dds_get_type_name(g_topicRtmDataType, name, size); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %s", dds_err_str(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_get_type_name, deleted, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + char name[MAX_NAME_SIZE]; + dds_return_t ret; + dds_delete(g_topicRtmDataType); + ret = dds_get_type_name(g_topicRtmDataType, name, MAX_NAME_SIZE); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %s", dds_err_str(ret)); +} +/*************************************************************************************************/ + + + +/************************************************************************************************** + * + * These will set the topic qos in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_topic_set_qos, valid, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_return_t ret; + /* Latency is the only one allowed to change. */ + dds_qset_latency_budget(g_qos, DDS_SECS(1)); + ret = dds_set_qos(g_topicRtmDataType, g_qos); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_UNSUPPORTED, "returned %s", dds_err_str(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_set_qos, inconsistent, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_return_t ret; + OS_WARNING_MSVC_OFF(28020); /* Disable SAL warning on intentional misuse of the API */ + dds_qset_lifespan(g_qos, DDS_SECS(-1)); + OS_WARNING_MSVC_ON(28020); + ret = dds_set_qos(g_topicRtmDataType, g_qos); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_INCONSISTENT_POLICY, "returned %s", dds_err_str(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_topic_set_qos, immutable, .init=ddsc_topic_init, .fini=ddsc_topic_fini) +{ + dds_return_t ret; + dds_qset_destination_order(g_qos, DDS_DESTINATIONORDER_BY_SOURCE_TIMESTAMP); /* Immutable */ + ret = dds_set_qos(g_topicRtmDataType, g_qos); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_IMMUTABLE_POLICY, "returned %s", dds_err_str(ret)); +} +/*************************************************************************************************/ diff --git a/src/core/ddsc/tests/transientlocal.c b/src/core/ddsc/tests/transientlocal.c new file mode 100644 index 0000000..c33a818 --- /dev/null +++ b/src/core/ddsc/tests/transientlocal.c @@ -0,0 +1,105 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include "ddsc/dds.h" +#include "Space.h" +#include +#include + +#define TRACE_SAMPLE(info, sample) cr_log_info("%s (%d, %d, %d)\n", info, sample.long_1, sample.long_2, sample.long_3); +#define MAX_SAMPLES (7) +Test(ddsc_transient_local, late_joiner) +{ + Space_Type1 sample = { 0 }; + dds_return_t ret; + dds_entity_t par; + dds_entity_t pub; + dds_entity_t sub; + dds_entity_t top; + dds_entity_t wrt; + dds_entity_t rdr; + dds_qos_t *qos; + static void* samples[MAX_SAMPLES]; + static Space_Type1 data[MAX_SAMPLES]; + static dds_sample_info_t info[MAX_SAMPLES]; + + /* Initialize reading buffers. */ + memset (data, 0, sizeof (data)); + for (int i = 0; i < MAX_SAMPLES; i++) { + samples[i] = &data[i]; + } + + /* Use transient local with reliable. */ + qos = dds_qos_create(); + dds_qset_durability(qos, DDS_DURABILITY_TRANSIENT_LOCAL); + dds_qset_reliability(qos, DDS_RELIABILITY_RELIABLE, DDS_INFINITY); + + /* Create participant and topic. */ + par = dds_create_participant(DDS_DOMAIN_DEFAULT, qos, NULL); + cr_assert_gt(par, 0, "Failed to create prerequisite par"); + top = dds_create_topic(par, &Space_Type1_desc, "ddsc_transient_local_happy_days", qos, NULL); + cr_assert_gt(par, 0, "Failed to create prerequisite top"); + + /* Create publishing entities. */ + cr_log_info("Create writer\n"); + pub = dds_create_publisher(par, qos, NULL); + cr_assert_gt(pub, 0, "Failed to create prerequisite pub"); + wrt = dds_create_writer(pub, top, qos, NULL); + cr_assert_gt(wrt, 0, "Failed to create prerequisite wrt"); + + /* Write first set of samples. */ + sample.long_1 = 1; + sample.long_2 = 1; + sample.long_3 = 1; + TRACE_SAMPLE("Write ", sample); + ret = dds_write(wrt, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + sample.long_1 = 2; + sample.long_2 = 2; + sample.long_3 = 2; + TRACE_SAMPLE("Write ", sample); + ret = dds_write(wrt, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + + /* Create subscribing entities. */ + cr_log_info("Create reader\n"); + sub = dds_create_subscriber(par, qos, NULL); + cr_assert_gt(sub, 0, "Failed to create prerequisite sub"); + rdr = dds_create_reader(sub, top, qos, NULL); + cr_assert_gt(rdr, 0, "Failed to create prerequisite g_reader"); + + /* Write second set of samples. */ + sample.long_1 = 8; + sample.long_2 = 8; + sample.long_3 = 8; + TRACE_SAMPLE("Write ", sample); + ret = dds_write(wrt, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + sample.long_1 = 9; + sample.long_2 = 9; + sample.long_3 = 9; + TRACE_SAMPLE("Write ", sample); + ret = dds_write(wrt, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + + /* Read samples, which should be all four. */ + ret = dds_read(rdr, samples, info, MAX_SAMPLES, MAX_SAMPLES); + cr_log_info("Read cnt %d\n", ret); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)samples[i]; + TRACE_SAMPLE("Read ", (*sample)); + } + cr_assert_eq(ret, 4, "# read %d, expected 4", ret); + + dds_delete(par); + dds_qos_delete(qos); +} diff --git a/src/core/ddsc/tests/types.c b/src/core/ddsc/tests/types.c new file mode 100644 index 0000000..463eaf0 --- /dev/null +++ b/src/core/ddsc/tests/types.c @@ -0,0 +1,123 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include "ddsc/dds.h" +#include "TypesArrayKey.h" + + +#define strfy(s) #s +#define DDSC_ARRAYTYPEKEY_TEST(type, init) \ + do { \ + dds_return_t status; \ + dds_entity_t par, top, wri; \ + TypesArrayKey_##type##_arraytypekey data; \ + for( unsigned i = 0; i < (sizeof data.key / sizeof *data.key); i++) data.key[i] = (init); \ + \ + par = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); \ + cr_assert_gt(par, 0); \ + top = dds_create_topic(par, &TypesArrayKey_##type##_arraytypekey_desc, strfy(type), NULL, NULL); \ + cr_assert_gt(top, 0); \ + wri = dds_create_writer(par, top, NULL, NULL); \ + cr_assert_gt(wri, 0); \ + \ + status = dds_write(wri, &data); \ + cr_assert_eq(dds_err_nr(status), DDS_RETCODE_OK); \ + \ + dds_delete(wri); \ + dds_delete(top); \ + dds_delete(par); \ + } while (0) + + +Test(ddsc_types, long_arraytypekey) +{ + DDSC_ARRAYTYPEKEY_TEST(long, 1); +} + +Test(ddsc_types, longlong_arraytypekey) +{ + DDSC_ARRAYTYPEKEY_TEST(longlong, 1); +} + +Test(ddsc_types, unsignedshort_arraytypekey) +{ + DDSC_ARRAYTYPEKEY_TEST(unsignedshort, 1); +} + +Test(ddsc_types, unsignedlong_arraytypekey) +{ + DDSC_ARRAYTYPEKEY_TEST(unsignedlong, 1); +} + +Test(ddsc_types, unsignedlonglong_arraytypekey) +{ + DDSC_ARRAYTYPEKEY_TEST(unsignedlonglong, 1); +} + +Test(ddsc_types, float_arraytypekey) +{ + DDSC_ARRAYTYPEKEY_TEST(float, 1.0f); +} + +Test(ddsc_types, double_arraytypekey) +{ + DDSC_ARRAYTYPEKEY_TEST(double, 1.0f); +} + +Test(ddsc_types, char_arraytypekey) +{ + DDSC_ARRAYTYPEKEY_TEST(char, '1'); +} + +Test(ddsc_types, boolean_arraytypekey) +{ + DDSC_ARRAYTYPEKEY_TEST(boolean, true); +} + +Test(ddsc_types, octet_arraytypekey) +{ + DDSC_ARRAYTYPEKEY_TEST(octet, 1); +} + +Test(ddsc_types, alltypeskey) +{ + dds_return_t status; + dds_entity_t par, top, wri; + const TypesArrayKey_alltypeskey atk_data = { + .l = -1, + .ll = -1, + .us = 1, + .ul = 1, + .ull = 1, + .f = 1.0f, + .d = 1.0f, + .c = '1', + .b = true, + .o = 1, + .s = "1" + }; + + par = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(par, 0); + top = dds_create_topic(par, &TypesArrayKey_alltypeskey_desc, "AllTypesKey", NULL, NULL); + cr_assert_gt(top, 0); + wri = dds_create_writer(par, top, NULL, NULL); + cr_assert_gt(wri, 0); + + status = dds_write(wri, &atk_data); + cr_assert_eq(dds_err_nr(status), DDS_RETCODE_OK); + + dds_delete(wri); + dds_delete(top); + dds_delete(par); +} diff --git a/src/core/ddsc/tests/unregister.c b/src/core/ddsc/tests/unregister.c new file mode 100644 index 0000000..6c23d8e --- /dev/null +++ b/src/core/ddsc/tests/unregister.c @@ -0,0 +1,709 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include + +#include "ddsc/dds.h" +#include "os/os.h" +#include +#include +#include +#include "Space.h" + + +/************************************************************************************************** + * + * Test fixtures + * + *************************************************************************************************/ +#define MAX_SAMPLES 7 +#define INITIAL_SAMPLES 2 + +static dds_entity_t g_participant = 0; +static dds_entity_t g_topic = 0; +static dds_entity_t g_reader = 0; +static dds_entity_t g_writer = 0; +static dds_entity_t g_waitset = 0; + +static dds_time_t g_past = 0; +static dds_time_t g_present = 0; + +static void* g_samples[MAX_SAMPLES]; +static Space_Type1 g_data[MAX_SAMPLES]; +static dds_sample_info_t g_info[MAX_SAMPLES]; + +static char* +create_topic_name(const char *prefix, char *name, size_t size) +{ + /* Get semi random g_topic name. */ + os_procId pid = os_procIdSelf(); + uintmax_t tid = os_threadIdToInteger(os_threadIdSelf()); + (void) snprintf(name, size, "%s_pid%"PRIprocId"_tid%"PRIuMAX"", prefix, pid, tid); + return name; +} + +static void +unregistering_init(void) +{ + Space_Type1 sample = { 0 }; + dds_qos_t *qos = dds_qos_create (); + dds_attach_t triggered; + dds_return_t ret; + char name[100]; + + /* Use by source timestamp to be able to check the time related funtions. */ + dds_qset_destination_order(qos, DDS_DESTINATIONORDER_BY_SOURCE_TIMESTAMP); + + g_participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(g_participant, 0, "Failed to create prerequisite g_participant"); + + g_waitset = dds_create_waitset(g_participant); + cr_assert_gt(g_waitset, 0, "Failed to create g_waitset"); + + g_topic = dds_create_topic(g_participant, &Space_Type1_desc, create_topic_name("ddsc_unregistering_test", name, 100), qos, NULL); + cr_assert_gt(g_topic, 0, "Failed to create prerequisite g_topic"); + + /* Create a reader that keeps one sample on three instances. */ + dds_qset_reliability(qos, DDS_RELIABILITY_RELIABLE, DDS_MSECS(100)); + dds_qset_resource_limits(qos, DDS_LENGTH_UNLIMITED, 3, 1); + g_reader = dds_create_reader(g_participant, g_topic, qos, NULL); + cr_assert_gt(g_reader, 0, "Failed to create prerequisite g_reader"); + + /* Create a writer that will not automatically dispose unregistered samples. */ + dds_qset_writer_data_lifecycle(qos, false); + g_writer = dds_create_writer(g_participant, g_topic, qos, NULL); + cr_assert_gt(g_writer, 0, "Failed to create prerequisite g_writer"); + + /* Sync g_writer to g_reader. */ + ret = dds_set_enabled_status(g_writer, DDS_PUBLICATION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_writer status"); + ret = dds_waitset_attach(g_waitset, g_writer, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_writer"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_writer r"); + cr_assert_eq(g_writer, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_writer a"); + ret = dds_waitset_detach(g_waitset, g_writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_writer"); + + /* Sync g_reader to g_writer. */ + ret = dds_set_enabled_status(g_reader, DDS_SUBSCRIPTION_MATCHED_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_reader status"); + ret = dds_waitset_attach(g_waitset, g_reader, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_reader"); + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Failed prerequisite dds_waitset_wait g_reader r"); + cr_assert_eq(g_reader, (dds_entity_t)(intptr_t)triggered, "Failed prerequisite dds_waitset_wait g_reader a"); + ret = dds_waitset_detach(g_waitset, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to detach prerequisite g_reader"); + + /* Write initial samples. */ + for (int i = 0; i < INITIAL_SAMPLES; i++) { + sample.long_1 = i; + sample.long_2 = i*2; + sample.long_3 = i*3; + ret = dds_write(g_writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed prerequisite write"); + } + + /* Initialize reading buffers. */ + memset (g_data, 0, sizeof (g_data)); + for (int i = 0; i < MAX_SAMPLES; i++) { + g_samples[i] = &g_data[i]; + } + + /* Initialize times. */ + g_present = dds_time(); + g_past = g_present - DDS_SECS(1); + + dds_qos_delete(qos); +} + +static void +unregistering_fini(void) +{ + dds_delete(g_reader); + dds_delete(g_writer); + dds_delete(g_waitset); + dds_delete(g_topic); + dds_delete(g_participant); +} + + +#if 0 +#else +/************************************************************************************************** + * + * These will check the dds_unregister_instance() in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_unregister_instance, deleted, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_return_t ret; + dds_delete(g_writer); + + ret = dds_unregister_instance(g_writer, g_data); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_unregister_instance, null, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_return_t ret; + ret = dds_unregister_instance(g_writer, NULL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_unregister_instance, invalid_writers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t writer), ddsc_unregister_instance, invalid_writers, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_unregister_instance(writer, g_data); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_unregister_instance, non_writers) = { + DataPoints(dds_entity_t*, &g_waitset, &g_reader, &g_topic, &g_participant), +}; +Theory((dds_entity_t *writer), ddsc_unregister_instance, non_writers, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_return_t ret; + ret = dds_unregister_instance(*writer, g_data); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_unregister_instance, unregistering_old_instance, .init=unregistering_init, .fini=unregistering_fini) +{ + Space_Type1 oldInstance = { 0, 22, 22 }; + dds_return_t ret; + + ret = dds_unregister_instance(g_writer, &oldInstance); + cr_assert_eq(ret, DDS_RETCODE_OK, "Unregistering old instance returned %d", dds_err_nr(ret)); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 == 0) { + /* Check data. */ + cr_assert_eq(sample->long_2, 0); + cr_assert_eq(sample->long_3, 0); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE); + } else if (sample->long_1 == 1) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else { + cr_assert(false, "Unknown sample read"); + } + } +} +/*************************************************************************************************/ + + + + +/************************************************************************************************** + * + * These will check the dds_unregister_instance_ts() in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_unregister_instance_ts, deleted, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_return_t ret; + dds_delete(g_writer); + ret = dds_unregister_instance_ts(g_writer, g_data, g_present); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_unregister_instance_ts, null, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_return_t ret; + ret = dds_unregister_instance_ts(g_writer, NULL, g_present); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_unregister_instance_ts, invalid_writers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t writer), ddsc_unregister_instance_ts, invalid_writers, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_unregister_instance_ts(writer, g_data, g_present); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_unregister_instance_ts, non_writers) = { + DataPoints(dds_entity_t*, &g_waitset, &g_reader, &g_topic, &g_participant), +}; +Theory((dds_entity_t *writer), ddsc_unregister_instance_ts, non_writers, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_return_t ret; + ret = dds_unregister_instance_ts(*writer, g_data, g_present); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_unregister_instance_ts, unregistering_old_instance, .init=unregistering_init, .fini=unregistering_fini) +{ + Space_Type1 oldInstance = { 0, 22, 22 }; + dds_return_t ret; + + ret = dds_unregister_instance_ts(g_writer, &oldInstance, g_present); + cr_assert_eq(ret, DDS_RETCODE_OK, "Unregistering old instance returned %d", dds_err_nr(ret)); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 == 0) { + /* Check data (data part of unregister is not used, only the key part). */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE); + } else if (sample->long_1 == 1) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else { + cr_assert(false, "Unknown sample read"); + } + } +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_unregister_instance_ts, unregistering_past_sample, .init=unregistering_init, .fini=unregistering_fini) +{ + Space_Type1 oldInstance = { 0, 0, 0 }; + dds_attach_t triggered; + dds_return_t ret; + + /* Unregistering a sample in the past should trigger a lost sample. */ + ret = dds_set_enabled_status(g_reader, DDS_SAMPLE_LOST_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_reader status"); + ret = dds_waitset_attach(g_waitset, g_reader, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_reader"); + + /* Now, unregister a sample in the past. */ + ret = dds_unregister_instance_ts(g_writer, &oldInstance, g_past); + cr_assert_eq(ret, DDS_RETCODE_OK, "Unregistering old instance returned %d", dds_err_nr(ret)); + + /* Wait for 'sample lost'. */ + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Unregistering past sample did not trigger 'sample lost'"); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if ((sample->long_1 == 0) || (sample->long_1 == 1)) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + } else { + cr_assert(false, "Unknown sample read"); + } + } + +} +/*************************************************************************************************/ + + + + +/************************************************************************************************** + * + * These will check the dds_unregister_instance_ih() in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_unregister_instance_ih, deleted, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_return_t ret; + dds_delete(g_writer); + ret = dds_unregister_instance_ih(g_writer, DDS_HANDLE_NIL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_unregister_instance_ih, invalid_handles) = { + DataPoints(dds_instance_handle_t, DDS_HANDLE_NIL, 0, 1, 100, UINT64_MAX), +}; +Theory((dds_instance_handle_t handle), ddsc_unregister_instance_ih, invalid_handles, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_return_t ret; + ret = dds_unregister_instance_ih(g_writer, handle); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_PRECONDITION_NOT_MET, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_unregister_instance_ih, invalid_writers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t writer), ddsc_unregister_instance_ih, invalid_writers, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_unregister_instance_ih(writer, DDS_HANDLE_NIL); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_unregister_instance_ih, non_writers) = { + DataPoints(dds_entity_t*, &g_waitset, &g_reader, &g_topic, &g_participant), +}; +Theory((dds_entity_t *writer), ddsc_unregister_instance_ih, non_writers, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_return_t ret; + ret = dds_unregister_instance_ih(*writer, DDS_HANDLE_NIL); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_unregister_instance_ih, unregistering_old_instance, .init=unregistering_init, .fini=unregistering_fini) +{ + Space_Type1 oldInstance = { 0, 22, 22 }; + dds_instance_handle_t hdl = dds_instance_lookup(g_writer, &oldInstance); + dds_return_t ret; + + ret = dds_unregister_instance_ih(g_writer, hdl); + cr_assert_eq(ret, DDS_RETCODE_OK, "Unregistering old instance returned %d", dds_err_nr(ret)); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 == 0) { + /* Check data. */ + cr_assert_eq(sample->long_2, 0); + cr_assert_eq(sample->long_3, 0); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE); + } else if (sample->long_1 == 1) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else { + cr_assert(false, "Unknown sample read"); + } + } +} +/*************************************************************************************************/ + + + + +/************************************************************************************************** + * + * These will check the dds_unregister_instance_ih_ts() in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_unregister_instance_ih_ts, deleted, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_return_t ret; + dds_delete(g_writer); + ret = dds_unregister_instance_ih_ts(g_writer, DDS_HANDLE_NIL, g_present); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_unregister_instance_ih_ts, invalid_handles) = { + DataPoints(dds_instance_handle_t, DDS_HANDLE_NIL, 0, 1, 100, UINT64_MAX), +}; +Theory((dds_instance_handle_t handle), ddsc_unregister_instance_ih_ts, invalid_handles, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_return_t ret; + ret = dds_unregister_instance_ih_ts(g_writer, handle, g_present); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_PRECONDITION_NOT_MET, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_unregister_instance_ih_ts, invalid_writers) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t writer), ddsc_unregister_instance_ih_ts, invalid_writers, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_unregister_instance_ih_ts(writer, DDS_HANDLE_NIL, g_present); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_unregister_instance_ih_ts, non_writers) = { + DataPoints(dds_entity_t*, &g_waitset, &g_reader, &g_topic, &g_participant), +}; +Theory((dds_entity_t *writer), ddsc_unregister_instance_ih_ts, non_writers, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_return_t ret; + ret = dds_unregister_instance_ih_ts(*writer, DDS_HANDLE_NIL, g_present); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_unregister_instance_ih_ts, unregistering_old_instance, .init=unregistering_init, .fini=unregistering_fini) +{ + Space_Type1 oldInstance = { 0, 22, 22 }; + dds_instance_handle_t hdl = dds_instance_lookup(g_writer, &oldInstance); + dds_return_t ret; + + ret = dds_unregister_instance_ih_ts(g_writer, hdl, g_present); + cr_assert_eq(ret, DDS_RETCODE_OK, "Unregistering old instance returned %d", dds_err_nr(ret)); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 == 0) { + /* Check data (data part of unregister is not used, only the key part). */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_NOT_ALIVE_NO_WRITERS_INSTANCE_STATE); + } else if (sample->long_1 == 1) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else { + cr_assert(false, "Unknown sample read"); + } + } +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_unregister_instance_ih_ts, unregistering_past_sample, .init=unregistering_init, .fini=unregistering_fini) +{ + Space_Type1 oldInstance = { 0, 0, 0 }; + dds_instance_handle_t hdl = dds_instance_lookup(g_writer, &oldInstance); + dds_attach_t triggered; + dds_return_t ret; + + /* Unregistering a sample in the past should trigger a lost sample. */ + ret = dds_set_enabled_status(g_reader, DDS_SAMPLE_LOST_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to set prerequisite g_reader status"); + ret = dds_waitset_attach(g_waitset, g_reader, g_reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite g_reader"); + + /* Now, unregister a sample in the past. */ + ret = dds_unregister_instance_ih_ts(g_writer, hdl, g_past); + cr_assert_eq(ret, DDS_RETCODE_OK, "Unregistering old instance returned %d", dds_err_nr(ret)); + + /* Wait for 'sample lost'. */ + ret = dds_waitset_wait(g_waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "Unregistering past sample did not trigger 'sample lost'"); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 2, "# read %d, expected %d", ret, 2); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if ((sample->long_1 == 0) || (sample->long_1 == 1)) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + } else { + cr_assert(false, "Unknown sample read"); + } + } + +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_unregister_instance, dispose_unregistered_sample, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_entity_t writer; + writer = dds_create_writer(g_participant, g_topic, NULL, NULL); + cr_assert_gt(g_writer, 0, "Failed to create writer"); + + Space_Type1 newInstance = { INITIAL_SAMPLES, 0, 0 }; + dds_return_t ret; + + ret = dds_write(writer, &newInstance); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed write"); + + ret = dds_unregister_instance(writer, &newInstance); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing unregistered sample returned %d", dds_err_nr(ret)); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 3, "# read %d, expected %d", ret, 3); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 <= 1) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else if (sample->long_1 == 2) { + /* Check data. */ + cr_assert_eq(sample->long_2, 0); + cr_assert_eq(sample->long_3, 0); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE); + } else { + cr_assert(false, "Unknown sample read"); + } + } + dds_delete(writer); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_unregister_instance_ts, dispose_unregistered_sample, .init=unregistering_init, .fini=unregistering_fini) +{ + dds_entity_t writer; + writer = dds_create_writer(g_participant, g_topic, NULL, NULL); + cr_assert_gt(g_writer, 0, "Failed to create writer"); + + Space_Type1 newInstance = { INITIAL_SAMPLES, 0, 0 }; + dds_return_t ret; + + ret = dds_write(writer, &newInstance); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed write"); + + ret = dds_unregister_instance(writer, &newInstance); + cr_assert_eq(ret, DDS_RETCODE_OK, "Disposing unregistered sample returned %d", dds_err_nr(ret)); + + /* Read all available samples. */ + ret = dds_read(g_reader, g_samples, g_info, MAX_SAMPLES, MAX_SAMPLES); + cr_assert_eq(ret, 3, "# read %d, expected %d", ret, 3); + for(int i = 0; i < ret; i++) { + Space_Type1 *sample = (Space_Type1*)g_samples[i]; + if (sample->long_1 <= 1) { + /* Check data. */ + cr_assert_eq(sample->long_2, sample->long_1 * 2); + cr_assert_eq(sample->long_3, sample->long_1 * 3); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_ALIVE_INSTANCE_STATE); + } else if (sample->long_1 == 2) { + /* Check data. */ + cr_assert_eq(sample->long_2, 0); + cr_assert_eq(sample->long_3, 0); + + /* Check states. */ + cr_assert_eq(g_info[i].valid_data, true); + cr_assert_eq(g_info[i].sample_state, DDS_SST_NOT_READ); + cr_assert_eq(g_info[i].view_state, DDS_VST_NEW); + cr_assert_eq(g_info[i].instance_state, DDS_NOT_ALIVE_DISPOSED_INSTANCE_STATE); + } else { + cr_assert(false, "Unknown sample read"); + } + } + dds_delete(writer); +} +/*************************************************************************************************/ + +#endif diff --git a/src/core/ddsc/tests/unsupported.c b/src/core/ddsc/tests/unsupported.c new file mode 100644 index 0000000..c809791 --- /dev/null +++ b/src/core/ddsc/tests/unsupported.c @@ -0,0 +1,182 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include + +#include "ddsc/dds.h" +#include "RoundTrip.h" + +#include "test-common.h" + +static dds_entity_t e[8]; + +#define PAR (0) /* Participant */ +#define TOP (1) /* Topic */ +#define PUB (2) /* Publisher */ +#define WRI (3) /* Writer */ +#define SUB (4) /* Subscriber */ +#define REA (5) /* Reader */ +#define RCD (6) /* ReadCondition */ +#define BAD (7) /* Bad (non-entity) */ + +static void +setup(void) +{ + e[PAR] = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(e[PAR], 0); + e[TOP] = dds_create_topic(e[PAR], &RoundTripModule_DataType_desc, "RoundTrip", NULL, NULL); + cr_assert_gt(e[TOP], 0); + e[PUB] = dds_create_publisher(e[PAR], NULL, NULL); + cr_assert_gt(e[PUB], 0); + e[WRI] = dds_create_writer(e[PUB], e[TOP], NULL, NULL); + cr_assert_gt(e[WRI], 0); + e[SUB] = dds_create_subscriber(e[PAR], NULL, NULL); + cr_assert_gt(e[SUB], 0); + e[REA] = dds_create_reader(e[SUB], e[TOP], NULL, NULL); + cr_assert_gt(e[REA], 0); + e[RCD] = dds_create_readcondition(e[REA], DDS_ANY_STATE); + cr_assert_gt(e[RCD], 0); + e[BAD] = 1; +} + +static void +teardown(void) +{ + for(unsigned i = (sizeof e / sizeof *e); i > 0; i--) { + dds_delete(e[i - 1]); + } +} + +/*************************************************************************************************/ +struct index_result { + unsigned index; + dds_return_t exp_res; +}; + +/*************************************************************************************************/ +ParameterizedTestParameters(ddsc_unsupported, dds_begin_end_coherent) { + /* The parameters seem to be initialized before spawning children, + * so it makes no sense to try and store anything dynamic here. */ + static struct index_result pars[] = { + {PUB, DDS_RETCODE_UNSUPPORTED}, + {WRI, DDS_RETCODE_UNSUPPORTED}, + {SUB, DDS_RETCODE_UNSUPPORTED}, + {REA, DDS_RETCODE_UNSUPPORTED}, + {BAD, DDS_RETCODE_BAD_PARAMETER} + }; + + return cr_make_param_array(struct index_result, pars, sizeof pars / sizeof *pars); +}; + +ParameterizedTest(struct index_result *par, ddsc_unsupported, dds_begin_end_coherent, .init = setup, .fini = teardown) +{ + dds_return_t result; + result = dds_begin_coherent(e[par->index]); + cr_expect_eq(dds_err_nr(result), par->exp_res, "Unexpected return code %d \"%s\" (expected %d \"%s\") from dds_begin_coherent(%s): (%d)", dds_err_nr(result), dds_err_str(result), par->exp_res, dds_err_str(-par->exp_res), entity_kind_str(e[par->index]), result); + + result = dds_end_coherent(e[par->index]); + cr_expect_eq(dds_err_nr(result), par->exp_res, "Unexpected return code %d \"%s\" (expected %d \"%s\") from dds_end_coherent(%s): (%d)", dds_err_nr(result), dds_err_str(result), par->exp_res, dds_err_str(-par->exp_res), entity_kind_str(e[par->index]), result); +} + +/*************************************************************************************************/ +ParameterizedTestParameters(ddsc_unsupported, dds_wait_for_acks) { + static struct index_result pars[] = { + {PUB, DDS_RETCODE_UNSUPPORTED}, + {WRI, DDS_RETCODE_UNSUPPORTED}, + {BAD, DDS_RETCODE_BAD_PARAMETER} + }; + + return cr_make_param_array(struct index_result, pars, sizeof pars / sizeof *pars); +}; + +ParameterizedTest(struct index_result *par, ddsc_unsupported, dds_wait_for_acks, .init = setup, .fini = teardown) +{ + dds_return_t result; + result = dds_wait_for_acks(e[par->index], 0); + cr_expect_eq(dds_err_nr(result), par->exp_res, "Unexpected return code %d \"%s\" (expected %d \"%s\") from dds_wait_for_acks(%s, 0): (%d)", dds_err_nr(result), dds_err_str(result), par->exp_res, dds_err_str(-par->exp_res), entity_kind_str(e[par->index]), result); +} + +/*************************************************************************************************/ +ParameterizedTestParameters(ddsc_unsupported, dds_suspend_resume) { + static struct index_result pars[] = { + {PUB, DDS_RETCODE_UNSUPPORTED}, + {WRI, DDS_RETCODE_BAD_PARAMETER}, + {BAD, DDS_RETCODE_BAD_PARAMETER} + }; + + return cr_make_param_array(struct index_result, pars, sizeof pars / sizeof *pars); +}; + +ParameterizedTest(struct index_result *par, ddsc_unsupported, dds_suspend_resume, .init = setup, .fini = teardown) +{ + dds_return_t result; + result = dds_suspend(e[par->index]); + cr_expect_eq(dds_err_nr(result), par->exp_res, "Unexpected return code %d \"%s\" (expected %d \"%s\") from dds_suspend(%s): (%d)", dds_err_nr(result), dds_err_str(result), par->exp_res, dds_err_str(-par->exp_res), entity_kind_str(e[par->index]), result); + + result = dds_resume(e[par->index]); + cr_expect_eq(dds_err_nr(result), par->exp_res, "Unexpected return code %d \"%s\" (expected %d \"%s\") from dds_resume(%s): (%d)", dds_err_nr(result), dds_err_str(result), par->exp_res, dds_err_str(-par->exp_res), entity_kind_str(e[par->index]), result); +} + +/*************************************************************************************************/ +ParameterizedTestParameters(ddsc_unsupported, dds_get_instance_handle) { + /* The parameters seem to be initialized before spawning children, + * so it makes no sense to try and store anything dynamic here. */ + static struct index_result pars[] = { + {TOP, DDS_RETCODE_ILLEGAL_OPERATION}, /* TODO: Shouldn't this be either supported or unsupported? */ + {PUB, DDS_RETCODE_UNSUPPORTED}, + {SUB, DDS_RETCODE_UNSUPPORTED}, + {RCD, DDS_RETCODE_ILLEGAL_OPERATION}, + {BAD, DDS_RETCODE_BAD_PARAMETER} + }; + + return cr_make_param_array(struct index_result, pars, sizeof pars / sizeof *pars); +}; + +ParameterizedTest(struct index_result *par, ddsc_unsupported, dds_get_instance_handle, .init = setup, .fini = teardown) +{ + dds_return_t result; + dds_instance_handle_t ih; + + result = dds_get_instance_handle(e[par->index], &ih); + cr_expect_eq(dds_err_nr(result), par->exp_res, "Unexpected return code %d \"%s\" (expected %d \"%s\") from dds_get_instance_handle(%s): (%d)", dds_err_nr(result), dds_err_str(result), par->exp_res, dds_err_str(-par->exp_res), entity_kind_str(e[par->index]), result); +} + +/*************************************************************************************************/ +ParameterizedTestParameters(ddsc_unsupported, dds_set_qos) { + /* The parameters seem to be initialized before spawning children, + * so it makes no sense to try and store anything dynamic here. */ + static struct index_result pars[] = { + {PAR, DDS_RETCODE_UNSUPPORTED}, + {TOP, DDS_RETCODE_UNSUPPORTED}, + {PUB, DDS_RETCODE_UNSUPPORTED}, + {WRI, DDS_RETCODE_UNSUPPORTED}, + {SUB, DDS_RETCODE_UNSUPPORTED}, + {REA, DDS_RETCODE_UNSUPPORTED}, + {RCD, DDS_RETCODE_ILLEGAL_OPERATION}, + {BAD, DDS_RETCODE_BAD_PARAMETER} + }; + + return cr_make_param_array(struct index_result, pars, sizeof pars / sizeof *pars); +}; + +ParameterizedTest(struct index_result *par, ddsc_unsupported, dds_set_qos, .init = setup, .fini = teardown) +{ + dds_return_t result; + dds_qos_t *qos; + + qos = dds_qos_create(); + result = dds_set_qos(e[par->index], qos); + cr_expect_eq(dds_err_nr(result), par->exp_res, "Unexpected return code %d \"%s\" (expected %d \"%s\") from dds_set_qos(%s, qos): (%d)", dds_err_nr(result), dds_err_str(result), par->exp_res, dds_err_str(-par->exp_res), entity_kind_str(e[par->index]), result); + dds_qos_delete(qos); +} diff --git a/src/core/ddsc/tests/waitset.c b/src/core/ddsc/tests/waitset.c new file mode 100644 index 0000000..9812341 --- /dev/null +++ b/src/core/ddsc/tests/waitset.c @@ -0,0 +1,1129 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include + +#include "ddsc/dds.h" +#include "os/os.h" +#include +#include +#include +#include "RoundTrip.h" + +/* Add --verbose command line argument to get the cr_log_info traces (if there are any). */ + +/************************************************************************************************** + * + * Some thread related convenience stuff. + * + *************************************************************************************************/ + +typedef enum thread_state_t { + STARTING, + WAITING, + STOPPED +} thread_state_t; + +typedef struct thread_arg_t { + os_threadId tid; + thread_state_t state; + dds_entity_t expected; +} thread_arg_t; + +static void waiting_thread_start(struct thread_arg_t *arg, dds_entity_t expected); +static dds_return_t waiting_thread_expect_exit(struct thread_arg_t *arg); + + + + +/************************************************************************************************** + * + * Test fixtures + * + *************************************************************************************************/ + +static dds_entity_t participant = 0; +static dds_entity_t topic = 0; +static dds_entity_t writer = 0; +static dds_entity_t reader = 0; +static dds_entity_t waitset = 0; +static dds_entity_t publisher = 0; +static dds_entity_t subscriber = 0; +static dds_entity_t readcond = 0; + + +static char* +create_topic_name(const char *prefix, char *name, size_t size) +{ + /* Get semi random g_topic name. */ + os_procId pid = os_procIdSelf(); + uintmax_t tid = os_threadIdToInteger(os_threadIdSelf()); + (void) snprintf(name, size, "%s_pid%"PRIprocId"_tid%"PRIuMAX"", prefix, pid, tid); + return name; +} + + +static void +ddsc_waitset_basic_init(void) +{ + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0, "Failed to create prerequisite participant"); + + waitset = dds_create_waitset(participant); + cr_assert_gt(waitset, 0, "Failed to create waitset"); +} + +static void +ddsc_waitset_basic_fini(void) +{ + /* It shouldn't matter if any of these entities were deleted previously. + * dds_delete will just return an error, which we don't check. */ + dds_delete(waitset); + dds_delete(participant); +} + +static void +ddsc_waitset_init(void) +{ + uint32_t mask = DDS_ANY_SAMPLE_STATE | DDS_ANY_VIEW_STATE | DDS_ANY_INSTANCE_STATE; + char name[100]; + + os_osInit(); + + ddsc_waitset_basic_init(); + + publisher = dds_create_publisher(participant, NULL, NULL); + cr_assert_gt(publisher, 0, "Failed to create prerequisite publisher"); + + subscriber = dds_create_subscriber(participant, NULL, NULL); + cr_assert_gt(subscriber, 0, "Failed to create prerequisite subscriber"); + + topic = dds_create_topic(participant, &RoundTripModule_DataType_desc, create_topic_name("ddsc_waitset_test", name, sizeof name), NULL, NULL); + cr_assert_gt(topic, 0, "Failed to create prerequisite topic"); + + reader = dds_create_reader(subscriber, topic, NULL, NULL); + cr_assert_gt(reader, 0, "Failed to create prerequisite reader"); + + writer = dds_create_writer(publisher, topic, NULL, NULL); + cr_assert_gt(writer, 0, "Failed to create prerequisite writer"); + + readcond = dds_create_readcondition(reader, mask); + cr_assert_gt(readcond, 0, "Failed to create prerequisite publisher"); +} + +static void +ddsc_waitset_fini(void) +{ + /* It shouldn't matter if any of these entities were deleted previously. + * dds_delete will just return an error, which we don't check. */ + dds_delete(readcond); + dds_delete(writer); + dds_delete(reader); + dds_delete(topic); + dds_delete(publisher); + dds_delete(subscriber); + ddsc_waitset_basic_fini(); + os_osExit(); +} + +static void +ddsc_waitset_attached_init(void) +{ + dds_return_t ret; + + ddsc_waitset_init(); + + /* Start with interest in nothing. */ + ret = dds_set_enabled_status(participant, 0); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to remove prerequisite participant status"); + ret = dds_set_enabled_status(writer, 0); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to remove prerequisite writer status"); + ret = dds_set_enabled_status(reader, 0); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to remove prerequisite reader status"); + ret = dds_set_enabled_status(topic, 0); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to remove prerequisite topic status"); + ret = dds_set_enabled_status(publisher, 0); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to remove prerequisite publisher status"); + ret = dds_set_enabled_status(subscriber, 0); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to remove prerequisite subscriber status"); + + /* Attach all entities to the waitset. */ + ret = dds_waitset_attach(waitset, participant, participant); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite participant"); + ret = dds_waitset_attach(waitset, waitset, waitset); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite waitset"); + ret = dds_waitset_attach(waitset, writer, writer); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite writer"); + ret = dds_waitset_attach(waitset, reader, reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite reader"); + ret = dds_waitset_attach(waitset, topic, topic); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite topic"); + ret = dds_waitset_attach(waitset, publisher, publisher); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite publisher"); + ret = dds_waitset_attach(waitset, subscriber, subscriber); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to attach prerequisite subscriber"); + +} + +static void +ddsc_waitset_attached_fini(void) +{ + /* Detach all entities to the waitset. */ + dds_waitset_detach(waitset, participant); + dds_waitset_detach(waitset, topic); + dds_waitset_detach(waitset, publisher); + dds_waitset_detach(waitset, subscriber); + dds_waitset_detach(waitset, waitset); + dds_waitset_detach(waitset, writer); + dds_waitset_detach(waitset, reader); + + ddsc_waitset_fini(); +} + +#if 0 +#else +/************************************************************************************************** + * + * These will check the waitset creation in various ways. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_waitset_create, second, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_entity_t ws; + dds_return_t ret; + + /* Basically, ddsc_waitset_basic_init() already tested the creation of a waitset. But + * just see if we can create a second one. */ + ws = dds_create_waitset(participant); + cr_assert_gt(ws, 0, "dds_create_waitset(): returned %d", dds_err_nr(ws)); + + /* Also, we should be able to delete this second one. */ + ret = dds_delete(ws); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_delete(): returned %d", dds_err_nr(ret)); + + /* And, of course, be able to delete the first one (return code isn't checked in the test fixtures). */ + ret = dds_delete(waitset); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_delete(): returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_waitset_create, deleted_participant, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_entity_t ws; + dds_entity_t deleted; + deleted = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + dds_delete(deleted); + ws = dds_create_waitset(deleted); + cr_assert_eq(dds_err_nr(ws), DDS_RETCODE_ALREADY_DELETED, "dds_create_waitset(): returned %d", dds_err_nr(ws)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_create, invalid_params) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t par), ddsc_waitset_create, invalid_params, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_entity_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_entity_t ws; + + ws = dds_create_waitset(par); + cr_assert_eq(dds_err_nr(ws), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ws), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_create, non_participants) = { + DataPoints(dds_entity_t*, &topic, &writer, &reader, &waitset, &publisher, &subscriber, &readcond), +}; +Theory((dds_entity_t *par), ddsc_waitset_create, non_participants, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini) +{ + dds_entity_t ws; + ws = dds_create_waitset(*par); + cr_assert_eq(dds_err_nr(ws), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ws)); +} +/*************************************************************************************************/ + + + +/************************************************************************************************** + * + * These will check the waitset attach in various invalid ways. + * - Valid waitset but try to attach all kinds of invalid entities. + * - Try to attach a valid participant to all kinds of invalid entities. + * - Try attaching valid entities to valid (non-waitset) entities + * - Try attaching a valid entity a second time. + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_attach, invalid_params) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), + DataPoints(dds_attach_t, (dds_attach_t)NULL, (dds_attach_t)&reader, (dds_attach_t)3), +}; +Theory((dds_entity_t e, dds_attach_t a), ddsc_waitset_attach, invalid_params, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_return_t ret; + ret = dds_waitset_attach(waitset, e, a); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d != expected %d", dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_attach, invalid_waitsets) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), + DataPoints(dds_attach_t, (dds_attach_t)NULL, (dds_attach_t)&reader, (dds_attach_t)3), +}; +Theory((dds_entity_t ws, dds_attach_t a), ddsc_waitset_attach, invalid_waitsets, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_return_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_waitset_attach(ws, participant, a); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_attach, non_waitsets) = { + DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &publisher, &subscriber, &readcond), + DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &waitset, &publisher, &subscriber, &readcond), + DataPoints(dds_attach_t, (dds_attach_t)NULL, (dds_attach_t)&reader, (dds_attach_t)3), +}; +Theory((dds_entity_t *ws, dds_entity_t *e, dds_attach_t a), ddsc_waitset_attach, non_waitsets, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini) +{ + dds_return_t ret; + ret = dds_waitset_attach(*ws, *e, a); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_waitset_attach, deleted_waitset, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_return_t ret; + dds_delete(waitset); + ret = dds_waitset_attach(waitset, participant, 0); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + +/************************************************************************************************** + * + * These will check the waitset attach and detach with valid entities (which should always work). + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_attach_detach, valid_entities) = { + DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &waitset, &publisher, &subscriber, &readcond), + DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &waitset, &publisher, &subscriber, &readcond), + DataPoints(dds_attach_t, (dds_attach_t)NULL, (dds_attach_t)&reader, (dds_attach_t)3), +}; +Theory((dds_entity_t *ws, dds_entity_t *e, dds_attach_t a), ddsc_waitset_attach_detach, valid_entities, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini) +{ + dds_return_t exp; + dds_return_t ret; + + if (*ws == waitset) { + /* Attaching to the waitset should work. */ + exp = DDS_RETCODE_OK; + } else { + /* Attaching to every other entity should fail. */ + exp = DDS_RETCODE_ILLEGAL_OPERATION; + } + + /* Try to attach. */ + ret = dds_waitset_attach(*ws, *e, a); + cr_assert_eq(dds_err_nr(ret), exp, "returned %d != expected %d", dds_err_nr(ret), exp); + + /* Detach when needed. */ + if (ret == DDS_RETCODE_OK) { + ret = dds_waitset_detach(*ws, *e); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_detach(): returned %d", dds_err_nr(ret)); + } +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_waitset_attach_detach, second, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_return_t ret; + + ret = dds_waitset_attach(waitset, waitset, 0); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_attach(): returned %d", dds_err_nr(ret)); + + ret = dds_waitset_attach(waitset, waitset, 0); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_PRECONDITION_NOT_MET, "dds_waitset_attach(): returned %d", dds_err_nr(ret)); + + ret = dds_waitset_detach(waitset, waitset); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_detach(): returned %d", dds_err_nr(ret)); + + ret = dds_waitset_detach(waitset, waitset); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_PRECONDITION_NOT_MET, "dds_waitset_detach(): returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + +/************************************************************************************************** + * + * These will check the waitset detach in various ways. + * - Valid waitset but try to detach all kinds of invalid entities. + * - Try to detach a valid participant from all kinds of invalid entities. + * - Try detach valid entities from valid entities + * -- detach to entities that are not waitsets. + * -- detach to a waitset. + * + * Note: everything is expected to fail for varous reasons. The 'happy day' detach is tested on + * the fly in ddsc_waitset_attach_detach::valid_entities + * + *************************************************************************************************/ +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_detach, invalid_params) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t e), ddsc_waitset_detach, invalid_params, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_return_t ret; + ret = dds_waitset_detach(waitset, e); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d != expected %d", dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_detach, invalid_waitsets) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t ws), ddsc_waitset_detach, invalid_waitsets, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_return_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_waitset_detach(ws, participant); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_detach, valid_entities) = { + DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &waitset, &publisher, &subscriber, &readcond), + DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &waitset, &publisher, &subscriber, &readcond), +}; +Theory((dds_entity_t *ws, dds_entity_t *e), ddsc_waitset_detach, valid_entities, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini) +{ + dds_return_t exp; + dds_return_t ret; + + if (*ws == waitset) { + /* Detaching from an empty waitset should yield 'precondition not met'. */ + exp = DDS_RETCODE_PRECONDITION_NOT_MET; + } else { + /* Attaching to every other entity should yield 'illegal operation'. */ + exp = DDS_RETCODE_ILLEGAL_OPERATION; + } + + ret = dds_waitset_detach(*ws, *e); + cr_assert_eq(dds_err_nr(ret), exp, "returned %d != expected %d", dds_err_nr(ret), exp); +} +/*************************************************************************************************/ + + + + +/************************************************************************************************** + * + * These will check the waitset attach/detach in various ways. We will use NULL as attach argument + * because it will be properly used in 'waitset behaviour' tests anyway. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_waitset_attach_detach, itself, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_return_t ret; + + ret = dds_waitset_attach(waitset, waitset, 0); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_attach(): returned %d", dds_err_nr(ret)); + + ret = dds_waitset_detach(waitset, waitset); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_detach(): returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_waitset_attach_detach, participant, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_return_t ret; + + ret = dds_waitset_attach(waitset, participant, 0); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_attach(): returned %d", dds_err_nr(ret)); + + ret = dds_waitset_detach(waitset, participant); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_detach(): returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_waitset_attach_detach, reader, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini) +{ + dds_return_t ret; + + ret = dds_waitset_attach(waitset, reader, 0); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_attach(): returned %d", dds_err_nr(ret)); + + ret = dds_waitset_detach(waitset, reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_detach(): returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_waitset_attach_detach, readcondition, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini) +{ + dds_return_t ret; + + ret = dds_waitset_attach(waitset, readcond, 0); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_attach(): returned %d", dds_err_nr(ret)); + + ret = dds_waitset_detach(waitset, readcond); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_detach(): returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + + +/************************************************************************************************** + * + * These will check entities can be deleted while attached to the waitset. We will use NULL as + * attach argument because it will be properly used in 'waitset behaviour' tests anyway. + * 1) Test if the waitset can be deleted when attached to itself. + * 2) Test if the waitset parent can be deleted when attached. + * 3) Test if an 'ordinary' entity can be deleted when attached. + * 4) Test if an condition can be deleted when attached. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_waitset_delete_attached, self, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_return_t ret; + + ret = dds_waitset_attach(waitset, waitset, 0); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_attach(): returned %d", dds_err_nr(ret)); + + ret = dds_delete(waitset); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_delete(): returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_waitset_delete_attached, participant, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_return_t ret; + + ret = dds_waitset_attach(waitset, participant, 0); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_attach(): returned %d", dds_err_nr(ret)); + + ret = dds_delete(participant); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_delete(): returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_waitset_delete_attached, reader, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini) +{ + dds_return_t ret; + + ret = dds_waitset_attach(waitset, reader, 0); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_attach(): returned %d", dds_err_nr(ret)); + + ret = dds_delete(reader); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_delete(): returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_waitset_delete_attached, readcondition, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini) +{ + dds_return_t ret; + + ret = dds_waitset_attach(waitset, readcond, 0); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_attach(): returned %d", dds_err_nr(ret)); + + ret = dds_delete(readcond); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_delete(): returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + +/************************************************************************************************** + * + * These will check the waitset triggering in various invalid ways. + * The happy days scenario is tested by ddsc_waitset_triggering::on_self. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_waitset_set_trigger, deleted_waitset, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_return_t ret; + dds_delete(waitset); + ret = dds_waitset_set_trigger(waitset, true); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_set_trigger, invalid_params) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t ws), ddsc_waitset_set_trigger, invalid_params, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_return_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_waitset_set_trigger(ws, true); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_set_trigger, non_waitsets) = { + DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &publisher, &subscriber, &readcond), +}; +Theory((dds_entity_t *ws), ddsc_waitset_set_trigger, non_waitsets, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini) +{ + dds_return_t ret; + ret = dds_waitset_set_trigger(*ws, true); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + +/************************************************************************************************** + * + * These will check the waitset wait in various invalid ways. + * The happy days scenario is tested by ddsc_waitset_triggering::*. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_waitset_wait, deleted_waitset, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini) +{ + dds_attach_t triggered; + dds_return_t ret; + dds_delete(waitset); + ret = dds_waitset_wait(waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_wait, invalid_waitsets) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t ws), ddsc_waitset_wait, invalid_waitsets, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_attach_t triggered; + dds_return_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_waitset_wait(ws, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_wait, non_waitsets) = { + DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &publisher, &subscriber, &readcond), +}; +Theory((dds_entity_t *ws), ddsc_waitset_wait, non_waitsets, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini) +{ + dds_attach_t triggered; + dds_return_t ret; + ret = dds_waitset_wait(*ws, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +static dds_attach_t attachment; +TheoryDataPoints(ddsc_waitset_wait, invalid_params) = { + DataPoints(dds_attach_t *, &attachment, NULL), + DataPoints(size_t, 0, 1, 100), + DataPoints(int, -1, 0, 1), +}; +Theory((dds_attach_t *a, size_t size, int msec), ddsc_waitset_wait, invalid_params, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_return_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + /* Only test when the combination of parameters is actually invalid. */ + cr_assume(((a == NULL) && (size != 0)) || ((a != NULL) && (size == 0)) || (size < 0) || (msec < 0)); + + ret = dds_waitset_wait(waitset, a, size, DDS_MSECS(msec)); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d != expected %d", dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_waitset_wait_until, deleted_waitset, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini) +{ + dds_attach_t triggered; + dds_return_t ret; + dds_delete(waitset); + ret = dds_waitset_wait_until(waitset, &triggered, 1, dds_time()); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_wait_until, invalid_waitsets) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t ws), ddsc_waitset_wait_until, invalid_waitsets, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_attach_t triggered; + dds_return_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + ret = dds_waitset_wait_until(ws, &triggered, 1, dds_time()); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_wait_until, non_waitsets) = { + DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &publisher, &subscriber, &readcond), +}; +Theory((dds_entity_t *ws), ddsc_waitset_wait_until, non_waitsets, .init=ddsc_waitset_init, .fini=ddsc_waitset_fini) +{ + dds_attach_t triggered; + dds_return_t ret; + ret = dds_waitset_wait_until(*ws, &triggered, 1, dds_time()); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +static dds_attach_t attachment; +TheoryDataPoints(ddsc_waitset_wait_until, invalid_params) = { + DataPoints(dds_attach_t *, &attachment, NULL), + DataPoints(size_t, 0, 1, 100) +}; +Theory((dds_attach_t *a, size_t size), ddsc_waitset_wait_until, invalid_params, .init=ddsc_waitset_basic_init, .fini=ddsc_waitset_basic_fini) +{ + dds_return_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_return_t ret; + + /* Only test when the combination of parameters is actually invalid. */ + cr_assume(((a == NULL) && (size != 0)) || ((a != NULL) && (size == 0)) || (size < 0)); + + ret = dds_waitset_wait_until(waitset, a, size, dds_time()); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER, "returned %d != expected %d", dds_err_nr(ret), DDS_RETCODE_BAD_PARAMETER); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_waitset_wait_until, past, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini) +{ + dds_attach_t triggered; + dds_return_t ret; + + ret = dds_waitset_wait_until(waitset, &triggered, 1, dds_time() - 100000); + cr_assert_eq(ret, 0, "returned %d != expected 0", ret); +} +/*************************************************************************************************/ + + + +/************************************************************************************************** + * + * These will check the waitset getting the attached entities. + * + *************************************************************************************************/ +#define MAX_ENTITIES_CNT (10) +#define FOUND_PARTICIPANT (0x0001) +#define FOUND_PUBLISHER (0x0002) +#define FOUND_SUBSCRIBER (0x0004) +#define FOUND_WAITSET (0x0008) +#define FOUND_TOPIC (0x0010) +#define FOUND_READER (0x0020) +#define FOUND_WRITER (0x0040) +#define FOUND_ALL (0x007F) + +static uint32_t NumberOfSetBits(uint32_t i) +{ + /* https://stackoverflow.com/questions/109023/how-to-count-the-number-of-set-bits-in-a-32-bit-integer */ + // Java: use >>> instead of >> + // C or C++: use uint32_t + i = i - ((i >> 1) & 0x55555555); + i = (i & 0x33333333) + ((i >> 2) & 0x33333333); + return (((i + (i >> 4)) & 0x0F0F0F0F) * 0x01010101) >> 24; +} + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_get_entities, array_sizes) = { + DataPoints(size_t, 0, 1, 7, MAX_ENTITIES_CNT), +}; +Theory((size_t size), ddsc_waitset_get_entities, array_sizes, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini) +{ + uint32_t found = 0; + dds_return_t i; + dds_return_t ret; + dds_entity_t entities[MAX_ENTITIES_CNT]; + + /* Make sure at least one entity is in the waitsets' internal triggered list. */ + ret = dds_waitset_set_trigger(waitset, true); + cr_assert_eq(ret, DDS_RETCODE_OK, "Failed to prerequisite trigger entity"); + + /* Get the actual attached entities. */ + ret = dds_waitset_get_entities(waitset, entities, size); + + /* ddsc_waitset_attached_init() attached 7 entities. */ + cr_assert_eq(ret, 7, "entities cnt %d (err %d)", ret, dds_err_nr(ret)); + + /* Found entities should be only present once. */ + ret = ((dds_return_t)size < ret) ? (dds_return_t)size : ret; + for (i = 0; i < ret; i++) { + if (entities[i] == participant) { + cr_assert(!(found & FOUND_PARTICIPANT), "Participant found twice"); + found |= FOUND_PARTICIPANT; + } else if (entities[i] == publisher) { + cr_assert(!(found & FOUND_PUBLISHER), "Publisher found twice"); + found |= FOUND_PUBLISHER; + } else if (entities[i] == subscriber) { + cr_assert(!(found & FOUND_SUBSCRIBER), "Subscriber found twice"); + found |= FOUND_SUBSCRIBER; + } else if (entities[i] == waitset) { + cr_assert(!(found & FOUND_WAITSET), "Waitset found twice"); + found |= FOUND_WAITSET; + } else if (entities[i] == topic) { + cr_assert(!(found & FOUND_TOPIC), "Topic found twice"); + found |= FOUND_TOPIC; + } else if (entities[i] == reader) { + cr_assert(!(found & FOUND_READER), "Reader found twice"); + found |= FOUND_READER; + } else if (entities[i] == writer) { + cr_assert(!(found & FOUND_WRITER), "Writer found twice"); + found |= FOUND_WRITER; + } + } + + /* Every found entity should be a known one. */ + cr_assert_eq(NumberOfSetBits(found), ret, "Not all found entities are known"); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_waitset_get_entities, no_array, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini) +{ + dds_return_t ret; + OS_WARNING_MSVC_OFF(6387); /* Disable SAL warning on intentional misuse of the API */ + ret = dds_waitset_get_entities(waitset, NULL, 1); + OS_WARNING_MSVC_ON(6387); + /* ddsc_waitset_attached_init attached 7 entities. */ + cr_assert_eq(ret, 7, "entities cnt %d (err %d)", ret, dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +Test(ddsc_waitset_get_entities, deleted_waitset, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini) +{ + uint32_t found = 0; + dds_return_t ret; + dds_entity_t entities[MAX_ENTITIES_CNT]; + dds_delete(waitset); + ret = dds_waitset_get_entities(waitset, entities, MAX_ENTITIES_CNT); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ALREADY_DELETED, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_get_entities, invalid_params) = { + DataPoints(dds_entity_t, -2, -1, 0, 1, 100, INT_MAX, INT_MIN), +}; +Theory((dds_entity_t ws), ddsc_waitset_get_entities, invalid_params, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini) +{ + dds_return_t exp = DDS_RETCODE_BAD_PARAMETER * -1; + dds_entity_t entities[MAX_ENTITIES_CNT]; + dds_return_t ret; + + ret = dds_waitset_get_entities(ws, entities, MAX_ENTITIES_CNT); + cr_assert_eq(dds_err_nr(ret), dds_err_nr(exp), "returned %d != expected %d", dds_err_nr(ret), dds_err_nr(exp)); +} +/*************************************************************************************************/ + +/*************************************************************************************************/ +TheoryDataPoints(ddsc_waitset_get_entities, non_waitsets) = { + DataPoints(dds_entity_t*, &participant, &topic, &writer, &reader, &publisher, &subscriber, &readcond), +}; +Theory((dds_entity_t *ws), ddsc_waitset_get_entities, non_waitsets, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini) +{ + dds_entity_t entities[MAX_ENTITIES_CNT]; + dds_return_t ret; + ret = dds_waitset_get_entities(*ws, entities, MAX_ENTITIES_CNT); + cr_assert_eq(dds_err_nr(ret), DDS_RETCODE_ILLEGAL_OPERATION, "returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + + + +/************************************************************************************************** + * + * This will check if waitset will wake up when it is attached to itself and triggered. + * + * In short: + * 1) Attach the waitset to itself + * 2) A different thread will call dds_waitset_wait. This should block because the waitset + * hasn't been triggered yet. We also want it to block to know for sure that it'll wake up. + * 3) Trigger the waitset. This should unblock the other thread that was waiting on the waitset. + * 4) A new dds_waitset_wait should return immediately because the trigger value hasn't been + * reset (dds_waitset_set_trigger(waitset, false)) after the waitset woke up. + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_waitset_triggering, on_self, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini) +{ + dds_attach_t triggered; + thread_arg_t arg; + dds_return_t ret; + + /* The waitset should not have been triggered. */ + ret = dds_triggered(waitset); + cr_assert_eq(ret, 0, "dds_triggered(): returned %d", dds_err_nr(ret)); + + /* Start a thread that'll wait because no entity attached to the waitset + * has been triggered (for instance by a status change). */ + waiting_thread_start(&arg, waitset); + + /* Triggering of the waitset should unblock the thread. */ + ret = dds_waitset_set_trigger(waitset, true); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_set_trigger(): returned %d", dds_err_nr(ret)); + ret = waiting_thread_expect_exit(&arg); + cr_assert_eq(ret, DDS_RETCODE_OK, "waiting thread did not unblock"); + + /* Now the trigger state should be true. */ + ret = dds_triggered(waitset); + cr_assert_gt(ret, 0, "dds_triggered(): returned %d", dds_err_nr(ret)); + + /* Waitset shouldn't wait, but immediately return our waitset. */ + ret = dds_waitset_wait(waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "dds_waitset_wait(): returned %d", ret); + cr_assert_eq(waitset, (dds_entity_t)(intptr_t)triggered); + + /* Reset waitset trigger. */ + ret = dds_waitset_set_trigger(waitset, false); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_set_trigger(): returned %d", dds_err_nr(ret)); + ret = dds_triggered(waitset); + cr_assert_eq(ret, 0, "dds_triggered(): returned %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + + +/************************************************************************************************** + * + * This will check if waitset will wake up when data is written related to an attached reader. + * + * In short: + * 1) Attach the reader to the waitset + * 2) A different thread will call dds_waitset_wait. This should block because the reader + * hasn't got data yet. We also want it to block to know for sure that it'll wake up. + * 3) Write data. This should unblock the other thread that was waiting on the waitset. + * 4) A new dds_waitset_wait should return immediately because the status of the reader hasn't + * changed after the first trigger (it didn't read the data). + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_waitset_triggering, on_reader, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini) +{ + RoundTripModule_DataType sample; + dds_attach_t triggered; + thread_arg_t arg; + dds_return_t ret; + + memset(&sample, 0, sizeof(RoundTripModule_DataType)); + + /* Only interested in data_available for this test. */ + ret = dds_set_enabled_status(reader, DDS_DATA_AVAILABLE_STATUS); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_set_enabled_status(): returned %d", dds_err_nr(ret)); + + /* The reader should not have been triggered. */ + ret = dds_triggered(reader); + cr_assert_eq(ret, 0, "dds_triggered(): returned %d", dds_err_nr(ret)); + + /* Start a thread that'll wait because no entity attached to the waitset + * has been triggered (for instance by a status change). */ + waiting_thread_start(&arg, reader); + + /* Writing data should unblock the thread. */ + ret = dds_write(writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_write(): returned %d", dds_err_nr(ret)); + ret = waiting_thread_expect_exit(&arg); + cr_assert_eq(ret, DDS_RETCODE_OK, "waiting thread did not unblock"); + + /* Now the trigger state should be true. */ + ret = dds_triggered(reader); + cr_assert_gt(ret, 0, "dds_triggered: Invalid return code %d", dds_err_nr(ret)); + + /* Waitset shouldn't wait, but immediately return our reader. */ + ret = dds_waitset_wait(waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "dds_waitset_wait ret"); + cr_assert_eq(reader, (dds_entity_t)(intptr_t)triggered, "dds_waitset_wait attachment"); +} +/*************************************************************************************************/ + + + + +/************************************************************************************************** + * + * This will check if waitset will wake up when data is written related to an attached condition. + * + * In short: + * 1) Attach the readcondition to the waitset + * 2) A different thread will call dds_waitset_wait. This should block because the related reader + * hasn't got data yet. We also want it to block to know for sure that it'll wake up. + * 3) Write data. This should unblock the other thread that was waiting on the readcondition. + * 4) A new dds_waitset_wait should return immediately because the status of the related reader + * (and thus the readcondition) hasn't changed after the first trigger (it didn't read the data). + * + *************************************************************************************************/ +/*************************************************************************************************/ +Test(ddsc_waitset_triggering, on_readcondition, .init=ddsc_waitset_attached_init, .fini=ddsc_waitset_attached_fini) +{ + RoundTripModule_DataType sample; + dds_attach_t triggered; + thread_arg_t arg; + dds_return_t ret; + + memset(&sample, 0, sizeof(RoundTripModule_DataType)); + + /* Make sure that we start un-triggered. */ + ret = dds_triggered(readcond); + cr_assert_eq(ret, 0, "dds_triggered: Invalid return code %d", dds_err_nr(ret)); + + /* Attach condition to the waitset. */ + ret = dds_waitset_attach(waitset, readcond, readcond); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_attach(): returned %d", dds_err_nr(ret)); + + /* Start a thread that'll wait because no entity attached to the waitset + * has been triggered (for instance by a status change). */ + waiting_thread_start(&arg, readcond); + + /* Writing data should unblock the thread. */ + ret = dds_write(writer, &sample); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_write(): returned %d", dds_err_nr(ret)); + ret = waiting_thread_expect_exit(&arg); + cr_assert_eq(ret, DDS_RETCODE_OK, "waiting thread did not unblock"); + + /* Now the trigger state should be true. */ + ret = dds_triggered(readcond); + cr_assert_gt(ret, 0, "dds_triggered: Invalid return code %d", dds_err_nr(ret)); + + /* Waitset shouldn't wait, but immediately return our reader. */ + ret = dds_waitset_wait(waitset, &triggered, 1, DDS_SECS(1)); + cr_assert_eq(ret, 1, "dds_waitset_wait ret"); + cr_assert_eq(readcond, (dds_entity_t)(intptr_t)triggered, "dds_waitset_wait attachment"); + + /* Detach condition. */ + ret = dds_waitset_detach(waitset, readcond); + cr_assert_eq(ret, DDS_RETCODE_OK, "dds_waitset_attach: Invalid return code %d", dds_err_nr(ret)); +} +/*************************************************************************************************/ + + + + + +#endif + +/************************************************************************************************** + * + * Convenience support functions. + * + *************************************************************************************************/ + +static uint32_t +waiting_thread(void *a) +{ + thread_arg_t *arg = (thread_arg_t*)a; + dds_attach_t triggered; + dds_return_t ret; + + arg->state = WAITING; + /* This should block until the main test released all claims. */ + ret = dds_waitset_wait(waitset, &triggered, 1, DDS_SECS(1000)); + cr_assert_eq(ret, 1, "dds_waitset_wait returned %d", ret); + cr_assert_eq(arg->expected, (dds_entity_t)(intptr_t)triggered, "dds_waitset_wait attachment"); + arg->state = STOPPED; + + return 0; +} + +static os_result +thread_reached_state(thread_state_t *actual, thread_state_t expected, int32_t msec) +{ + /* Convenience function. */ + bool stopped = false; + os_time msec10 = { 0, 10000000 }; + while ((msec > 0) && (*actual != expected)) { + os_nanoSleep(msec10); + msec -= 10; + } + return (*actual == expected) ? os_resultSuccess : os_resultTimeout; +} + +static void +waiting_thread_start(struct thread_arg_t *arg, dds_entity_t expected) +{ + os_threadId thread_id; + os_threadAttr thread_attr; + os_result osr; + + assert(arg); + + /* Create an other thread that will blocking wait on the waitset. */ + arg->expected = expected; + arg->state = STARTING; + os_threadAttrInit(&thread_attr); + osr = os_threadCreate(&thread_id, "waiting_thread", &thread_attr, waiting_thread, arg); + cr_assert_eq(osr, os_resultSuccess, "os_threadCreate"); + + /* The thread should reach 'waiting' state. */ + osr = thread_reached_state(&(arg->state), WAITING, 1000); + cr_assert_eq(osr, os_resultSuccess, "waiting returned %d", osr); + + /* But thread should block and thus NOT reach 'stopped' state. */ + osr = thread_reached_state(&(arg->state), STOPPED, 100); + cr_assert_eq(osr, os_resultTimeout, "waiting returned %d", osr); + + arg->tid = thread_id; +} + +static dds_return_t +waiting_thread_expect_exit(struct thread_arg_t *arg) +{ + os_result osr; + assert(arg); + osr = thread_reached_state(&(arg->state), STOPPED, 5000); + if (osr == os_resultSuccess) { + os_threadWaitExit(arg->tid, NULL); + return DDS_RETCODE_OK; + } + return DDS_RETCODE_TIMEOUT; +} + diff --git a/src/core/ddsc/tests/write.c b/src/core/ddsc/tests/write.c new file mode 100644 index 0000000..d94f85d --- /dev/null +++ b/src/core/ddsc/tests/write.c @@ -0,0 +1,160 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include "ddsc/dds.h" +#include "RoundTrip.h" +#include "Space.h" +#include "os/os.h" + +/* Tests in this file only concern themselves with very basic api tests of + dds_write and dds_write_ts */ + +static const int payloadSize = 32; +static RoundTripModule_DataType data = { 0 }; + +static dds_entity_t participant = 0; +static dds_entity_t topic = 0; +static dds_entity_t publisher = 0; +static dds_entity_t writer = 0; + +static void +setup(void) +{ + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0); + topic = dds_create_topic(participant, &RoundTripModule_DataType_desc, "RoundTrip", NULL, NULL); + cr_assert_gt(topic, 0); + publisher = dds_create_publisher(participant, NULL, NULL); + cr_assert_gt(publisher, 0); + writer = dds_create_writer(participant, topic, NULL, NULL); + cr_assert_gt(writer, 0); + + memset(&data, 0, sizeof(data)); + data.payload._length = payloadSize; + data.payload._buffer = dds_alloc (payloadSize); + memset(data.payload._buffer, 'a', payloadSize); + data.payload._release = true; + data.payload._maximum = 0; +} + +static void +teardown(void) +{ + RoundTripModule_DataType_free (&data, DDS_FREE_CONTENTS); + memset(&data, 0, sizeof(data)); + + dds_delete(writer); + dds_delete(publisher); + dds_delete(topic); + dds_delete(participant); +} + +Test(ddsc_write, basic, .init = setup, .fini = teardown) +{ + dds_return_t status; + + status = dds_write(writer, &data); + cr_assert_eq(dds_err_nr(status), DDS_RETCODE_OK); +} + +Test(ddsc_write, null_writer, .init = setup, .fini = teardown) +{ + dds_return_t status; + + /* Disable warning related to improper API usage by passing incompatible parameter. */ + OS_WARNING_MSVC_OFF(28020); + status = dds_write(0, &data); + OS_WARNING_MSVC_ON(28020); + cr_assert_eq(dds_err_nr(status), DDS_RETCODE_BAD_PARAMETER); +} + +Test(ddsc_write, bad_writer, .init = setup, .fini = teardown) +{ + dds_return_t status; + + status = dds_write(publisher, &data); + cr_assert_eq(dds_err_nr(status), DDS_RETCODE_ILLEGAL_OPERATION); +} + +Test(ddsc_write, closed_writer, .init = setup, .fini = teardown) +{ + dds_return_t status; + + status = dds_delete(writer); + cr_assert_eq(dds_err_nr(status), DDS_RETCODE_OK); + status = dds_write(writer, &data); + writer = 0; + cr_assert_eq(dds_err_nr(status), DDS_RETCODE_ALREADY_DELETED); +} + +Test(ddsc_write, null_sample, .init = setup, .fini = teardown) +{ + dds_return_t status; + + /* Disable warning related to improper API usage by passing NULL to a non-NULL parameter. */ + OS_WARNING_MSVC_OFF(6387); + status = dds_write(writer, NULL); + OS_WARNING_MSVC_ON(6387); + + cr_assert_eq(dds_err_nr(status), DDS_RETCODE_BAD_PARAMETER); +} + +Test(ddsc_write_ts, basic, .init = setup, .fini = teardown) +{ + dds_return_t status; + + status = dds_write_ts(writer, &data, dds_time()); + cr_assert_eq(dds_err_nr(status), DDS_RETCODE_OK); +} + +Test(ddsc_write_ts, bad_timestamp, .init = setup, .fini = teardown) +{ + dds_return_t status; + + status = dds_write_ts(writer, &data, -1); + cr_assert_eq(dds_err_nr(status), DDS_RETCODE_BAD_PARAMETER); +} + +Test(ddsc_write, simpletypes) +{ + dds_return_t status; + dds_entity_t par, top, wri; + const Space_simpletypes st_data = { + .l = -1, + .ll = -1, + .us = 1, + .ul = 1, + .ull = 1, + .f = 1.0f, + .d = 1.0f, + .c = '1', + .b = true, + .o = 1, + .s = "This string is exactly so long that it would previously trigger CHAM-405. If this string is shortened exactly one character, all is well. Since it is fixed now, there doesn't need to be any further investigation." + }; + + par = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(par, 0); + top = dds_create_topic(par, &Space_simpletypes_desc, "SimpleTypes", NULL, NULL); + cr_assert_gt(top, 0); + wri = dds_create_writer(par, top, NULL, NULL); + cr_assert_gt(wri, 0); + + status = dds_write(wri, &st_data); + cr_assert_eq(dds_err_nr(status), DDS_RETCODE_OK); + + dds_delete(wri); + dds_delete(top); + dds_delete(par); +} diff --git a/src/core/ddsc/tests/writer.c b/src/core/ddsc/tests/writer.c new file mode 100644 index 0000000..068e502 --- /dev/null +++ b/src/core/ddsc/tests/writer.c @@ -0,0 +1,110 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include + +#include "ddsc/dds.h" +#include "RoundTrip.h" +#include "os/os.h" + +static dds_entity_t participant = 0; +static dds_entity_t topic = 0; +static dds_entity_t publisher = 0; +static dds_entity_t writer = 0; + +static void +setup(void) +{ + participant = dds_create_participant(DDS_DOMAIN_DEFAULT, NULL, NULL); + cr_assert_gt(participant, 0); + topic = dds_create_topic(participant, &RoundTripModule_DataType_desc, "RoundTrip", NULL, NULL); + cr_assert_gt(topic, 0); + publisher = dds_create_publisher(participant, NULL, NULL); + cr_assert_gt(publisher, 0); +} + +static void +teardown(void) +{ + dds_delete(writer); + dds_delete(publisher); + dds_delete(topic); + dds_delete(participant); +} + +Test(ddsc_create_writer, basic, .init = setup, .fini = teardown) +{ + dds_return_t result; + + writer = dds_create_writer(participant, topic, NULL, NULL); + cr_assert_gt(writer, 0); + result = dds_delete(writer); + cr_assert_eq(result, DDS_RETCODE_OK); + +} + +Test(ddsc_create_writer, null_parent, .init = setup, .fini = teardown) +{ + OS_WARNING_MSVC_OFF(28020); /* Disable SAL warning on intentional misuse of the API */ + writer = dds_create_writer(0, topic, NULL, NULL); + OS_WARNING_MSVC_ON(28020); + cr_assert_eq(dds_err_nr(writer), DDS_RETCODE_BAD_PARAMETER); +} + +Test(ddsc_create_writer, bad_parent, .init = setup, .fini = teardown) +{ + writer = dds_create_writer(topic, topic, NULL, NULL); + cr_assert_eq(dds_err_nr(writer), DDS_RETCODE_ILLEGAL_OPERATION); +} + +Test(ddsc_create_writer, participant, .init = setup, .fini = teardown) +{ + writer = dds_create_writer(participant, topic, NULL, NULL); + cr_assert_gt(writer, 0); +} + +Test(ddsc_create_writer, publisher, .init = setup, .fini = teardown) +{ + writer = dds_create_writer(publisher, topic, NULL, NULL); + cr_assert_gt(writer, 0); +} + +Test(ddsc_create_writer, deleted_publisher, .init = setup, .fini = teardown) +{ + dds_delete(publisher); + + writer = dds_create_writer(publisher, topic, NULL, NULL); + cr_assert_eq(dds_err_nr(writer), DDS_RETCODE_ALREADY_DELETED); +} + +Test(ddsc_create_writer, null_topic, .init = setup, .fini = teardown) +{ + OS_WARNING_MSVC_OFF(28020); /* Disable SAL warning on intentional misuse of the API */ + writer = dds_create_writer(publisher, 0, NULL, NULL); + OS_WARNING_MSVC_ON(28020); + cr_assert_eq(dds_err_nr(writer), DDS_RETCODE_BAD_PARAMETER); +} + +Test(ddsc_create_writer, bad_topic, .init = setup, .fini = teardown) +{ + writer = dds_create_writer(publisher, publisher, NULL, NULL); + cr_assert_eq(dds_err_nr(writer), DDS_RETCODE_ILLEGAL_OPERATION); +} + +Test(ddsc_create_writer, deleted_topic, .init = setup, .fini = teardown) +{ + dds_delete(topic); + + writer = dds_create_writer(publisher, topic, NULL, NULL); + cr_assert_eq(dds_err_nr(writer), DDS_RETCODE_ALREADY_DELETED); +} diff --git a/src/core/ddsi/.fileids b/src/core/ddsi/.fileids new file mode 100644 index 0000000..a765e76 --- /dev/null +++ b/src/core/ddsi/.fileids @@ -0,0 +1,42 @@ +# ddsi sources +1 src/ddsi_ser.c +2 src/ddsi_ssl.c +3 src/ddsi_tcp.c +4 src/ddsi_tran.c +5 src/ddsi_udp.c +6 src/q_addrset.c +7 src/q_bitset_inlines.c +8 src/q_bswap.c +9 src/q_bswap_inlines.c +10 src/q_config.c +11 src/q_ddsi_discovery.c +12 src/q_debmon.c +13 src/q_entity.c +14 src/q_ephash.c +15 src/q_gc.c +16 src/q_init.c +17 src/q_lat_estim.c +18 src/q_lease.c +19 src/q_log.c +20 src/q_md5.c +21 src/q_misc.c +22 src/q_nwif.c +23 src/q_pcap.c +24 src/q_plist.c +25 src/q_qosmatch.c +26 src/q_radmin.c +27 src/q_receive.c +28 src/q_security.c +29 src/q_servicelease.c +30 src/q_sockwaitset.c +31 src/q_thread.c +32 src/q_thread_inlines.c +33 src/q_time.c +34 src/q_transmit.c +35 src/q_whc.c +36 src/q_xevent.c +37 src/q_xmsg.c +38 src/q_freelist.c +39 src/sysdeps.c +71 src/q_builtin_topic.c + diff --git a/src/core/ddsi/CMakeLists.txt b/src/core/ddsi/CMakeLists.txt new file mode 100644 index 0000000..d834ee8 --- /dev/null +++ b/src/core/ddsi/CMakeLists.txt @@ -0,0 +1,120 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +PREPEND(srcs_ddsi "${CMAKE_CURRENT_LIST_DIR}/src" + ddsi_ser.c + ddsi_ssl.c + ddsi_tcp.c + ddsi_tran.c + ddsi_udp.c + q_addrset.c + q_bitset_inlines.c + q_bswap.c + q_bswap_inlines.c + q_builtin_topic.c + q_config.c + q_ddsi_discovery.c + q_debmon.c + q_entity.c + q_ephash.c + q_gc.c + q_init.c + q_lat_estim.c + q_lease.c + q_log.c + q_md5.c + q_misc.c + q_nwif.c + q_pcap.c + q_plist.c + q_qosmatch.c + q_radmin.c + q_receive.c + q_security.c + q_servicelease.c + q_sockwaitset.c + q_thread.c + q_thread_inlines.c + q_time.c + q_transmit.c + q_whc.c + q_xevent.c + q_xmsg.c + q_freelist.c + sysdeps.c +) + +# The includes should reside close to the code. As long as that's not the case, +# pull them in from this CMakeLists.txt. +PREPEND(hdrs_private_ddsi "${CMAKE_CURRENT_LIST_DIR}/include/ddsi" + ddsi_ser.h + ddsi_ssl.h + ddsi_tcp.h + ddsi_tran.h + ddsi_udp.h + probes-constants.h + q_addrset.h + q_align.h + q_bitset.h + q_bitset_template.h + q_bswap.h + q_bswap_template.h + q_builtin_topic.h + q_config.h + q_ddsi_discovery.h + q_debmon.h + q_entity.h + q_ephash.h + q_error.h + q_feature_check.h + q_freelist.h + q_gc.h + q_globals.h + q_hbcontrol.h + q_inline.h + q_lat_estim.h + q_lease.h + q_log.h + q_md5.h + q_misc.h + q_nwif.h + q_pcap.h + q_plist.h + q_protocol.h + q_qosmatch.h + q_radmin.h + q_receive.h + q_rtps.h + q_security.h + q_servicelease.h + q_sockwaitset.h + q_static_assert.h + q_thread.h + q_thread_template.h + q_time.h + q_transmit.h + q_unused.h + q_whc.h + q_xevent.h + q_xmsg.h + q_xqos.h + sysdeps.h +) + +target_sources(ddsc + PRIVATE + ${srcs_ddsi} + ${hdrs_private_ddsi} +) + +target_include_directories(ddsc + PRIVATE + "${CMAKE_CURRENT_LIST_DIR}/include") diff --git a/src/core/ddsi/include/ddsi/ddsi_ser.h b/src/core/ddsi/include/ddsi/ddsi_ser.h new file mode 100644 index 0000000..170b39e --- /dev/null +++ b/src/core/ddsi/include/ddsi/ddsi_ser.h @@ -0,0 +1,192 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef DDSI_SER_H +#define DDSI_SER_H + +#include "os/os.h" +#include "ddsi/q_plist.h" /* for nn_prismtech_writer_info */ +#include "ddsi/q_freelist.h" +#include "util/ut_avl.h" +#include "sysdeps.h" + +#include "ddsc/dds.h" +#include "dds__topic.h" + +#ifndef PLATFORM_IS_LITTLE_ENDIAN +# if OS_ENDIANNESS == OS_BIG_ENDIAN +# define PLATFORM_IS_LITTLE_ENDIAN 0 +# elif OS_ENDIANNESS == OS_LITTLE_ENDIAN +# define PLATFORM_IS_LITTLE_ENDIAN 1 +# else +# error "invalid endianness setting" +# endif +#endif /* PLATFORM_IS_LITTLE_ENDIAN */ + +#if PLATFORM_IS_LITTLE_ENDIAN +#define CDR_BE 0x0000 +#define CDR_LE 0x0100 +#else +#define CDR_BE 0x0000 +#define CDR_LE 0x0001 +#endif + +typedef struct serstatepool * serstatepool_t; +typedef struct serstate * serstate_t; +typedef struct serdata * serdata_t; +typedef struct sertopic * sertopic_t; + +struct CDRHeader +{ + unsigned short identifier; + unsigned short options; +}; + +struct serdata_msginfo +{ + unsigned statusinfo; + nn_wctime_t timestamp; +}; + +enum serstate_kind { + STK_EMPTY, + STK_KEY, + STK_DATA +}; + +struct serstate +{ + serdata_t data; + nn_mtime_t twrite; /* write time, not source timestamp, set post-throttling */ + os_atomic_uint32_t refcount; + size_t pos; + size_t size; + const struct sertopic * topic; + enum serstate_kind kind; + serstatepool_t pool; + struct serstate *next; /* in pool->freelist */ +}; + +struct serstatepool +{ + struct nn_freelist freelist; +}; + + +#define DDS_KEY_SET 0x0001 +#define DDS_KEY_HASH_SET 0x0002 +#define DDS_KEY_IS_HASH 0x0004 + +typedef struct dds_key_hash +{ + char m_hash [16]; /* Key hash value. Also possibly key. */ + uint32_t m_key_len; /* Length of key (may be in m_hash or m_key_buff) */ + uint32_t m_key_buff_size; /* Size of allocated key buffer (m_key_buff) */ + char * m_key_buff; /* Key buffer */ + uint32_t m_flags; /* State of key/hash (see DDS_KEY_XXX) */ +} +dds_key_hash_t; + +struct serdata_base +{ + serstate_t st; /* back pointer to (opaque) serstate so RTPS impl only needs serdata */ + struct serdata_msginfo msginfo; + int hash_valid; /* whether hash is valid or must be computed from key/data */ + uint32_t hash; /* cached serdata hash, valid only if hash_valid != 0 */ + dds_key_hash_t keyhash; + bool bswap; /* Whether state is native endian or requires swapping */ +}; + +struct serdata +{ + struct serdata_base v; + /* padding to ensure CDRHeader is at an offset 4 mod 8 from the + start of the memory, so that data is 8-byte aligned provided + serdata is 8-byte aligned */ + char pad[8 - ((sizeof (struct serdata_base) + 4) % 8)]; + struct CDRHeader hdr; + char data[1]; +}; + + +struct dds_key_descriptor; + + +struct dds_topic; +typedef void (*topic_cb_t) (struct dds_topic * topic); +#ifndef DDS_TOPIC_INTERN_FILTER_FN_DEFINED +#define DDS_TOPIC_INTERN_FILTER_FN_DEFINED +typedef bool (*dds_topic_intern_filter_fn) (const void * sample, void *ctx); +#endif + +struct sertopic +{ + ut_avlNode_t avlnode; + char * name_typename; + char * name; + char * typename; + void * type; + unsigned nkeys; + + uint32_t id; + uint32_t hash; + uint32_t flags; + size_t opt_size; + os_atomic_uint32_t refcount; + topic_cb_t status_cb; + dds_topic_intern_filter_fn filter_fn; + void * filter_sample; + void * filter_ctx; + struct dds_topic * status_cb_entity; + const struct dds_key_descriptor * keys; + + /* + Array of keys, represented as offset in the OpenSplice internal + format data blob. Keys must be stored in the order visited by + serializer (so that the serializer can simply compare the current + offset with the next key offset). Also: keys[nkeys].off =def= + ~0u, which won't equal any real offset so that there is no need + to test for the end of the array. + + Offsets work 'cos only primitive types, enums and strings are + accepted as keys. So there is no ambiguity if a key happens to + be inside a nested struct. + */ +}; + +serstatepool_t ddsi_serstatepool_new (void); +void ddsi_serstatepool_free (serstatepool_t pool); + +serdata_t ddsi_serdata_ref (serdata_t serdata); +OSAPI_EXPORT void ddsi_serdata_unref (serdata_t serdata); +int ddsi_serdata_refcount_is_1 (serdata_t serdata); +nn_mtime_t ddsi_serdata_twrite (const struct serdata * serdata); +void ddsi_serdata_set_twrite (struct serdata * serdata, nn_mtime_t twrite); +uint32_t ddsi_serdata_size (const struct serdata * serdata); +int ddsi_serdata_is_key (const struct serdata * serdata); +int ddsi_serdata_is_empty (const struct serdata * serdata); + +OSAPI_EXPORT void ddsi_serstate_append_blob (serstate_t st, size_t align, size_t sz, const void *data); +OSAPI_EXPORT void ddsi_serstate_set_msginfo +( + serstate_t st, unsigned statusinfo, nn_wctime_t timestamp, + void * dummy +); +OSAPI_EXPORT serstate_t ddsi_serstate_new (serstatepool_t pool, const struct sertopic * topic); +OSAPI_EXPORT serdata_t ddsi_serstate_fix (serstate_t st); +nn_mtime_t ddsi_serstate_twrite (const struct serstate *serstate); +void ddsi_serstate_set_twrite (struct serstate *serstate, nn_mtime_t twrite); +void ddsi_serstate_release (serstate_t st); +void * ddsi_serstate_append (serstate_t st, size_t n); +void * ddsi_serstate_append_align (serstate_t st, size_t a); +void * ddsi_serstate_append_aligned (serstate_t st, size_t n, size_t a); + +#endif diff --git a/src/core/ddsi/include/ddsi/ddsi_ssl.h b/src/core/ddsi/include/ddsi/ddsi_ssl.h new file mode 100644 index 0000000..c6c102a --- /dev/null +++ b/src/core/ddsi/include/ddsi/ddsi_ssl.h @@ -0,0 +1,28 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDSI_SSL_H_ +#define _DDSI_SSL_H_ + +#ifdef DDSI_INCLUDE_SSL + +#ifdef _WIN32 +/* WinSock2 must be included before openssl headers + otherwise winsock will be used */ +#include +#endif + +#include + +void ddsi_ssl_plugin (void); + +#endif +#endif diff --git a/src/core/ddsi/include/ddsi/ddsi_tcp.h b/src/core/ddsi/include/ddsi/ddsi_tcp.h new file mode 100644 index 0000000..feda2aa --- /dev/null +++ b/src/core/ddsi/include/ddsi/ddsi_tcp.h @@ -0,0 +1,41 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDSI_TCP_H_ +#define _DDSI_TCP_H_ + +#include "ddsi/ddsi_tran.h" + +#ifdef DDSI_INCLUDE_SSL + +#include "ddsi/ddsi_ssl.h" + +struct ddsi_ssl_plugins +{ + void (*config) (void); + c_bool (*init) (void); + void (*fini) (void); + void (*ssl_free) (SSL * ssl); + void (*bio_vfree) (BIO * bio); + os_ssize_t (*read) (SSL * ssl, void * buf, os_size_t len, int * err); + os_ssize_t (*write) (SSL * ssl, const void * msg, os_size_t len, int * err); + SSL * (*connect) (os_socket sock); + BIO * (*listen) (os_socket sock); + SSL * (*accept) (BIO * bio, os_socket * sock); +}; + +struct ddsi_ssl_plugins ddsi_tcp_ssl_plugin; + +#endif + +int ddsi_tcp_init (void); + +#endif diff --git a/src/core/ddsi/include/ddsi/ddsi_tran.h b/src/core/ddsi/include/ddsi/ddsi_tran.h new file mode 100644 index 0000000..248e824 --- /dev/null +++ b/src/core/ddsi/include/ddsi/ddsi_tran.h @@ -0,0 +1,191 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDSI_TRAN_H_ +#define _DDSI_TRAN_H_ + +/* DDSI Transport module */ + +#include "ddsi/q_globals.h" +#include "ddsi/q_protocol.h" + +/* Types supporting handles */ + +#define DDSI_TRAN_CONN 1 +#define DDSI_TRAN_LISTENER 2 + +/* Flags */ + +#define DDSI_TRAN_ON_CONNECT 0x0001 + +/* Core types */ + +typedef struct ddsi_tran_base * ddsi_tran_base_t; +typedef struct ddsi_tran_conn * ddsi_tran_conn_t; +typedef struct ddsi_tran_listener * ddsi_tran_listener_t; +typedef struct ddsi_tran_factory * ddsi_tran_factory_t; +typedef struct ddsi_tran_qos * ddsi_tran_qos_t; + +/* Function pointer types */ + +typedef ssize_t (*ddsi_tran_read_fn_t) (ddsi_tran_conn_t , unsigned char *, size_t); +typedef ssize_t (*ddsi_tran_write_fn_t) (ddsi_tran_conn_t, const struct msghdr *, size_t, uint32_t); +typedef int (*ddsi_tran_locator_fn_t) (ddsi_tran_base_t, nn_locator_t *); +typedef bool (*ddsi_tran_supports_fn_t) (int32_t); +typedef os_handle (*ddsi_tran_handle_fn_t) (ddsi_tran_base_t); +typedef int (*ddsi_tran_listen_fn_t) (ddsi_tran_listener_t); +typedef void (*ddsi_tran_free_fn_t) (void); +typedef void (*ddsi_tran_peer_locator_fn_t) (ddsi_tran_conn_t, nn_locator_t *); +typedef ddsi_tran_conn_t (*ddsi_tran_accept_fn_t) (ddsi_tran_listener_t); +typedef ddsi_tran_conn_t (*ddsi_tran_create_conn_fn_t) (uint32_t, ddsi_tran_qos_t); +typedef ddsi_tran_listener_t (*ddsi_tran_create_listener_fn_t) (int port, ddsi_tran_qos_t); +typedef void (*ddsi_tran_release_conn_fn_t) (ddsi_tran_conn_t); +typedef void (*ddsi_tran_close_conn_fn_t) (ddsi_tran_conn_t); +typedef void (*ddsi_tran_unblock_listener_fn_t) (ddsi_tran_listener_t); +typedef void (*ddsi_tran_release_listener_fn_t) (ddsi_tran_listener_t); +typedef int (*ddsi_tran_join_mc_fn_t) (ddsi_tran_conn_t, const nn_locator_t *srcip, const nn_locator_t *mcip); +typedef int (*ddsi_tran_leave_mc_fn_t) (ddsi_tran_conn_t, const nn_locator_t *srcip, const nn_locator_t *mcip); + +/* Data types */ + +struct ddsi_tran_base +{ + /* Data */ + + uint32_t m_port; + uint32_t m_trantype; + bool m_multicast; + + /* Functions */ + + ddsi_tran_locator_fn_t m_locator_fn; + ddsi_tran_handle_fn_t m_handle_fn; +}; + +struct ddsi_tran_conn +{ + struct ddsi_tran_base m_base; + + /* Functions */ + + ddsi_tran_read_fn_t m_read_fn; + ddsi_tran_write_fn_t m_write_fn; + ddsi_tran_peer_locator_fn_t m_peer_locator_fn; + + /* Data */ + + bool m_server; + bool m_connless; + bool m_stream; + bool m_closed; + os_atomic_uint32_t m_count; + + /* Relationships */ + + ddsi_tran_factory_t m_factory; + ddsi_tran_listener_t m_listener; + ddsi_tran_conn_t m_conn; +}; + +struct ddsi_tran_listener +{ + struct ddsi_tran_base m_base; + + /* Functions */ + + ddsi_tran_listen_fn_t m_listen_fn; + ddsi_tran_accept_fn_t m_accept_fn; + + /* Relationships */ + + ddsi_tran_conn_t m_connections; + ddsi_tran_factory_t m_factory; + ddsi_tran_listener_t m_listener; +}; + +struct ddsi_tran_factory +{ + /* Functions */ + + ddsi_tran_create_conn_fn_t m_create_conn_fn; + ddsi_tran_create_listener_fn_t m_create_listener_fn; + ddsi_tran_release_conn_fn_t m_release_conn_fn; + ddsi_tran_close_conn_fn_t m_close_conn_fn; + ddsi_tran_unblock_listener_fn_t m_unblock_listener_fn; + ddsi_tran_release_listener_fn_t m_release_listener_fn; + ddsi_tran_supports_fn_t m_supports_fn; + ddsi_tran_free_fn_t m_free_fn; + ddsi_tran_join_mc_fn_t m_join_mc_fn; + ddsi_tran_leave_mc_fn_t m_leave_mc_fn; + + /* Data */ + + int32_t m_kind; + const char * m_typename; + bool m_connless; + bool m_stream; + + /* Relationships */ + + ddsi_tran_factory_t m_factory; +}; + +struct ddsi_tran_qos +{ + /* QoS Data */ + + bool m_multicast; + int m_diffserv; +}; + +/* Functions and pseudo functions (macro wrappers) */ + +#define ddsi_tran_type(b) (((ddsi_tran_base_t) (b))->m_trantype) +#define ddsi_tran_port(b) (((ddsi_tran_base_t) (b))->m_port) +int ddsi_tran_locator (ddsi_tran_base_t base, nn_locator_t * loc); +void ddsi_tran_free (ddsi_tran_base_t base); +void ddsi_tran_free_qos (ddsi_tran_qos_t qos); +ddsi_tran_qos_t ddsi_tran_create_qos (void); +os_handle ddsi_tran_handle (ddsi_tran_base_t base); + +#define ddsi_factory_create_listener(f,p,q) (((f)->m_create_listener_fn) ((p), (q))) +#define ddsi_factory_supports(f,k) (((f)->m_supports_fn) (k)) + +ddsi_tran_conn_t ddsi_factory_create_conn +( + ddsi_tran_factory_t factory, + uint32_t port, + ddsi_tran_qos_t qos +); +void ddsi_tran_factories_fini (void); +void ddsi_factory_add (ddsi_tran_factory_t factory); +void ddsi_factory_free (ddsi_tran_factory_t factory); +ddsi_tran_factory_t ddsi_factory_find (const char * type); +void ddsi_factory_conn_init (ddsi_tran_factory_t factory, ddsi_tran_conn_t conn); + +#define ddsi_conn_handle(c) (ddsi_tran_handle (&(c)->m_base)) +#define ddsi_conn_locator(c,l) (ddsi_tran_locator (&(c)->m_base,(l))) +OSAPI_EXPORT ssize_t ddsi_conn_write (ddsi_tran_conn_t conn, const struct msghdr * msg, size_t len, uint32_t flags); +ssize_t ddsi_conn_read (ddsi_tran_conn_t conn, unsigned char * buf, size_t len); +bool ddsi_conn_peer_locator (ddsi_tran_conn_t conn, nn_locator_t * loc); +void ddsi_conn_add_ref (ddsi_tran_conn_t conn); +void ddsi_conn_free (ddsi_tran_conn_t conn); + +int ddsi_conn_join_mc (ddsi_tran_conn_t conn, const nn_locator_t *srcip, const nn_locator_t *mcip); +int ddsi_conn_leave_mc (ddsi_tran_conn_t conn, const nn_locator_t *srcip, const nn_locator_t *mcip); + +#define ddsi_listener_locator(s,l) (ddsi_tran_locator (&(s)->m_base,(l))) +ddsi_tran_conn_t ddsi_listener_accept (ddsi_tran_listener_t listener); +int ddsi_listener_listen (ddsi_tran_listener_t listener); +void ddsi_listener_unblock (ddsi_tran_listener_t listener); +void ddsi_listener_free (ddsi_tran_listener_t listener); + +#endif diff --git a/src/core/ddsi/include/ddsi/ddsi_udp.h b/src/core/ddsi/include/ddsi/ddsi_udp.h new file mode 100644 index 0000000..4dece96 --- /dev/null +++ b/src/core/ddsi/include/ddsi/ddsi_udp.h @@ -0,0 +1,17 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef _DDSI_UDP_H_ +#define _DDSI_UDP_H_ + +int ddsi_udp_init (void); + +#endif diff --git a/src/core/ddsi/include/ddsi/probes-constants.h b/src/core/ddsi/include/ddsi/probes-constants.h new file mode 100644 index 0000000..83b15ab --- /dev/null +++ b/src/core/ddsi/include/ddsi/probes-constants.h @@ -0,0 +1,18 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef PROBES_CONSTANTS_H +#define PROBES_CONSTANTS_H +#define DROP_QUEUE_FULL 1 +#define DROP_TOO_OLD 2 +#define DROP_REORDER_FULL 3 +#define DROP_DUPLICATE 4 +#endif diff --git a/src/core/ddsi/include/ddsi/q_addrset.h b/src/core/ddsi/include/ddsi/q_addrset.h new file mode 100644 index 0000000..e709044 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_addrset.h @@ -0,0 +1,92 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_ADDRSET_H +#define NN_ADDRSET_H + +#include "os/os.h" + +#include "util/ut_avl.h" +#include "ddsi/q_log.h" +#include "ddsi/q_protocol.h" +#include "ddsi/q_feature_check.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +typedef struct addrset_node { + ut_avlNode_t avlnode; + nn_locator_t loc; +} * addrset_node_t; + +struct addrset { + os_mutex lock; + os_atomic_uint32_t refc; + ut_avlCTree_t ucaddrs, mcaddrs; +}; + +typedef void (*addrset_forall_fun_t) (const nn_locator_t *loc, void *arg); +typedef ssize_t (*addrset_forone_fun_t) (const nn_locator_t *loc, void *arg); + +struct addrset *new_addrset (void); +struct addrset *ref_addrset (struct addrset *as); +void unref_addrset (struct addrset *as); +void add_to_addrset (struct addrset *as, const nn_locator_t *loc); +void remove_from_addrset (struct addrset *as, const nn_locator_t *loc); +int addrset_purge (struct addrset *as); +int compare_locators (const nn_locator_t *a, const nn_locator_t *b); + +/* These lock ASADD, then lock/unlock AS any number of times, then + unlock ASADD */ +void copy_addrset_into_addrset_uc (struct addrset *as, const struct addrset *asadd); +void copy_addrset_into_addrset_mc (struct addrset *as, const struct addrset *asadd); +void copy_addrset_into_addrset (struct addrset *as, const struct addrset *asadd); + +size_t addrset_count (const struct addrset *as); +size_t addrset_count_uc (const struct addrset *as); +size_t addrset_count_mc (const struct addrset *as); +int addrset_empty_uc (const struct addrset *as); +int addrset_empty_mc (const struct addrset *as); +int addrset_empty (const struct addrset *as); +int addrset_any_uc (const struct addrset *as, nn_locator_t *dst); +int addrset_any_mc (const struct addrset *as, nn_locator_t *dst); + +/* Keeps AS locked */ +int addrset_forone (struct addrset *as, addrset_forone_fun_t f, void *arg); +void addrset_forall (struct addrset *as, addrset_forall_fun_t f, void *arg); +size_t addrset_forall_count (struct addrset *as, addrset_forall_fun_t f, void *arg); +void nn_log_addrset (logcat_t tf, const char *prefix, const struct addrset *as); + +/* Tries to lock A then B for a decent check, returning false if + trylock B fails */ +int addrset_eq_onesidederr (const struct addrset *a, const struct addrset *b); + +int is_mcaddr (const nn_locator_t *loc); +int is_unspec_locator (const nn_locator_t *loc); +void set_unspec_locator (nn_locator_t *loc); + +int add_addresses_to_addrset (struct addrset *as, const char *addrs, int port_mode, const char *msgtag, int req_mc); + +#ifdef DDSI_INCLUDE_SSM +int is_ssm_mcaddr (const nn_locator_t *loc); +int addrset_contains_ssm (const struct addrset *as); +int addrset_any_ssm (const struct addrset *as, nn_locator_t *dst); +int addrset_any_non_ssm_mc (const struct addrset *as, nn_locator_t *dst); +void copy_addrset_into_addrset_no_ssm_mc (struct addrset *as, const struct addrset *asadd); +void copy_addrset_into_addrset_no_ssm (struct addrset *as, const struct addrset *asadd); +void addrset_pruge_ssm (struct addrset *as); +#endif + +#if defined (__cplusplus) +} +#endif +#endif /* NN_ADDRSET_H */ diff --git a/src/core/ddsi/include/ddsi/q_align.h b/src/core/ddsi/include/ddsi/q_align.h new file mode 100644 index 0000000..170936d --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_align.h @@ -0,0 +1,18 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_ALIGN_H +#define NN_ALIGN_H + +#define ALIGN4(x) (((x) + 3) & -4u) +#define ALIGN8(x) (((x) + 7) & -8u) + +#endif /* NN_ALIGN_H */ diff --git a/src/core/ddsi/include/ddsi/q_bitset.h b/src/core/ddsi/include/ddsi/q_bitset.h new file mode 100644 index 0000000..8e5ec1a --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_bitset.h @@ -0,0 +1,36 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_BITSET_H +#define NN_BITSET_H + +#include +#include + +#include "ddsi/q_inline.h" + +#if NN_HAVE_C99_INLINE && !defined SUPPRESS_BITSET_INLINES +#include "q_bitset_template.h" +#else +#if defined (__cplusplus) +extern "C" { +#endif +int nn_bitset_isset (unsigned numbits, const unsigned *bits, unsigned idx); +void nn_bitset_set (unsigned numbits, unsigned *bits, unsigned idx); +void nn_bitset_clear (unsigned numbits, unsigned *bits, unsigned idx); +void nn_bitset_zero (unsigned numbits, unsigned *bits); +void nn_bitset_one (unsigned numbits, unsigned *bits); +#if defined (__cplusplus) +} +#endif +#endif + +#endif /* NN_BITSET_H */ diff --git a/src/core/ddsi/include/ddsi/q_bitset_template.h b/src/core/ddsi/include/ddsi/q_bitset_template.h new file mode 100644 index 0000000..13eb488 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_bitset_template.h @@ -0,0 +1,53 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +/* -*- c -*- */ + +#include "ddsi/q_unused.h" + +#if defined SUPPRESS_BITSET_INLINES && defined NN_C99_INLINE +#undef NN_C99_INLINE +#define NN_C99_INLINE +#endif + +NN_C99_INLINE int nn_bitset_isset (unsigned numbits, const unsigned *bits, unsigned idx) +{ + return idx < numbits && (bits[idx/32] & (1u << (31 - (idx%32)))); +} + +NN_C99_INLINE void nn_bitset_set (UNUSED_ARG_NDEBUG (unsigned numbits), unsigned *bits, unsigned idx) +{ + assert (idx < numbits); + bits[idx/32] |= 1u << (31 - (idx%32)); +} + +NN_C99_INLINE void nn_bitset_clear (UNUSED_ARG_NDEBUG (unsigned numbits), unsigned *bits, unsigned idx) +{ + assert (idx < numbits); + bits[idx/32] &= ~(1u << (31 - (idx%32))); +} + +NN_C99_INLINE void nn_bitset_zero (unsigned numbits, unsigned *bits) +{ + memset (bits, 0, 4 * ((numbits + 31) / 32)); +} + +NN_C99_INLINE void nn_bitset_one (unsigned numbits, unsigned *bits) +{ + memset (bits, 0xff, 4 * ((numbits + 31) / 32)); + + /* clear bits "accidentally" set */ + { + const unsigned k = numbits / 32; + const unsigned n = numbits % 32; + bits[k] &= ~(~0u >> n); + } +} diff --git a/src/core/ddsi/include/ddsi/q_bswap.h b/src/core/ddsi/include/ddsi/q_bswap.h new file mode 100644 index 0000000..f09850a --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_bswap.h @@ -0,0 +1,87 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_BSWAP_H +#define NN_BSWAP_H + +#include "os/os.h" + +#include "ddsi/q_inline.h" +#include "ddsi/q_rtps.h" /* for nn_guid_t, nn_guid_prefix_t */ +#include "ddsi/q_protocol.h" /* for nn_sequence_number_t */ + +#define bswap2(x) ((short) bswap2u ((unsigned short) (x))) +#define bswap4(x) ((int) bswap4u ((unsigned) (x))) +#define bswap8(x) ((long long) bswap8u ((unsigned long long) (x))) + +#if NN_HAVE_C99_INLINE && !defined SUPPRESS_BSWAP_INLINES +#include "q_bswap_template.h" +#else +#if defined (__cplusplus) +extern "C" { +#endif +unsigned short bswap2u (unsigned short x); +unsigned bswap4u (unsigned x); +unsigned long long bswap8u (unsigned long long x); +void bswapSN (nn_sequence_number_t *sn); +#if defined (__cplusplus) +} +#endif +#endif + +#if OS_ENDIANNESS == OS_LITTLE_ENDIAN +#define toBE2(x) bswap2 (x) +#define toBE2u(x) bswap2u (x) +#define toBE4(x) bswap4 (x) +#define toBE4u(x) bswap4u (x) +#define toBE8(x) bswap8 (x) +#define toBE8u(x) bswap8u (x) +#define fromBE2(x) bswap2 (x) +#define fromBE2u(x) bswap2u (x) +#define fromBE4(x) bswap4 (x) +#define fromBE4u(x) bswap4u (x) +#define fromBE8(x) bswap8 (x) +#define fromBE8u(x) bswap8u (x) +#else +#define toBE2u(x) (x) +#define toBE4(x) (x) +#define toBE4u(x) (x) +#define toBE8(x) (x) +#define toBE8u(x) (x) +#define fromBE2(x) (x) +#define fromBE2u(x) (x) +#define fromBE4(x) (x) +#define fromBE4u(x) (x) +#define fromBE8(x) (x) +#define fromBE8u(x) (x) +#endif + +#if defined (__cplusplus) +extern "C" { +#endif + +nn_guid_prefix_t nn_hton_guid_prefix (nn_guid_prefix_t p); +nn_guid_prefix_t nn_ntoh_guid_prefix (nn_guid_prefix_t p); +nn_entityid_t nn_hton_entityid (nn_entityid_t e); +nn_entityid_t nn_ntoh_entityid (nn_entityid_t e); +nn_guid_t nn_hton_guid (nn_guid_t g); +nn_guid_t nn_ntoh_guid (nn_guid_t g); + +void bswap_sequence_number_set_hdr (nn_sequence_number_set_t *snset); +void bswap_sequence_number_set_bitmap (nn_sequence_number_set_t *snset); +void bswap_fragment_number_set_hdr (nn_fragment_number_set_t *fnset); +void bswap_fragment_number_set_bitmap (nn_fragment_number_set_t *fnset); + +#if defined (__cplusplus) +} +#endif + +#endif /* NN_BSWAP_H */ diff --git a/src/core/ddsi/include/ddsi/q_bswap_template.h b/src/core/ddsi/include/ddsi/q_bswap_template.h new file mode 100644 index 0000000..5514bfe --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_bswap_template.h @@ -0,0 +1,41 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +/* -*- c -*- */ + +#if defined SUPPRESS_BSWAP_INLINES && defined VDDS_INLINE +#undef VDDS_INLINE +#define VDDS_INLINE +#endif + +VDDS_INLINE unsigned short bswap2u (unsigned short x) +{ + return (unsigned short) ((x >> 8) | (x << 8)); +} + +VDDS_INLINE unsigned bswap4u (unsigned x) +{ + return (x >> 24) | ((x >> 8) & 0xff00) | ((x << 8) & 0xff0000) | (x << 24); +} + +VDDS_INLINE unsigned long long bswap8u (unsigned long long x) +{ + const unsigned newhi = bswap4u ((unsigned) x); + const unsigned newlo = bswap4u ((unsigned) (x >> 32)); + return ((unsigned long long) newhi << 32) | (unsigned long long) newlo; +} + +VDDS_INLINE void bswapSN (nn_sequence_number_t *sn) +{ + sn->high = bswap4 (sn->high); + sn->low = bswap4u (sn->low); +} + diff --git a/src/core/ddsi/include/ddsi/q_builtin_topic.h b/src/core/ddsi/include/ddsi/q_builtin_topic.h new file mode 100644 index 0000000..47175d2 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_builtin_topic.h @@ -0,0 +1,75 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_BUILTIN_TOPIC_H +#define Q_BUILTIN_TOPIC_H + +#include "ddsi/q_time.h" + +#include "dds_builtinTopics.h" + +struct entity_common; +struct nn_plist; + +/* Functions called at proxy entity creation/deletion time, so they + can do whatever is necessary to get the builtin topics function + correctly. + + These probably should return an error code, but I don't quite know + how to handle it yet and this way we have Coverity on our side. + Implementation is outside the common core. + + These may assume the proxy entities are stable, without parallel QoS + changes. */ + +void +propagate_builtin_topic_participant( + _In_ const struct entity_common *proxypp, + _In_ const nn_plist_t *plist, + _In_ nn_wctime_t timestamp, + _In_ int alive); + +void +propagate_builtin_topic_cmparticipant( + _In_ const struct entity_common *proxypp, + _In_ const nn_plist_t *plist, + _In_ nn_wctime_t timestamp, + _In_ int alive); +#if 0 +void dispose_builtin_topic_proxy_participant (const struct proxy_participant *proxypp, nn_wctime_t timestamp, int isimplicit); +void write_builtin_topic_proxy_writer (const struct proxy_writer *pwr, nn_wctime_t timestamp); +void dispose_builtin_topic_proxy_writer (const struct proxy_writer *pwr, nn_wctime_t timestamp, int isimplicit); +void write_builtin_topic_proxy_reader (const struct proxy_reader *prd, nn_wctime_t timestamp); +void dispose_builtin_topic_proxy_reader (const struct proxy_reader *prd, nn_wctime_t timestamp, int isimplicit); +void write_builtin_topic_proxy_group (const struct proxy_group *pgroup, nn_wctime_t timestamp); +void dispose_builtin_topic_proxy_group (const struct proxy_group *pgroup, nn_wctime_t timestamp, int isimplicit); + +void write_builtin_topic_proxy_topic (const struct nn_plist *datap, nn_wctime_t timestamp); +#endif + + +/* + * Let the layer on top of DDSI handle the received builtin data when it wants to. + */ +extern void +forward_builtin_participant( + _In_ DDS_ParticipantBuiltinTopicData *data, + _In_ nn_wctime_t timestamp, + _In_ int alive); + +extern void +forward_builtin_cmparticipant( + _In_ DDS_CMParticipantBuiltinTopicData *data, + _In_ nn_wctime_t timestamp, + _In_ int alive); + + +#endif diff --git a/src/core/ddsi/include/ddsi/q_config.h b/src/core/ddsi/include/ddsi/q_config.h new file mode 100644 index 0000000..d79c647 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_config.h @@ -0,0 +1,446 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_CONFIG_H +#define NN_CONFIG_H + +#include "os/os.h" + +#include "ddsi/q_log.h" +#include "ddsi/q_thread.h" +#ifdef DDSI_INCLUDE_ENCRYPTION +#include "ddsi/q_security.h" +#endif /* DDSI_INCLUDE_ENCRYPTION */ +#include "ddsi/q_xqos.h" +#include "ddsi/ddsi_tran.h" +#include "ddsi/q_feature_check.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +/* FIXME: should eventually move to abstraction layer */ +typedef enum q__schedPrioClass { + Q__SCHED_PRIO_RELATIVE, + Q__SCHED_PRIO_ABSOLUTE +} q__schedPrioClass; + +enum nn_standards_conformance { + NN_SC_PEDANTIC, + NN_SC_STRICT, + NN_SC_LAX +}; + +#define NN_PEDANTIC_P (config.standards_conformance <= NN_SC_PEDANTIC) +#define NN_STRICT_P (config.standards_conformance <= NN_SC_STRICT) + +enum besmode { + BESMODE_FULL, + BESMODE_WRITERS, + BESMODE_MINIMAL +}; + +enum retransmit_merging { + REXMIT_MERGE_NEVER, + REXMIT_MERGE_ADAPTIVE, + REXMIT_MERGE_ALWAYS +}; + +enum boolean_default { + BOOLDEF_DEFAULT, + BOOLDEF_FALSE, + BOOLDEF_TRUE +}; + +enum durability_cdr +{ + DUR_CDR_LE, + DUR_CDR_BE, + DUR_CDR_SERVER, + DUR_CDR_CLIENT +}; + +#define PARTICIPANT_INDEX_AUTO -1 +#define PARTICIPANT_INDEX_NONE -2 + +/* config_listelem must be an overlay for all used listelem types */ +struct config_listelem { + struct config_listelem *next; +}; + +#ifdef DDSI_INCLUDE_ENCRYPTION +struct q_security_plugins +{ + c_bool (*encode) (q_securityEncoderSet, uint32_t, void *, uint32_t, uint32_t *); + c_bool (*decode) (q_securityDecoderSet, void *, size_t, size_t *); + q_securityEncoderSet (*new_encoder) (void); + q_securityDecoderSet (*new_decoder) (void); + c_bool (*free_encoder) (q_securityEncoderSet); + c_bool (*free_decoder) (q_securityDecoderSet); + ssize_t (*send_encoded) (ddsi_tran_conn_t, struct msghdr *, q_securityEncoderSet *, uint32_t, uint32_t); + char * (*cipher_type) (q_cipherType); + c_bool (*cipher_type_from_string) (const char *, q_cipherType *); + uint32_t (*header_size) (q_securityEncoderSet, uint32_t); + q_cipherType (*encoder_type) (q_securityEncoderSet, uint32_t); + c_bool (*valid_uri) (q_cipherType, const char *); +}; + +struct q_security_plugins q_security_plugin; + +struct config_securityprofile_listelem +{ + struct config_securityprofile_listelem *next; + char *name; + q_cipherType cipher; + char *key; +}; +#endif /* DDSI_INCLUDE_ENCRYPTION */ + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS +struct config_networkpartition_listelem { + struct config_networkpartition_listelem *next; + char *name; + char *address_string; + struct addrset *as; + int connected; +#ifdef DDSI_INCLUDE_ENCRYPTION + char *profileName; + struct config_securityprofile_listelem *securityProfile; +#endif /* DDSI_INCLUDE_ENCRYPTION */ + uint32_t partitionHash; + uint32_t partitionId; +}; + +struct config_ignoredpartition_listelem { + struct config_ignoredpartition_listelem *next; + char *DCPSPartitionTopic; +}; + +struct config_partitionmapping_listelem { + struct config_partitionmapping_listelem *next; + char *networkPartition; + char *DCPSPartitionTopic; + struct config_networkpartition_listelem *partition; +}; +#endif /* DDSI_INCLUDE_NETWORK_PARTITIONS */ + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS +struct config_channel_listelem { + struct config_channel_listelem *next; + char *name; + int priority; + int64_t resolution; +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING + uint32_t data_bandwidth_limit; + uint32_t auxiliary_bandwidth_limit; +#endif + int diffserv_field; + struct thread_state1 *channel_reader_ts; /* keeping an handle to the running thread for this channel */ + struct nn_dqueue *dqueue; /* The handle of teh delivery queue servicing incoming data for this channel*/ + struct xeventq *evq; /* The handle of the event queue servicing this channel*/ + uint32_t queueId; /* the index of the networkqueue serviced by this channel*/ + struct ddsi_tran_conn * transmit_conn; /* the connection used for sending data out via this channel */ +}; +#endif /* DDSI_INCLUDE_NETWORK_CHANNELS */ + +struct config_maybe_int32 { + int isdefault; + int32_t value; +}; + +struct config_maybe_uint32 { + int isdefault; + uint32_t value; +}; + +struct config_maybe_int64 { + int isdefault; + int64_t value; +}; + +struct config_thread_properties_listelem { + struct config_thread_properties_listelem *next; + char *name; + os_schedClass sched_class; + struct config_maybe_int32 sched_priority; + struct config_maybe_uint32 stack_size; +}; + +struct config_peer_listelem +{ + struct config_peer_listelem *next; + char *peer; +}; + +struct prune_deleted_ppant { + int64_t delay; + int enforce_delay; +}; + +/* allow multicast bits: */ +#define AMC_FALSE 0u +#define AMC_SPDP 1u +#define AMC_ASM 2u +#ifdef DDSI_INCLUDE_SSM +#define AMC_SSM 4u +#define AMC_TRUE (AMC_SPDP | AMC_ASM | AMC_SSM) +#else +#define AMC_TRUE (AMC_SPDP | AMC_ASM) +#endif + +struct config +{ + int valid; + logcat_t enabled_logcats; + char *servicename; + char *pcap_file; + + char *networkAddressString; + char **networkRecvAddressStrings; + char *externalAddressString; + char *externalMaskString; + FILE *tracingOutputFile; + char *tracingOutputFileName; + int tracingTimestamps; + int tracingRelativeTimestamps; + int tracingAppendToFile; + unsigned allowMulticast; + int useIpv6; + int dontRoute; + int enableMulticastLoopback; + int domainId; + int participantIndex; + int maxAutoParticipantIndex; + int port_base; + struct config_maybe_int32 discoveryDomainId; + char *spdpMulticastAddressString; + char *defaultMulticastAddressString; + char *assumeMulticastCapable; + int64_t spdp_interval; + int64_t spdp_response_delay_max; + int64_t startup_mode_duration; + int64_t lease_duration; + int64_t const_hb_intv_sched; + int64_t const_hb_intv_sched_min; + int64_t const_hb_intv_sched_max; + int64_t const_hb_intv_min; + enum retransmit_merging retransmit_merging; + int64_t retransmit_merging_period; + int squash_participants; + int startup_mode_full; + int forward_all_messages; + int noprogress_log_stacktraces; + int prioritize_retransmit; + int xpack_send_async; + + unsigned primary_reorder_maxsamples; + unsigned secondary_reorder_maxsamples; + + unsigned delivery_queue_maxsamples; + + float servicelease_expiry_time; + float servicelease_update_factor; + + int enableLoopback; + enum durability_cdr durability_cdr; + + int buggy_datafrag_flags_mode; + int do_topic_discovery; + + uint32_t max_msg_size; + uint32_t fragment_size; + + int publish_uc_locators; /* Publish discovery unicast locators */ + + /* TCP transport configuration */ + + int tcp_enable; + int tcp_nodelay; + int tcp_port; + int64_t tcp_read_timeout; + int64_t tcp_write_timeout; + +#ifdef DDSI_INCLUDE_SSL + + /* SSL support for TCP */ + + int ssl_enable; + int ssl_verify; + int ssl_verify_client; + int ssl_self_signed; + char * ssl_keystore; + char * ssl_rand_file; + char * ssl_key_pass; + char * ssl_ciphers; + +#endif + + /* Thread pool configuration */ + + int tp_enable; + uint32_t tp_threads; + uint32_t tp_max_threads; + + int advertise_builtin_topic_writers; + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + struct config_channel_listelem *channels; + struct config_channel_listelem *max_channel; /* channel with highest prio; always computed */ +#endif /* DDSI_INCLUDE_NETWORK_CHANNELS */ +#ifdef DDSI_INCLUDE_ENCRYPTION + struct config_securityprofile_listelem *securityProfiles; +#endif /* DDSI_INCLUDE_ENCRYPTION */ +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + struct config_networkpartition_listelem *networkPartitions; + unsigned nof_networkPartitions; + struct config_ignoredpartition_listelem *ignoredPartitions; + struct config_partitionmapping_listelem *partitionMappings; +#endif /* DDSI_INCLUDE_NETWORK_PARTITIONS */ + struct config_peer_listelem *peers; + struct config_peer_listelem *peers_group; + struct config_thread_properties_listelem *thread_properties; + + /* debug/test/undoc features: */ + int xmit_lossiness; /**<< fraction of packets to drop on xmit, in units of 1e-3 */ + uint32_t rmsg_chunk_size; /**<< size of a chunk in the receive buffer */ + uint32_t rbuf_size; /* << size of a single receiver buffer */ + enum besmode besmode; + int aggressive_keep_last_whc; + int conservative_builtin_reader_startup; + int meas_hb_to_ack_latency; + int suppress_spdp_multicast; + int unicast_response_to_spdp_messages; + int synchronous_delivery_priority_threshold; + int64_t synchronous_delivery_latency_bound; + + /* Write cache */ + + int whc_batch; + uint32_t whc_lowwater_mark; + uint32_t whc_highwater_mark; + struct config_maybe_uint32 whc_init_highwater_mark; + int whc_adaptive; + + unsigned defrag_unreliable_maxsamples; + unsigned defrag_reliable_maxsamples; + unsigned accelerate_rexmit_block_size; + int64_t responsiveness_timeout; + uint32_t max_participants; + int64_t writer_linger_duration; + int multicast_ttl; + struct config_maybe_uint32 socket_min_rcvbuf_size; + uint32_t socket_min_sndbuf_size; + int64_t nack_delay; + int64_t preemptive_ack_delay; + int64_t schedule_time_rounding; + int64_t auto_resched_nack_delay; + int64_t ds_grace_period; +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING + uint32_t auxiliary_bandwidth_limit; /* bytes/second */ +#endif + uint32_t max_queued_rexmit_bytes; + unsigned max_queued_rexmit_msgs; + unsigned ddsi2direct_max_threads; + int late_ack_mode; + int retry_on_reject_besteffort; + int generate_keyhash; + uint32_t max_sample_size; + + /* compability options */ + enum nn_standards_conformance standards_conformance; + int explicitly_publish_qos_set_to_default; + int many_sockets_mode; + int arrival_of_data_asserts_pp_and_ep_liveliness; + int acknack_numbits_emptyset; + int respond_to_rti_init_zero_ack_with_invalid_heartbeat; + int assume_rti_has_pmd_endpoints; + + int port_dg; + int port_pg; + int port_d0; + int port_d1; + int port_d2; + int port_d3; + + int monitor_port; + + int enable_control_topic; + int initial_deaf; + int initial_mute; + int64_t initial_deaf_mute_reset; + int use_multicast_if_mreqn; + struct prune_deleted_ppant prune_deleted_ppant; + + /* not used by ddsi2, only validated; user layer directly accesses + the configuration tree */ + os_schedClass watchdog_sched_class; + int32_t watchdog_sched_priority; + q__schedPrioClass watchdog_sched_priority_class; +}; + +struct rhc; +struct nn_xqos; +struct tkmap_instance; +struct nn_rsample_info; +struct serdata; +struct sertopic; +struct proxy_writer; +struct proxy_writer_info; + +struct ddsi_plugin +{ + int (*init_fn) (void); + void (*fini_fn) (void); + + + /* Read cache */ + + void (*rhc_free_fn) (struct rhc *rhc); + void (*rhc_fini_fn) (struct rhc *rhc); + bool (*rhc_store_fn) + (struct rhc * __restrict rhc, const struct nn_rsample_info * __restrict sampleinfo, + struct serdata * __restrict sample, struct tkmap_instance * __restrict tk); + void (*rhc_unregister_wr_fn) + (struct rhc * __restrict rhc, const struct proxy_writer_info * __restrict pwr_info); + void (*rhc_relinquish_ownership_fn) + (struct rhc * __restrict rhc, const uint64_t wr_iid); + void (*rhc_set_qos_fn) (struct rhc * rhc, const struct nn_xqos * qos); + struct tkmap_instance * (*rhc_lookup_fn) (struct serdata *serdata); + void (*rhc_unref_fn) (struct tkmap_instance *tk); + + /* IID generator */ + + uint64_t (*iidgen_fn) (void); +}; + +extern struct config OSAPI_EXPORT config; +extern struct ddsi_plugin ddsi_plugin; + +struct cfgst; + +struct cfgst *config_init (_In_opt_ const char *configfile); +void config_print_cfgst (_In_ struct cfgst *cfgst); +void config_fini (_In_ struct cfgst *cfgst); + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS +struct config_partitionmapping_listelem *find_partitionmapping (const char *partition, const char *topic); +struct config_networkpartition_listelem *find_networkpartition_by_id (uint32_t id); +int is_ignored_partition (const char *partition, const char *topic); +#endif +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS +struct config_channel_listelem *find_channel (nn_transport_priority_qospolicy_t transport_priority); +#endif + +#if defined (__cplusplus) +} +#endif + +#endif /* NN_CONFIG_H */ diff --git a/src/core/ddsi/include/ddsi/q_ddsi_discovery.h b/src/core/ddsi/include/ddsi/q_ddsi_discovery.h new file mode 100644 index 0000000..d2ae27a --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_ddsi_discovery.h @@ -0,0 +1,47 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_DDSI_DISCOVERY_H +#define NN_DDSI_DISCOVERY_H + +#include "ddsi/q_unused.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +struct participant; +struct writer; +struct reader; +struct nn_rsample_info; +struct nn_rdata; +struct nn_plist; + +int spdp_write (struct participant *pp); +int spdp_dispose_unregister (struct participant *pp); + +int sedp_write_writer (struct writer *wr); +int sedp_write_reader (struct reader *rd); +int sedp_dispose_unregister_writer (struct writer *wr); +int sedp_dispose_unregister_reader (struct reader *rd); + +int sedp_write_topic (struct participant *pp, const struct nn_plist *datap); +int sedp_write_cm_participant (struct participant *pp, int alive); +int sedp_write_cm_publisher (const struct nn_plist *datap, int alive); +int sedp_write_cm_subscriber (const struct nn_plist *datap, int alive); + +int builtins_dqueue_handler (const struct nn_rsample_info *sampleinfo, const struct nn_rdata *fragchain, const nn_guid_t *rdguid, void *qarg); + +#if defined (__cplusplus) +} +#endif + +#endif /* NN_DDSI_DISCOVERY_H */ diff --git a/src/core/ddsi/include/ddsi/q_debmon.h b/src/core/ddsi/include/ddsi/q_debmon.h new file mode 100644 index 0000000..d6e4c1c --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_debmon.h @@ -0,0 +1,23 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_DEBMON_H +#define Q_DEBMON_H + +struct debug_monitor; +typedef int (*debug_monitor_cpf_t) (ddsi_tran_conn_t conn, const char *fmt, ...); +typedef int (*debug_monitor_plugin_t) (ddsi_tran_conn_t conn, debug_monitor_cpf_t cpf, void *arg); + +struct debug_monitor *new_debug_monitor (int port); +void add_debug_monitor_plugin (struct debug_monitor *dm, debug_monitor_plugin_t fn, void *arg); +void free_debug_monitor (struct debug_monitor *dm); + +#endif /* defined(__ospli_osplo__q_debmon__) */ diff --git a/src/core/ddsi/include/ddsi/q_entity.h b/src/core/ddsi/include/ddsi/q_entity.h new file mode 100644 index 0000000..48057ce --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_entity.h @@ -0,0 +1,579 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_ENTITY_H +#define Q_ENTITY_H + +#include "os/os.h" +#include "util/ut_avl.h" +#include "ddsi/q_rtps.h" +#include "ddsi/q_protocol.h" +#include "ddsi/q_lat_estim.h" +#include "ddsi/q_ephash.h" +#include "ddsi/q_hbcontrol.h" +#include "ddsi/q_feature_check.h" + +#include "ddsi/ddsi_tran.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +struct xevent; +struct nn_reorder; +struct nn_defrag; +struct nn_dqueue; +struct addrset; +struct sertopic; +struct whc; +struct nn_xqos; +struct nn_plist; +struct v_gid_s; + +struct proxy_group; +struct proxy_endpoint_common; +typedef void (*ddsi2direct_directread_cb_t) (const struct nn_rsample_info *sampleinfo, const struct nn_rdata *fragchain, void *arg); + +typedef struct status_cb_data +{ + uint32_t status; + uint32_t extra; + uint64_t handle; + bool add; +} +status_cb_data_t; + +typedef void (*status_cb_t) (void * entity, const status_cb_data_t * data); + +struct prd_wr_match { + ut_avlNode_t avlnode; + nn_guid_t wr_guid; +}; + +struct rd_pwr_match { + ut_avlNode_t avlnode; + nn_guid_t pwr_guid; +#ifdef DDSI_INCLUDE_SSM + nn_locator_t ssm_mc_loc; + nn_locator_t ssm_src_loc; +#endif +}; + +struct wr_rd_match { + ut_avlNode_t avlnode; + nn_guid_t rd_guid; +}; + +struct rd_wr_match { + ut_avlNode_t avlnode; + nn_guid_t wr_guid; +}; + +struct wr_prd_match { + ut_avlNode_t avlnode; + nn_guid_t prd_guid; /* guid of the proxy reader */ + unsigned assumed_in_sync: 1; /* set to 1 upon receipt of ack not nack'ing msgs */ + unsigned has_replied_to_hb: 1; /* we must keep sending HBs until all readers have this set */ + unsigned all_have_replied_to_hb: 1; /* true iff 'has_replied_to_hb' for all readers in subtree */ + unsigned is_reliable: 1; /* true iff reliable proxy reader */ + seqno_t min_seq; /* smallest ack'd seq nr in subtree */ + seqno_t max_seq; /* sort-of highest ack'd seq nr in subtree (see augment function) */ + seqno_t seq; /* highest acknowledged seq nr */ + int num_reliable_readers_where_seq_equals_max; + nn_guid_t arbitrary_unacked_reader; + nn_count_t next_acknack; /* next acceptable acknack sequence number */ + nn_count_t next_nackfrag; /* next acceptable nackfrag sequence number */ + nn_etime_t t_acknack_accepted; /* (local) time an acknack was last accepted */ + struct nn_lat_estim hb_to_ack_latency; + nn_wctime_t hb_to_ack_latency_tlastlog; + uint32_t non_responsive_count; + uint32_t rexmit_requests; +}; + +enum pwr_rd_match_syncstate { + PRMSS_SYNC, /* in sync with proxy writer, has caught up with historical data */ + PRMSS_TLCATCHUP, /* in sync with proxy writer, pwr + readers still catching up on historical data */ + PRMSS_OUT_OF_SYNC /* not in sync with proxy writer */ +}; + +struct pwr_rd_match { + ut_avlNode_t avlnode; + nn_guid_t rd_guid; + nn_mtime_t tcreate; + nn_count_t count; /* most recent acknack sequence number */ + nn_count_t next_heartbeat; /* next acceptable heartbeat (see also add_proxy_writer_to_reader) */ + nn_wctime_t hb_timestamp; /* time of most recent heartbeat that rescheduled the ack event */ + nn_etime_t t_heartbeat_accepted; /* (local) time a heartbeat was last accepted */ + nn_mtime_t t_last_nack; /* (local) time we last sent a NACK */ /* FIXME: probably elapsed time is better */ + seqno_t seq_last_nack; /* last seq for which we requested a retransmit */ + struct xevent *acknack_xevent; /* entry in xevent queue for sending acknacks */ + enum pwr_rd_match_syncstate in_sync; /* whether in sync with the proxy writer */ + union { + struct { + seqno_t end_of_tl_seq; /* when seq >= end_of_tl_seq, it's in sync, =0 when not tl */ + seqno_t end_of_out_of_sync_seq; /* when seq >= end_of_tl_seq, it's in sync, =0 when not tl */ + struct nn_reorder *reorder; /* can be done (mostly) per proxy writer, but that is harder; only when state=OUT_OF_SYNC */ + } not_in_sync; + } u; +}; + +struct nn_rsample_info; +struct nn_rdata; + +struct entity_common { + enum entity_kind kind; + nn_guid_t guid; + char *name; + uint64_t iid; + os_mutex lock; + bool onlylocal; +}; + +struct local_reader_ary { + os_mutex rdary_lock; + unsigned valid: 1; /* always true until (proxy-)writer is being deleted; !valid => !fastpath_ok */ + unsigned fastpath_ok: 1; /* if not ok, fall back to using GUIDs (gives access to the reader-writer match data for handling readers that bumped into resource limits, hence can flip-flop, unlike "valid") */ + int n_readers; + struct reader **rdary; /* for efficient delivery, null-pointer terminated */ +}; + +struct participant +{ + struct entity_common e; + long long lease_duration; /* constant */ + unsigned bes; /* built-in endpoint set */ + unsigned prismtech_bes; /* prismtech-specific extension of built-in endpoints set */ + unsigned is_ddsi2_pp: 1; /* true for the "federation leader", the ddsi2 participant itself in OSPL; FIXME: probably should use this for broker mode as well ... */ + nn_plist_t *plist; /* settings/QoS for this participant */ + struct xevent *spdp_xevent; /* timed event for periodically publishing SPDP */ + struct xevent *pmd_update_xevent; /* timed event for periodically publishing ParticipantMessageData */ + nn_locator_t m_locator; + ddsi_tran_conn_t m_conn; + unsigned next_entityid; /* next available entity id [e.lock] */ + os_mutex refc_lock; + int32_t user_refc; /* number of non-built-in endpoints in this participant [refc_lock] */ + int32_t builtin_refc; /* number of built-in endpoints in this participant [refc_lock] */ + int builtins_deleted; /* whether deletion of built-in endpoints has been initiated [refc_lock] */ +}; + +struct endpoint_common { + struct participant *pp; + nn_guid_t group_guid; +}; + +struct generic_endpoint { /* FIXME: currently only local endpoints; proxies use entity_common + proxy_endpoint common */ + struct entity_common e; + struct endpoint_common c; +}; + +enum writer_state { + WRST_OPERATIONAL, /* normal situation */ + WRST_LINGERING, /* writer deletion has been requested but still has unack'd data */ + WRST_DELETING /* writer is actually being deleted (removed from hash table) */ +}; + +#if OS_ATOMIC64_SUPPORT +typedef os_atomic_uint64_t seq_xmit_t; +#define INIT_SEQ_XMIT(wr, v) os_atomic_st64(&(wr)->seq_xmit, (uint64_t) (v)) +#define READ_SEQ_XMIT(wr) ((seqno_t) os_atomic_ld64(&(wr)->seq_xmit)) +#define UPDATE_SEQ_XMIT_LOCKED(wr, nv) do { uint64_t ov_; do { \ + ov_ = os_atomic_ld64(&(wr)->seq_xmit); \ + if ((uint64_t) nv <= ov_) break; \ +} while (!os_atomic_cas64(&(wr)->seq_xmit, ov_, (uint64_t) nv)); } while (0) +#define UPDATE_SEQ_XMIT_UNLOCKED(sx, nv) UPDATE_SEQ_XMIT_LOCKED(sx, nv) +#else +typedef seqno_t seq_xmit_t; +#define INIT_SEQ_XMIT(wr, v) ((wr)->seq_xmit = (v)) +#define READ_SEQ_XMIT(wr) ((wr)->seq_xmit) +#define UPDATE_SEQ_XMIT_LOCKED(wr, nv) do { \ + if ((nv) > (wr)->seq_xmit) { (wr)->seq_xmit = (nv); } \ +} while (0) +#define UPDATE_SEQ_XMIT_UNLOCKED(wr, nv) do { \ + os_mutexLock (&(wr)->e.lock); \ + if ((nv) > (wr)->seq_xmit) { (wr)->seq_xmit = (nv); } \ + os_mutexUnlock (&(wr)->e.lock); \ +} while (0) +#endif + +struct writer +{ + struct entity_common e; + struct endpoint_common c; + status_cb_t status_cb; + void * status_cb_entity; + os_cond throttle_cond; /* used to trigger a transmit thread blocked in throttle_writer() */ + seqno_t seq; /* last sequence number (transmitted seqs are 1 ... seq) */ + seqno_t cs_seq; /* 1st seq in coherent set (or 0) */ + seq_xmit_t seq_xmit; /* last sequence number actually transmitted */ + seqno_t min_local_readers_reject_seq; /* mimum of local_readers->last_deliv_seq */ + nn_count_t hbcount; /* last hb seq number */ + nn_count_t hbfragcount; /* last hb frag seq number */ + int throttling; /* non-zero when some thread is waiting for the WHC to shrink */ + struct hbcontrol hbcontrol; /* controls heartbeat timing, piggybacking */ + struct nn_xqos *xqos; + enum writer_state state; + unsigned reliable: 1; /* iff 1, writer is reliable <=> heartbeat_xevent != NULL */ + unsigned handle_as_transient_local: 1; /* controls whether data is retained in WHC */ + unsigned aggressive_keep_last: 1; /* controls whether KEEP_LAST will overwrite samples that haven't been ACK'd yet */ + unsigned startup_mode: 1; /* causes data to be treated as T-L for a while */ + unsigned include_keyhash: 1; /* iff 1, this writer includes a keyhash; keyless topics => include_keyhash = 0 */ + unsigned retransmitting: 1; /* iff 1, this writer is currently retransmitting */ +#ifdef DDSI_INCLUDE_SSM + unsigned supports_ssm: 1; + struct addrset *ssm_as; +#endif + const struct sertopic * topic; /* topic, but may be NULL for built-ins */ + struct addrset *as; /* set of addresses to publish to */ + struct addrset *as_group; /* alternate case, used for SPDP, when using Cloud with multiple bootstrap locators */ + struct xevent *heartbeat_xevent; /* timed event for "periodically" publishing heartbeats when unack'd data present, NULL <=> unreliable */ + long long lease_duration; + struct whc *whc; /* WHC tracking history, T-L durability service history + samples by sequence number for retransmit */ + uint32_t whc_low, whc_high; /* watermarks for WHC in bytes (counting only unack'd data) */ + nn_etime_t t_rexmit_end; /* time of last 1->0 transition of "retransmitting" */ + nn_etime_t t_whc_high_upd; /* time "whc_high" was last updated for controlled ramp-up of throughput */ + int num_reliable_readers; /* number of matching reliable PROXY readers */ + ut_avlTree_t readers; /* all matching PROXY readers, see struct wr_prd_match */ + ut_avlTree_t local_readers; /* all matching LOCAL readers, see struct wr_rd_match */ +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + uint32_t partition_id; +#endif + uint32_t num_acks_received; /* cum received ACKNACKs with no request for retransmission */ + uint32_t num_nacks_received; /* cum received ACKNACKs that did request retransmission */ + uint32_t throttle_count; /* cum times transmitting was throttled (whc hitting high-level mark) */ + uint32_t throttle_tracing; + uint32_t rexmit_count; /* cum samples retransmitted (counting events; 1 sample can be counted many times) */ + uint32_t rexmit_lost_count; /* cum samples lost but retransmit requested (also counting events) */ + struct xeventq *evq; /* timed event queue to be used by this writer */ + struct local_reader_ary rdary; /* LOCAL readers for fast-pathing; if not fast-pathed, fall back to scanning local_readers */ +}; + +struct reader +{ + struct entity_common e; + struct endpoint_common c; + status_cb_t status_cb; + void * status_cb_entity; + struct rhc * rhc; /* reader history, tracks registrations and data */ + struct nn_xqos *xqos; + unsigned reliable: 1; /* 1 iff reader is reliable */ + unsigned handle_as_transient_local: 1; /* 1 iff reader wants historical data from proxy writers */ +#ifdef DDSI_INCLUDE_SSM + unsigned favours_ssm: 1; /* iff 1, this reader favours SSM */ +#endif + nn_count_t init_acknack_count; /* initial value for "count" (i.e. ACK seq num) for newly matched proxy writers */ +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + struct addrset *as; +#endif + const struct sertopic * topic; /* topic is NULL for built-in readers */ + ut_avlTree_t writers; /* all matching PROXY writers, see struct rd_pwr_match */ + ut_avlTree_t local_writers; /* all matching LOCAL writers, see struct rd_wr_match */ + ddsi2direct_directread_cb_t ddsi2direct_cb; + void *ddsi2direct_cbarg; +}; + +struct proxy_participant +{ + struct entity_common e; + uint32_t refc; /* number of proxy endpoints (both user & built-in; not groups, they don't have a life of their own) */ + nn_vendorid_t vendor; /* vendor code from discovery */ + unsigned bes; /* built-in endpoint set */ + unsigned prismtech_bes; /* prismtech-specific extension of built-in endpoints set */ + nn_guid_t privileged_pp_guid; /* if this PP depends on another PP for its SEDP writing */ + nn_plist_t *plist; /* settings/QoS for this participant */ + os_atomic_voidp_t lease; /* lease object for this participant, for automatic leases */ + struct addrset *as_default; /* default address set to use for user data traffic */ + struct addrset *as_meta; /* default address set to use for discovery traffic */ + struct proxy_endpoint_common *endpoints; /* all proxy endpoints can be reached from here */ + ut_avlTree_t groups; /* table of all groups (publisher, subscriber), see struct proxy_group */ + unsigned kernel_sequence_numbers : 1; /* whether this proxy participant generates OSPL kernel sequence numbers */ + unsigned implicitly_created : 1; /* participants are implicitly created for Cloud/Fog discovered endpoints */ + unsigned is_ddsi2_pp: 1; /* if this is the federation-leader on the remote node */ + unsigned minimal_bes_mode: 1; + unsigned lease_expired: 1; + unsigned proxypp_have_spdp: 1; + unsigned proxypp_have_cm: 1; + unsigned owns_lease: 1; +}; + +/* Representing proxy subscriber & publishers as "groups": until DDSI2 + gets a reason to care about these other than for the generation of + CM topics, there's little value in distinguishing between the two. + In another way, they're secondly-class citizens, too: "real" + entities are garbage collected and found using lock-free hash + tables, but "groups" only live in the context of a proxy + participant. */ +struct proxy_group { + ut_avlNode_t avlnode; + nn_guid_t guid; + char *name; + struct proxy_participant *proxypp; /* uncounted backref to proxy participant */ + struct nn_xqos *xqos; /* publisher/subscriber QoS */ +}; + +struct proxy_endpoint_common +{ + struct proxy_participant *proxypp; /* counted backref to proxy participant */ + struct proxy_endpoint_common *next_ep; /* next \ endpoint belonging to this proxy participant */ + struct proxy_endpoint_common *prev_ep; /* prev / -- this is in arbitrary ordering */ + struct nn_xqos *xqos; /* proxy endpoint QoS lives here; FIXME: local ones should have it moved to common as well */ + const struct sertopic * topic; /* topic may be NULL: for built-ins, but also for never-yet matched proxies (so we don't have to know the topic; when we match, we certainly do know) */ + struct addrset *as; /* address set to use for communicating with this endpoint */ + nn_guid_t group_guid; /* 0:0:0:0 if not available */ + nn_vendorid_t vendor; /* cached from proxypp->vendor */ +}; + +struct proxy_writer { + struct entity_common e; + struct proxy_endpoint_common c; + ut_avlTree_t readers; /* matching LOCAL readers, see pwr_rd_match */ + int n_reliable_readers; /* number of those that are reliable */ + int n_readers_out_of_sync; /* number of those that require special handling (accepting historical data, waiting for historical data set to become complete) */ + seqno_t last_seq; /* highest known seq published by the writer, not last delivered */ + uint32_t last_fragnum; /* last known frag for last_seq, or ~0u if last_seq not partial */ + nn_count_t nackfragcount; /* last nackfrag seq number */ + os_atomic_uint32_t next_deliv_seq_lowword; /* lower 32-bits for next sequence number that will be delivered; for generating acks; 32-bit so atomic reads on all supported platforms */ + unsigned last_fragnum_reset: 1; /* iff set, heartbeat advertising last_seq as highest seq resets last_fragnum */ + unsigned deliver_synchronously: 1; /* iff 1, delivery happens straight from receive thread for non-historical data; else through delivery queue "dqueue" */ + unsigned have_seen_heartbeat: 1; /* iff 1, we have received at least on heartbeat from this proxy writer */ + unsigned assert_pp_lease: 1; /* iff 1, renew the proxy-participant's lease when data comes in */ + unsigned local_matching_inprogress: 1; /* iff 1, we are still busy matching local readers; this is so we don't deliver incoming data to some but not all readers initially */ +#ifdef DDSI_INCLUDE_SSM + unsigned supports_ssm: 1; /* iff 1, this proxy writer supports SSM */ +#endif + struct nn_defrag *defrag; /* defragmenter for this proxy writer; FIXME: perhaps shouldn't be for historical data */ + struct nn_reorder *reorder; /* message reordering for this proxy writer, out-of-sync readers can have their own, see pwr_rd_match */ + struct nn_dqueue *dqueue; /* delivery queue for asynchronous delivery (historical data is always delivered asynchronously) */ + struct xeventq *evq; /* timed event queue to be used for ACK generation */ + struct local_reader_ary rdary; /* LOCAL readers for fast-pathing; if not fast-pathed, fall back to scanning local_readers */ + ddsi2direct_directread_cb_t ddsi2direct_cb; + void *ddsi2direct_cbarg; +}; + +struct proxy_reader { + struct entity_common e; + struct proxy_endpoint_common c; + unsigned deleting: 1; /* set when being deleted */ + unsigned is_fict_trans_reader: 1; /* only true when it is certain that is a fictitious transient data reader (affects built-in topic generation) */ + unsigned assert_pp_lease: 1; /* iff 1, renew the proxy-participant's lease when data comes in */ +#ifdef DDSI_INCLUDE_SSM + unsigned favours_ssm: 1; /* iff 1, this proxy reader favours SSM when available */ +#endif + ut_avlTree_t writers; /* matching LOCAL writers */ +}; + +extern const ut_avlTreedef_t wr_readers_treedef; +extern const ut_avlTreedef_t wr_local_readers_treedef; +extern const ut_avlTreedef_t rd_writers_treedef; +extern const ut_avlTreedef_t rd_local_writers_treedef; +extern const ut_avlTreedef_t pwr_readers_treedef; +extern const ut_avlTreedef_t prd_writers_treedef; +extern const ut_avlTreedef_t deleted_participants_treedef; + +#define DPG_LOCAL 1 +#define DPG_REMOTE 2 + +int deleted_participants_admin_init (void); +void deleted_participants_admin_fini (void); +int is_deleted_participant_guid (const struct nn_guid *guid, unsigned for_what); + +nn_entityid_t to_entityid (unsigned u); +int is_builtin_entityid (nn_entityid_t id, nn_vendorid_t vendorid); +int is_writer_entityid (nn_entityid_t id); +int is_reader_entityid (nn_entityid_t id); + +int pp_allocate_entityid (nn_entityid_t *id, unsigned kind, struct participant *pp); + +/* Interface for glue code between the OpenSplice kernel and the DDSI + entities. These all return 0 iff successful. All GIDs supplied + __MUST_BE_UNIQUE__. All hell may break loose if they aren't. + + All delete operations synchronously remove the entity being deleted + from the various global hash tables on GUIDs. This ensures no new + operations can be invoked by the glue code, discovery, protocol + messages, &c. The entity is then scheduled for garbage collection. + + There is one exception: a participant without built-in + endpoints: that one synchronously reaches reference count zero + and is then freed immediately. + + If new_writer() and/or new_reader() may be called in parallel to + delete_participant(), trouble ensues. The current glue code + performs all local discovery single-threaded, and can't ever get + into that issue. + + A garbage collector thread is used to perform the actual freeing of + an entity, but it never does so before all threads have made + sufficient progress to guarantee they are not using that entity any + longer, with the exception of use via internal pointers in the + entity data structures. + + An example of the latter is that (proxy) endpoints have a pointer + to the owning (proxy) participant, but the (proxy) participant is + reference counted to make this safe. + + The case of a proxy writer is particularly complicated is it has to + pass through a multiple-stage delay in the garbage collector before + it may be freed: first there is the possibility of a parallel + delete or protocol message, then there is still the possibility of + data in a delivery queue. This is dealt by requeueing garbage + collection and sending bubbles through the delivery queue. */ + +/* Set this flag in new_participant to prevent the creation SPDP, SEDP + and PMD readers for that participant. It doesn't really need it, + they all share the information anyway. But you do need it once. */ +#define RTPS_PF_NO_BUILTIN_READERS 1u +/* Set this flag to prevent the creation of SPDP, SEDP and PMD + writers. It will then rely on the "privileged participant", which + must exist at the time of creation. It creates a reference to that + "privileged participant" to ensure it won't disappear too early. */ +#define RTPS_PF_NO_BUILTIN_WRITERS 2u +/* Set this flag to mark the participant as the "privileged + participant", there can only be one of these. The privileged + participant MUST have all builtin readers and writers. */ +#define RTPS_PF_PRIVILEGED_PP 4u + /* Set this flag to mark the participant as is_ddsi2_pp. */ +#define RTPS_PF_IS_DDSI2_PP 8u + /* Set this flag to mark the participant as an local entity only. */ +#define RTPS_PF_ONLY_LOCAL 16u + +/* To create a DDSI participant given a GUID. May return ERR_OUT_OF_IDS + (a.o.) */ +int new_participant_guid (const nn_guid_t *ppguid, unsigned flags, const struct nn_plist *plist); + +int new_participant (struct nn_guid *ppguid, unsigned flags, const struct nn_plist *plist); + +/* To delete a DDSI participant: this only removes the participant + from the hash tables and schedules the actual delete operation, + which will start doing scary things once all but the DDSI built-in + endpoints are gone. It is acceptable to call delete_participant() + before all its readers and writers have been deleted (which also + fits nicely with model where the glue calls merely schedules + garbage-collection). */ +int delete_participant (const struct nn_guid *ppguid); + +/* To obtain the builtin writer to be used for publishing SPDP, SEDP, + PMD stuff for PP and its endpoints, given the entityid. If PP has + its own writer, use it; else use the privileged participant. */ +struct writer *get_builtin_writer (const struct participant *pp, unsigned entityid); + +/* To create a new DDSI writer or reader belonging to participant with + GUID "ppguid". May return NULL if participant unknown or + writer/reader already known. */ + +struct writer * new_writer +( + struct nn_guid *wrguid, + const struct nn_guid *group_guid, + const struct nn_guid *ppguid, + const struct sertopic *topic, + const struct nn_xqos *xqos, + status_cb_t status_cb, + void * status_cb_arg +); + +struct reader * new_reader +( + struct nn_guid *rdguid, + const struct nn_guid *group_guid, + const struct nn_guid *ppguid, + const struct sertopic *topic, + const struct nn_xqos *xqos, + struct rhc * rhc, + status_cb_t status_cb, + void * status_cb_arg +); + +struct whc_node; +unsigned remove_acked_messages (struct writer *wr, struct whc_node **deferred_free_list); +unsigned remove_acked_messages_and_free (struct writer *wr); +seqno_t writer_max_drop_seq (const struct writer *wr); +int writer_must_have_hb_scheduled (const struct writer *wr); +void writer_set_retransmitting (struct writer *wr); +void writer_clear_retransmitting (struct writer *wr); + +int delete_writer (const struct nn_guid *guid); +int delete_writer_nolinger (const struct nn_guid *guid); +int delete_writer_nolinger_locked (struct writer *wr); + +int delete_reader (const struct nn_guid *guid); +uint64_t reader_instance_id (const struct nn_guid *guid); + +/* To create or delete a new proxy participant: "guid" MUST have the + pre-defined participant entity id. Unlike delete_participant(), + deleting a proxy participant will automatically delete all its + readers & writers. Delete removes the participant from a hash table + and schedules the actual deletion. + + -- XX what about proxy participants without built-in endpoints? + XX -- +*/ + +/* Set this custom flag when using nn_prismtech_writer_info_t iso nn_prismtech_writer_info_old_t */ +#define CF_INC_KERNEL_SEQUENCE_NUMBERS (1 << 0) +/* Set when this proxy participant is created implicitly and has to be deleted upon disappearance + of its last endpoint. FIXME: Currently there is a potential race with adding a new endpoint + in parallel to deleting the last remaining one. The endpoint will then be created, added to the + proxy participant and then both are deleted. With the current single-threaded discovery + this can only happen when it is all triggered by lease expiry. */ +#define CF_IMPLICITLY_CREATED_PROXYPP (1 << 1) +/* Set when this proxy participant is a DDSI2 participant, to help Cloud figure out whom to send + discovery data when used in conjunction with the networking bridge */ +#define CF_PARTICIPANT_IS_DDSI2 (1 << 2) +/* Set when this proxy participant is not to be announced on the built-in topics yet */ +#define CF_PROXYPP_NO_SPDP (1 << 3) + +void new_proxy_participant (const struct nn_guid *guid, unsigned bes, unsigned prismtech_bes, const struct nn_guid *privileged_pp_guid, struct addrset *as_default, struct addrset *as_meta, const struct nn_plist *plist, int64_t tlease_dur, nn_vendorid_t vendor, unsigned custom_flags, nn_wctime_t timestamp); +int delete_proxy_participant_by_guid (const struct nn_guid * guid, nn_wctime_t timestamp, int isimplicit); +uint64_t participant_instance_id (const struct nn_guid *guid); + +enum update_proxy_participant_source { + UPD_PROXYPP_SPDP, + UPD_PROXYPP_CM +}; + +int update_proxy_participant_plist_locked (struct proxy_participant *proxypp, const struct nn_plist *datap, enum update_proxy_participant_source source, nn_wctime_t timestamp); +int update_proxy_participant_plist (struct proxy_participant *proxypp, const struct nn_plist *datap, enum update_proxy_participant_source source, nn_wctime_t timestamp); +void proxy_participant_reassign_lease (struct proxy_participant *proxypp, struct lease *newlease); + +void purge_proxy_participants (const nn_locator_t *loc, bool delete_from_as_disc); + +/* To create a new proxy writer or reader; the proxy participant is + determined from the GUID and must exist. */ +int new_proxy_writer (const struct nn_guid *ppguid, const struct nn_guid *guid, struct addrset *as, const struct nn_plist *plist, struct nn_dqueue *dqueue, struct xeventq *evq, nn_wctime_t timestamp); +int new_proxy_reader (const struct nn_guid *ppguid, const struct nn_guid *guid, struct addrset *as, const struct nn_plist *plist, nn_wctime_t timestamp +#ifdef DDSI_INCLUDE_SSM + , int favours_ssm +#endif + ); + +/* To delete a proxy writer or reader; these synchronously hide it + from the outside world, preventing it from being matched to a + reader or writer. Actual deletion is scheduled in the future, when + no outstanding references may still exist (determined by checking + thread progress, &c.). */ +int delete_proxy_writer (const struct nn_guid *guid, nn_wctime_t timestamp, int isimplicit); +int delete_proxy_reader (const struct nn_guid *guid, nn_wctime_t timestamp, int isimplicit); + +void update_proxy_reader (struct proxy_reader * prd, struct addrset *as); +void update_proxy_writer (struct proxy_writer * pwr, struct addrset *as); + +int new_proxy_group (const struct nn_guid *guid, const struct v_gid_s *gid, const char *name, const struct nn_xqos *xqos, nn_wctime_t timestamp); +void delete_proxy_group (const struct nn_guid *guid, nn_wctime_t timestamp, int isimplicit); + +void writer_exit_startup_mode (struct writer *wr); +uint64_t writer_instance_id (const struct nn_guid *guid); + + +#if defined (__cplusplus) +} +#endif + +#endif /* Q_ENTITY_H */ diff --git a/src/core/ddsi/include/ddsi/q_ephash.h b/src/core/ddsi/include/ddsi/q_ephash.h new file mode 100644 index 0000000..43863ce --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_ephash.h @@ -0,0 +1,142 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_EPHASH_H +#define Q_EPHASH_H + +#include "os/os_defs.h" +#include "util/ut_hopscotch.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +struct ephash; +struct participant; +struct reader; +struct writer; +struct proxy_participant; +struct proxy_reader; +struct proxy_writer; +struct nn_guid; + + enum entity_kind { + EK_PARTICIPANT, + EK_PROXY_PARTICIPANT, + EK_WRITER, + EK_PROXY_WRITER, + EK_READER, + EK_PROXY_READER + }; +#define EK_NKINDS ((int) EK_PROXY_READER + 1) + +struct ephash_enum +{ + struct ut_chhIter it; + enum entity_kind kind; + struct entity_common *cur; +}; + +/* Readers & writers are both in a GUID- and in a GID-keyed table. If + they are in the GID-based one, they are also in the GUID-based one, + but not the way around, for two reasons: + + - firstly, there are readers & writers that do not have a GID + (built-in endpoints, fictitious transient data readers), + + - secondly, they are inserted first in the GUID-keyed one, and then + in the GID-keyed one. + + The GID is used solely for the interface with the OpenSplice + kernel, all internal state and protocol handling is done using the + GUID. So all this means is that, e.g., a writer being deleted + becomes invisible to the network reader slightly before it + disappears in the protocol handling, or that a writer might exist + at the protocol level slightly before the network reader can use it + to transmit data. */ + +struct ephash *ephash_new (void); +void ephash_free (struct ephash *ephash); + +void ephash_insert_participant_guid (struct participant *pp); +void ephash_insert_proxy_participant_guid (struct proxy_participant *proxypp); +void ephash_insert_writer_guid (struct writer *wr); +void ephash_insert_reader_guid (struct reader *rd); +void ephash_insert_proxy_writer_guid (struct proxy_writer *pwr); +void ephash_insert_proxy_reader_guid (struct proxy_reader *prd); + +void ephash_remove_participant_guid (struct participant *pp); +void ephash_remove_proxy_participant_guid (struct proxy_participant *proxypp); +void ephash_remove_writer_guid (struct writer *wr); +void ephash_remove_reader_guid (struct reader *rd); +void ephash_remove_proxy_writer_guid (struct proxy_writer *pwr); +void ephash_remove_proxy_reader_guid (struct proxy_reader *prd); + +void *ephash_lookup_guid (const struct nn_guid *guid, enum entity_kind kind); + +struct participant *ephash_lookup_participant_guid (const struct nn_guid *guid); +struct proxy_participant *ephash_lookup_proxy_participant_guid (const struct nn_guid *guid); +struct writer *ephash_lookup_writer_guid (const struct nn_guid *guid); +struct reader *ephash_lookup_reader_guid (const struct nn_guid *guid); +struct proxy_writer *ephash_lookup_proxy_writer_guid (const struct nn_guid *guid); +struct proxy_reader *ephash_lookup_proxy_reader_guid (const struct nn_guid *guid); + + +/* Enumeration of entries in the hash table: + + - "next" visits at least all entries that were in the hash table at + the time of calling init and that have not subsequently been + removed; + + - "next" may visit an entry more than once, but will do so only + because of rare events (i.e., resize or so); + + - the order in which entries are visited is arbitrary; + + - the caller must call init() before it may call next(); it must + call fini() before it may call init() again. */ +struct ephash_enum_participant { struct ephash_enum st; }; +struct ephash_enum_writer { struct ephash_enum st; }; +struct ephash_enum_reader { struct ephash_enum st; }; +struct ephash_enum_proxy_participant { struct ephash_enum st; }; +struct ephash_enum_proxy_writer { struct ephash_enum st; }; +struct ephash_enum_proxy_reader { struct ephash_enum st; }; + +void ephash_enum_init (struct ephash_enum *st, enum entity_kind kind); +void *ephash_enum_next (struct ephash_enum *st); +void ephash_enum_fini (struct ephash_enum *st); + +void ephash_enum_writer_init (struct ephash_enum_writer *st); +void ephash_enum_reader_init (struct ephash_enum_reader *st); +void ephash_enum_proxy_writer_init (struct ephash_enum_proxy_writer *st); +void ephash_enum_proxy_reader_init (struct ephash_enum_proxy_reader *st); +void ephash_enum_participant_init (struct ephash_enum_participant *st); +void ephash_enum_proxy_participant_init (struct ephash_enum_proxy_participant *st); + +struct writer *ephash_enum_writer_next (struct ephash_enum_writer *st); +struct reader *ephash_enum_reader_next (struct ephash_enum_reader *st); +struct proxy_writer *ephash_enum_proxy_writer_next (struct ephash_enum_proxy_writer *st); +struct proxy_reader *ephash_enum_proxy_reader_next (struct ephash_enum_proxy_reader *st); +struct participant *ephash_enum_participant_next (struct ephash_enum_participant *st); +struct proxy_participant *ephash_enum_proxy_participant_next (struct ephash_enum_proxy_participant *st); + +void ephash_enum_writer_fini (struct ephash_enum_writer *st); +void ephash_enum_reader_fini (struct ephash_enum_reader *st); +void ephash_enum_proxy_writer_fini (struct ephash_enum_proxy_writer *st); +void ephash_enum_proxy_reader_fini (struct ephash_enum_proxy_reader *st); +void ephash_enum_participant_fini (struct ephash_enum_participant *st); +void ephash_enum_proxy_participant_fini (struct ephash_enum_proxy_participant *st); + +#if defined (__cplusplus) +} +#endif + +#endif /* Q_EPHASH_H */ diff --git a/src/core/ddsi/include/ddsi/q_error.h b/src/core/ddsi/include/ddsi/q_error.h new file mode 100644 index 0000000..9771772 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_error.h @@ -0,0 +1,26 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_ERROR_H +#define NN_ERROR_H + +#define ERR_UNSPECIFIED -1 +#define ERR_INVALID -2 +#define ERR_OUT_OF_MEMORY -3 +#define ERR_ENTITY_EXISTS -4 +#define ERR_UNKNOWN_ENTITY -5 +#define ERR_OUT_OF_IDS -6 +#define ERR_INVALID_DATA -7 +#define ERR_BUSY -8 +#define ERR_NO_ADDRESS -9 +#define ERR_TIMEOUT -10 + +#endif /* NN_ERROR_H */ diff --git a/src/core/ddsi/include/ddsi/q_feature_check.h b/src/core/ddsi/include/ddsi/q_feature_check.h new file mode 100644 index 0000000..f7c80cf --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_feature_check.h @@ -0,0 +1,57 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +/* Feature macros: + + - ENCRYPTION: support for encryption + requires: NETWORK_PARTITIONS + + - SSM: support for source-specific multicast + requires: NETWORK_PARTIITONS + also requires platform support; SSM is silently disabled if the + platform doesn't support it + + - BANDWIDTH_LIMITING: transmit-side bandwidth limiting + requires: NETWORK_CHANNELS (for now, anyway) + + - IPV6: support for IPV6 + requires: platform support (which itself is not part of DDSI) + + - NETWORK_PARTITIONS: support for multiple network partitions + + - NETWORK_CHANNELS: support for multiple network channels + +*/ + +#ifdef DDSI_INCLUDE_ENCRYPTION + #ifndef DDSI_INCLUDE_NETWORK_PARTITIONS + #error "ENCRYPTION requires NETWORK_PARTITIONS" + #endif +#endif + +#ifdef DDSI_INCLUDE_SSM + #ifndef DDSI_INCLUDE_NETWORK_PARTITIONS + #error "SSM requires NETWORK_PARTITIONS" + #endif + + #include "os/os_socket.h" + #ifndef OS_SOCKET_HAS_SSM + #error "OS_SOCKET_HAS_SSM should be defined" + #elif ! OS_SOCKET_HAS_SSM + #undef DDSI_INCLUDE_SSM + #endif +#endif + +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING + #ifndef DDSI_INCLUDE_NETWORK_CHANNELS + #error "BANDWIDTH_LIMITING requires NETWORK_CHANNELS" + #endif +#endif diff --git a/src/core/ddsi/include/ddsi/q_freelist.h b/src/core/ddsi/include/ddsi/q_freelist.h new file mode 100644 index 0000000..783d4ac --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_freelist.h @@ -0,0 +1,77 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_FREELIST_H +#define NN_FREELIST_H + +#include "os/os.h" +#include "ddsi/sysdeps.h" + +#define FREELIST_SIMPLE 1 +#define FREELIST_ATOMIC_LIFO 2 +#define FREELIST_DOUBLE 3 + +#define FREELIST_TYPE FREELIST_DOUBLE + +#if 0 +#if HAVE_ATOMIC_LIFO +#define FREELIST_TYPE FREELIST_ATOMIC_LIFO +#else +#define FREELIST_TYPE FREELIST_SIMPLE +#endif +#endif + +#if FREELIST_TYPE == FREELIST_ATOMIC_LIFO + +struct nn_freelist { + os_atomic_lifo_t x; + os_atomic_uint32_t count; + uint32_t max; + off_t linkoff; +}; + +#elif FREELIST_TYPE == FREELIST_DOUBLE + +#define NN_FREELIST_NPAR 4 +#define NN_FREELIST_NPAR_LG2 2 +#define NN_FREELIST_MAGSIZE 256 + +struct nn_freelistM { + void *x[NN_FREELIST_MAGSIZE]; + void *next; +}; + +struct nn_freelist1 { + os_mutex lock; + uint32_t count; + struct nn_freelistM *m; +}; + +struct nn_freelist { + struct nn_freelist1 inner[NN_FREELIST_NPAR]; + os_atomic_uint32_t cc; + os_mutex lock; + struct nn_freelistM *mlist; + struct nn_freelistM *emlist; + uint32_t count; + uint32_t max; + off_t linkoff; +}; + +#endif + +void nn_freelist_init (_Out_ struct nn_freelist *fl, uint32_t max, off_t linkoff); +void nn_freelist_fini (_Inout_ _Post_invalid_ struct nn_freelist *fl, _In_ void (*free) (void *elem)); +_Check_return_ bool nn_freelist_push (_Inout_ struct nn_freelist *fl, _Inout_ _When_ (return != 0, _Post_invalid_) void *elem); +_Check_return_ _Ret_maybenull_ void *nn_freelist_pushmany (_Inout_ struct nn_freelist *fl, _Inout_opt_ _When_ (return != 0, _Post_invalid_) void *first, _Inout_opt_ _When_ (return != 0, _Post_invalid_) void *last, uint32_t n); +_Check_return_ _Ret_maybenull_ void *nn_freelist_pop (_Inout_ struct nn_freelist *fl); + +#endif /* NN_FREELIST_H */ diff --git a/src/core/ddsi/include/ddsi/q_gc.h b/src/core/ddsi/include/ddsi/q_gc.h new file mode 100644 index 0000000..3602d93 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_gc.h @@ -0,0 +1,57 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_GC_H +#define Q_GC_H + +#include "ddsi/q_thread.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +struct gcreq; +struct gcreq_queue; + +struct writer; +struct reader; +struct proxy_writer; +struct proxy_reader; + +typedef void (*gcreq_cb_t) (struct gcreq *gcreq); + +struct idx_vtime { + unsigned idx; + vtime_t vtime; +}; + +struct gcreq { + struct gcreq *next; + struct gcreq_queue *queue; + gcreq_cb_t cb; + void *arg; + unsigned nvtimes; + struct idx_vtime vtimes[1 /* really a flex ary */]; +}; + +struct gcreq_queue *gcreq_queue_new (void); +void gcreq_queue_free (struct gcreq_queue *q); + +struct gcreq *gcreq_new (struct gcreq_queue *gcreq_queue, gcreq_cb_t cb); +void gcreq_free (struct gcreq *gcreq); +void gcreq_enqueue (struct gcreq *gcreq); +int gcreq_requeue (struct gcreq *gcreq, gcreq_cb_t cb); + +#if defined (__cplusplus) +} +#endif + +#endif /* Q_GC_H */ diff --git a/src/core/ddsi/include/ddsi/q_globals.h b/src/core/ddsi/include/ddsi/q_globals.h new file mode 100644 index 0000000..90a4dd2 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_globals.h @@ -0,0 +1,310 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_GLOBALS_H +#define Q_GLOBALS_H + +#include + +#include "os/os.h" + +#include "util/ut_fibheap.h" + + +#include "ddsi/q_plist.h" +#include "ddsi/q_protocol.h" +#include "ddsi/q_nwif.h" +#include "ddsi/q_sockwaitset.h" + +#ifdef DDSI_INCLUDE_ENCRYPTION +#include "ddsi/q_security.h" /* for q_securityDecoderSet */ +#endif /* DDSI_INCLUDE_ENCRYPTION */ + +#if defined (__cplusplus) +extern "C" { +#endif + +struct nn_xmsgpool; +struct serstatepool; +struct nn_dqueue; +struct nn_reorder; +struct nn_defrag; +struct addrset; +struct xeventq; +struct gcreq_queue; +struct ephash; +struct lease; +struct ddsi_tran_conn; +struct ddsi_tran_listener; +struct ddsi_tran_factory; +struct ut_thread_pool_s; +struct debug_monitor; +struct tkmap; + +typedef struct ospl_in_addr_node { + os_sockaddr_storage addr; + struct ospl_in_addr_node *next; +} ospl_in_addr_node; + +enum recvips_mode { + RECVIPS_MODE_ALL, /* all MC capable interfaces */ + RECVIPS_MODE_ANY, /* kernel-default interface */ + RECVIPS_MODE_PREFERRED, /* selected interface only */ + RECVIPS_MODE_NONE, /* no interfaces at all */ + RECVIPS_MODE_SOME /* explicit list of interfaces; only one requiring recvips */ +}; + +#define N_LEASE_LOCKS_LG2 4 +#define N_LEASE_LOCKS ((int) (1 << N_LEASE_LOCKS_LG2)) + +struct q_globals { + volatile int terminate; + volatile int exception; + volatile int deaf; + volatile int mute; + + struct tkmap * m_tkmap; + + /* Hash tables for participants, readers, writers, proxy + participants, proxy readers and proxy writers by GUID + (guid_hash) */ + struct ephash *guid_hash; + + /* Timed events admin */ + struct xeventq *xevents; + + /* Queue for garbage collection requests */ + struct gcreq_queue *gcreq_queue; + struct nn_servicelease *servicelease; + + /* Lease junk */ + os_mutex leaseheap_lock; + os_mutex lease_locks[N_LEASE_LOCKS]; + ut_fibheap_t leaseheap; + + /* Transport factory */ + + struct ddsi_tran_factory * m_factory; + + /* Connections for multicast discovery & data, and those that correspond + to the one DDSI participant index that the DDSI2 service uses. The + DCPS participant of DDSI2 itself will be mirrored in a DDSI + participant, and in multi-socket mode that one gets its own + socket. */ + + struct ddsi_tran_conn * disc_conn_mc; + struct ddsi_tran_conn * data_conn_mc; + struct ddsi_tran_conn * disc_conn_uc; + struct ddsi_tran_conn * data_conn_uc; + + /* TCP listener */ + + struct ddsi_tran_listener * listener; + + /* Thread pool */ + + struct ut_thread_pool_s * thread_pool; + + /* Receive thread triggering: must have a socket per receive thread + because all receive threads must be triggered, even though each + receive thread takes the trigger message from the socket. With one + trigger socket, we can only have one receive thread (which enables + other optimisations that we don't currently do). */ + os_sockWaitset waitset; + + /* In many sockets mode, the receive threads maintain a local array + with participant GUIDs and sockets, participant_set_generation is + used to notify them. */ + os_atomic_uint32_t participant_set_generation; + + /* nparticipants is used primarily for limiting the number of active + participants, but also during shutdown to determine when it is + safe to stop the GC thread. */ + os_mutex participant_set_lock; + os_cond participant_set_cond; + uint32_t nparticipants; + + /* For participants without (some) built-in writers, we fall back to + this participant, which is the first one created with all + built-in writers present. It MUST be created before any in need + of it pops up! */ + struct participant *privileged_pp; + os_mutex privileged_pp_lock; + + /* GUID to be used in next call to new_participant; also protected + by privileged_pp_lock */ + struct nn_guid next_ppguid; + + /* number of up, non-loopback, IPv4/IPv6 interfaces, the index of + the selected/preferred one, and the discovered interfaces. */ + int n_interfaces; + int selected_interface; + struct nn_interface interfaces[MAX_INTERFACES]; + +#if OS_SOCKET_HAS_IPV6 + /* whether we're using an IPv6 link-local address (and therefore + only listening to multicasts on that interface) */ + int ipv6_link_local; +#endif + + /* Addressing: actual own (preferred) IP address, IP address + advertised in discovery messages (so that an external IP address on + a NAT may be advertised), and the DDSI multi-cast address. */ + enum recvips_mode recvips_mode; + struct ospl_in_addr_node *recvips; + struct in_addr extmask; + + os_sockaddr_storage ownip; + os_sockaddr_storage extip; + + /* InterfaceNo that the OwnIP is tied to */ + unsigned interfaceNo; + + /* Locators */ + + nn_locator_t loc_spdp_mc; + nn_locator_t loc_meta_mc; + nn_locator_t loc_meta_uc; + nn_locator_t loc_default_mc; + nn_locator_t loc_default_uc; + + /* + Initial discovery address set, and the current discovery address + set. These are the addresses that SPDP pings get sent to. The + as_disc_group is an FT group (only use first working). + */ + struct addrset *as_disc; + struct addrset *as_disc_group; + + /* qoslock serializes QoS changes, probably not strictly necessary, + but a lot more straightforward that way */ + os_rwlock qoslock; + + os_mutex lock; + + /* guarantees serialisation of attach/detach operations on waitsets + -- a single global lock is a bit coarse, but these operations are + rare and at initialisation time anyway */ + os_mutex attach_lock; + + /* Receive thread. (We can only has one for now, cos of the signal + trigger socket.) Receive buffer pool is per receive thread, + practical considerations led to it being a global variable + TEMPORARILY. */ + struct thread_state1 *recv_ts; + struct nn_rbufpool *rbufpool; + + /* Listener thread for connection based transports */ + struct thread_state1 *listen_ts; + + /* Flag cleared when stopping (receive threads). FIXME. */ + int rtps_keepgoing; + + /* Startup mode causes data to be treated as transient-local with + depth 1 (i.e., stored in the WHCs and regurgitated on request) to + cover the start-up delay of the discovery protocols. Because all + discovery data is shared, this is strictly a start-up issue of the + service. */ + int startup_mode; + + /* Start time of the DDSI2 service, for logging relative time stamps, + should I ever so desire. */ + nn_wctime_t tstart; + + /* Default QoSs for participant, readers and writers (needed for + eliminating default values in outgoing discovery packets, and for + supplying values for missing QoS settings in incoming discovery + packets); plus the actual QoSs needed for the builtin + endpoints. */ + nn_plist_t default_plist_pp; + nn_xqos_t default_xqos_rd; + nn_xqos_t default_xqos_wr; + nn_xqos_t default_xqos_wr_nad; + nn_xqos_t default_xqos_tp; + nn_xqos_t default_xqos_sub; + nn_xqos_t default_xqos_pub; + nn_xqos_t spdp_endpoint_xqos; + nn_xqos_t builtin_endpoint_xqos_rd; + nn_xqos_t builtin_endpoint_xqos_wr; + + /* SPDP packets get very special treatment (they're the only packets + we accept from writers we don't know) and have their very own + do-nothing defragmentation and reordering thingummies, as well as a + global mutex to in lieu of the proxy writer lock. */ + os_mutex spdp_lock; + struct nn_defrag *spdp_defrag; + struct nn_reorder *spdp_reorder; + + /* Built-in stuff other than SPDP gets funneled through the builtins + delivery queue; currently just SEDP and PMD */ + struct nn_dqueue *builtins_dqueue; + + /* Connection used by general timed-event queue for transmitting data */ + + struct ddsi_tran_conn * tev_conn; + + struct debug_monitor *debmon; + +#ifndef DDSI_INCLUDE_NETWORK_CHANNELS + uint32_t networkQueueId; + struct thread_state1 *channel_reader_ts; + + /* Application data gets its own delivery queue */ + struct nn_dqueue *user_dqueue; +#endif + + /* Transmit side: pools for the serializer & transmit messages and a + transmit queue*/ + struct serstatepool *serpool; + struct nn_xmsgpool *xmsgpool; + + /* Network ID needed by v_groupWrite -- FIXME: might as well pass it + to the receive thread instead of making it global (and that would + remove the need to include kernelModule.h) */ + uint32_t myNetworkId; + + os_mutex sendq_lock; + os_cond sendq_cond; + unsigned sendq_length; + struct nn_xpack *sendq_head; + struct nn_xpack *sendq_tail; + int sendq_stop; + struct thread_state1 *sendq_ts; + +#ifdef DDSI_INCLUDE_ENCRYPTION + /* Codecs needed for decoding incoming encrypted messages + FIXME: should be a property of the receiver thread, and pass down + while processing messages. For now made global */ + q_securityDecoderSet recvSecurityCodec; +#endif /* DDSI_INCLUDE_ENCRYPTION */ + + /* File for dumping captured packets, NULL if disabled */ + FILE *pcap_fp; + os_mutex pcap_lock; + + /* Data structure to capture power events */ + os_timePowerEvents powerEvents; + + /* Static log buffer, for those rare cases a thread calls nn_vlogb + without having its own log buffer (happens during config file + processing and for listeners, &c. */ + int static_logbuf_lock_inited; + os_mutex static_logbuf_lock; + struct logbuf static_logbuf; +}; + +extern struct q_globals OSAPI_EXPORT gv; + +#if defined (__cplusplus) +} +#endif + +#endif /* Q_GLOBALS_H */ diff --git a/src/core/ddsi/include/ddsi/q_hbcontrol.h b/src/core/ddsi/include/ddsi/q_hbcontrol.h new file mode 100644 index 0000000..72521d9 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_hbcontrol.h @@ -0,0 +1,42 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_HBCONTROL_H +#define Q_HBCONTROL_H + +#if defined (__cplusplus) +extern "C" { +#endif + +struct writer; + +struct hbcontrol { + nn_mtime_t t_of_last_write; + nn_mtime_t t_of_last_hb; + nn_mtime_t t_of_last_ackhb; + nn_mtime_t tsched; + unsigned hbs_since_last_write; + unsigned last_packetid; +}; + +void writer_hbcontrol_init (struct hbcontrol *hbc); +int64_t writer_hbcontrol_intv (const struct writer *wr, nn_mtime_t tnow); +void writer_hbcontrol_note_asyncwrite (struct writer *wr, nn_mtime_t tnow); +int writer_hbcontrol_ack_required (const struct writer *wr, nn_mtime_t tnow); +struct nn_xmsg *writer_hbcontrol_piggyback (struct writer *wr, nn_mtime_t tnow, unsigned packetid, int *hbansreq); +int writer_hbcontrol_must_send (const struct writer *wr, nn_mtime_t tnow); +struct nn_xmsg *writer_hbcontrol_create_heartbeat (struct writer *wr, nn_mtime_t tnow, int hbansreq, int issync); + +#if defined (__cplusplus) +} +#endif + +#endif /* Q_HBCONTROL_H */ diff --git a/src/core/ddsi/include/ddsi/q_inline.h b/src/core/ddsi/include/ddsi/q_inline.h new file mode 100644 index 0000000..f9ce923 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_inline.h @@ -0,0 +1,70 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_INLINE_H +#define NN_INLINE_H + +#ifdef NN_SUPPRESS_C99_INLINE + +#define NN_HAVE_C99_INLINE 0 + +#else +/* We want to inline these, but we don't want to emit an exernally + visible symbol for them and we don't want warnings if we don't use + them. + + It appears as if a plain "inline" will do just that in C99. + + In traditional GCC one had to use "extern inline" to achieve that + effect, but that will cause an externally visible symbol to be + emitted by a C99 compiler. + + Starting with GCC 4.3, GCC conforms to the C99 standard if + compiling in C99 mode, unless -fgnu89-inline is specified. It + defines __GNUC_STDC_INLINE__ if "inline"/"extern inline" behaviour + is conforming the C99 standard. + + So: GCC >= 4.3: choose between "inline" & "extern inline" based + upon __GNUC_STDC_INLINE__; for GCCs < 4.2, rely on the traditional + GCC behaiour; and for other compilers assume they behave conforming + the standard if they advertise themselves as C99 compliant (use + "inline"), and assume they do not support the inline keywords + otherwise. + + GCC when not optimizing ignores "extern inline" functions. So we + need to distinguish between optimizing & non-optimizing ... */ +#if __GNUC__ +# if __OPTIMIZE__ +# if 1 || __GNUC__ > 4 || (__GNUC__ == 4 && __GNUC_MINOR__ >= 3) +# ifdef __GNUC_STDC_INLINE__ +# define NN_HAVE_C99_INLINE 1 +# define NN_C99_INLINE inline +# else +# define NN_HAVE_C99_INLINE 1 +# define NN_C99_INLINE extern inline +# endif +# else +# define NN_HAVE_C99_INLINE 1 +# define NN_C99_INLINE extern inline +# endif +# endif +#elif __STDC_VERSION__ >= 199901L +# define NN_HAVE_C99_INLINE 1 +# define NN_C99_INLINE inline +#endif + +#endif /* NN_SUPPRESS_C99_INLINE */ + +#if ! NN_HAVE_C99_INLINE +#define NN_C99_INLINE +#endif + +#endif /* NN_INLINE_H */ diff --git a/src/core/ddsi/include/ddsi/q_lat_estim.h b/src/core/ddsi/include/ddsi/q_lat_estim.h new file mode 100644 index 0000000..42bd510 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_lat_estim.h @@ -0,0 +1,44 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_LAT_ESTIM_H +#define NN_LAT_ESTIM_H + +#include "os/os_defs.h" + +#include "ddsi/q_log.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +#define NN_LAT_ESTIM_MEDIAN_WINSZ 7 + +struct nn_lat_estim { + /* median filtering with a small window in an attempt to remove the + worst outliers */ + int index; + float window[NN_LAT_ESTIM_MEDIAN_WINSZ]; + /* simple alpha filtering for smoothing */ + float smoothed; +}; + +void nn_lat_estim_init (struct nn_lat_estim *le); +void nn_lat_estim_fini (struct nn_lat_estim *le); +void nn_lat_estim_update (struct nn_lat_estim *le, int64_t est); +double nn_lat_estim_current (const struct nn_lat_estim *le); +int nn_lat_estim_log (logcat_t logcat, const char *tag, const struct nn_lat_estim *le); + +#if defined (__cplusplus) +} +#endif + +#endif /* NN_LAT_ESTIM_H */ diff --git a/src/core/ddsi/include/ddsi/q_lease.h b/src/core/ddsi/include/ddsi/q_lease.h new file mode 100644 index 0000000..bbd519f --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_lease.h @@ -0,0 +1,42 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_LEASE_H +#define Q_LEASE_H + +#include "ddsi/q_time.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +struct receiver_state; +struct participant; +struct lease; +struct entity_common; +struct thread_state1; + +void lease_management_init (void); +void lease_management_term (void); +struct lease *lease_new (nn_etime_t texpire, int64_t tdur, struct entity_common *e); +void lease_register (struct lease *l); +void lease_free (struct lease *l); +void lease_renew (struct lease *l, nn_etime_t tnow); +void lease_set_expiry (struct lease *l, nn_etime_t when); +void check_and_handle_lease_expiration (struct thread_state1 *self, nn_etime_t tnow); + +void handle_PMD (const struct receiver_state *rst, nn_wctime_t timestamp, unsigned statusinfo, const void *vdata, unsigned len); + +#if defined (__cplusplus) +} +#endif + +#endif /* Q_LEASE_H */ diff --git a/src/core/ddsi/include/ddsi/q_log.h b/src/core/ddsi/include/ddsi/q_log.h new file mode 100644 index 0000000..229df22 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_log.h @@ -0,0 +1,85 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_LOG_H +#define NN_LOG_H + +#include + +#include "os/os.h" +#include "ddsi/q_time.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +#define LC_FATAL 1u +#define LC_ERROR 2u +#define LC_WARNING 4u +#define LC_CONFIG 8u +#define LC_INFO 16u +#define LC_DISCOVERY 32u +#define LC_DATA 64u +#define LC_TRACE 128u +#define LC_RADMIN 256u +#define LC_TIMING 512u +#define LC_TRAFFIC 1024u +#define LC_TOPIC 2048u +#define LC_TCP 4096u +#define LC_PLIST 8192u +#define LC_WHC 16384u +#define LC_THROTTLE 32768u +#define LC_ALLCATS (LC_FATAL | LC_ERROR | LC_WARNING | LC_CONFIG | LC_INFO | LC_DISCOVERY | LC_DATA | LC_TRACE | LC_TIMING | LC_TRAFFIC | LC_TCP | LC_THROTTLE) + +typedef unsigned logcat_t; + +typedef struct logbuf { + char buf[2048]; + size_t bufsz; + size_t pos; + nn_wctime_t tstamp; +} *logbuf_t; + +logbuf_t logbuf_new (void); +void logbuf_init (logbuf_t lb); +void logbuf_free (logbuf_t lb); + +int nn_vlog (logcat_t cat, const char *fmt, va_list ap); +OSAPI_EXPORT int nn_log (_In_ logcat_t cat, _In_z_ _Printf_format_string_ const char *fmt, ...) __attribute_format__((printf,2,3)); +OSAPI_EXPORT int nn_trace (_In_z_ _Printf_format_string_ const char *fmt, ...) __attribute_format__((printf,1,2)); +void nn_log_set_tstamp (nn_wctime_t tnow); + +#define TRACE(args) ((config.enabled_logcats & LC_TRACE) ? (nn_trace args) : 0) + +#define LOG_THREAD_CPUTIME(guard) do { \ + if (config.enabled_logcats & LC_TIMING) \ + { \ + nn_mtime_t tnowlt = now_mt (); \ + if (tnowlt.v >= (guard).v) \ + { \ + int64_t ts = get_thread_cputime (); \ + nn_log (LC_TIMING, "thread_cputime %d.%09d\n", \ + (int) (ts / T_SECOND), (int) (ts % T_SECOND)); \ + (guard).v = tnowlt.v + T_SECOND; \ + } \ + } \ + } while (0) + + +#define NN_WARNING(fmt,...) nn_log (LC_WARNING, (" " fmt),##__VA_ARGS__) +#define NN_ERROR(fmt,...) nn_log (LC_ERROR, (" " fmt),##__VA_ARGS__) +#define NN_FATAL(fmt,...) nn_log (LC_FATAL, (" " fmt),##__VA_ARGS__) + +#if defined (__cplusplus) +} +#endif + +#endif /* NN_LOG_H */ diff --git a/src/core/ddsi/include/ddsi/q_misc.h b/src/core/ddsi/include/ddsi/q_misc.h new file mode 100644 index 0000000..5d58b97 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_misc.h @@ -0,0 +1,48 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_MISC_H +#define NN_MISC_H + +#include "ddsi/q_protocol.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +struct v_gid_s; +struct nn_guid; + +int vendor_is_opensplice (nn_vendorid_t vid); +int vendor_is_rti (nn_vendorid_t vendor); +int vendor_is_twinoaks (nn_vendorid_t vendor); +int vendor_is_prismtech (nn_vendorid_t vendor); +int vendor_is_cloud (nn_vendorid_t vendor); +int is_own_vendor (nn_vendorid_t vendor); +unsigned char normalize_data_datafrag_flags (const SubmessageHeader_t *smhdr, int datafrag_as_data); + +seqno_t fromSN (const nn_sequence_number_t sn); +nn_sequence_number_t toSN (seqno_t); + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS +int WildcardOverlap(char * p1, char * p2); +#endif + +int ddsi2_patmatch (const char *pat, const char *str); + + +uint32_t crc32_calc (const void *buf, uint32_t length); + +#if defined (__cplusplus) +} +#endif + +#endif /* NN_MISC_H */ diff --git a/src/core/ddsi/include/ddsi/q_nwif.h b/src/core/ddsi/include/ddsi/q_nwif.h new file mode 100644 index 0000000..3556f15 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_nwif.h @@ -0,0 +1,62 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_NWIF_H +#define Q_NWIF_H + +#include "os/os_socket.h" +#include "ddsi/q_protocol.h" /* for nn_locator_t */ + +#if defined (__cplusplus) +extern "C" { +#endif + +#define INET6_ADDRSTRLEN_EXTENDED (INET6_ADDRSTRLEN + 8) /* 13: '[' + ']' + ':' + PORT */ +#define MAX_INTERFACES 128 +struct nn_interface { + os_sockaddr_storage addr; + os_sockaddr_storage netmask; + unsigned if_index; + unsigned mc_capable: 1; + unsigned point_to_point: 1; + char *name; +}; + +struct nn_group_membership; + +void nn_loc_to_address (os_sockaddr_storage *dst, const nn_locator_t *src); +void nn_address_to_loc (nn_locator_t *dst, const os_sockaddr_storage *src, int32_t kind); + +char *sockaddr_to_string_no_port (char addrbuf[INET6_ADDRSTRLEN_EXTENDED], const os_sockaddr_storage *src); +char *sockaddr_to_string_with_port (char addrbuf[INET6_ADDRSTRLEN_EXTENDED], const os_sockaddr_storage *src); +char *locator_to_string_no_port (char addrbuf[INET6_ADDRSTRLEN_EXTENDED], const nn_locator_t *loc); +char *locator_to_string_with_port (char addrbuf[INET6_ADDRSTRLEN_EXTENDED], const nn_locator_t *loc); +void print_sockerror (const char *msg); +int make_socket (os_socket *socket, unsigned short port, bool stream, bool reuse); +int find_own_ip (const char *requested_address); +unsigned short get_socket_port (os_socket socket); +struct nn_group_membership *new_group_membership (void); +void free_group_membership (struct nn_group_membership *mship); +int join_mcgroups (struct nn_group_membership *mship, os_socket socket, const os_sockaddr_storage *srcip, const os_sockaddr_storage *mcip); +int leave_mcgroups (struct nn_group_membership *mship, os_socket socket, const os_sockaddr_storage *srcip, const os_sockaddr_storage *mcip); +void sockaddr_set_port (os_sockaddr_storage *addr, unsigned short port); +unsigned short sockaddr_get_port (const os_sockaddr_storage *addr); +unsigned sockaddr_to_hopefully_unique_uint32 (const os_sockaddr_storage *src); + +#if defined (__cplusplus) +} +#endif + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS +void set_socket_diffserv (os_socket sock, int diffserv); +#endif + +#endif /* Q_NWIF_H */ diff --git a/src/core/ddsi/include/ddsi/q_pcap.h b/src/core/ddsi/include/ddsi/q_pcap.h new file mode 100644 index 0000000..d774dff --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_pcap.h @@ -0,0 +1,49 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_PCAP_H +#define Q_PCAP_H + +#include +#include "ddsi/q_time.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +struct msghdr; + +FILE * new_pcap_file (const char *name); + +void write_pcap_received +( + FILE * fp, + nn_wctime_t tstamp, + const os_sockaddr_storage * src, + const os_sockaddr_storage * dst, + unsigned char * buf, + size_t sz +); + +void write_pcap_sent +( + FILE * fp, + nn_wctime_t tstamp, + const os_sockaddr_storage * src, + const struct msghdr * hdr, + size_t sz +); + +#if defined (__cplusplus) +} +#endif + +#endif /* Q_PCAP_H */ diff --git a/src/core/ddsi/include/ddsi/q_plist.h b/src/core/ddsi/include/ddsi/q_plist.h new file mode 100644 index 0000000..eadb552 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_plist.h @@ -0,0 +1,241 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_PLIST_H +#define NN_PLIST_H + +#include "ddsi/q_feature_check.h" +#include "ddsi/q_xqos.h" + + +#if defined (__cplusplus) +extern "C" { +#endif + + +#define PP_PROTOCOL_VERSION ((uint64_t)1 << 0) +#define PP_VENDORID ((uint64_t)1 << 1) +#define PP_UNICAST_LOCATOR ((uint64_t)1 << 2) +#define PP_MULTICAST_LOCATOR ((uint64_t)1 << 3) +#define PP_DEFAULT_UNICAST_LOCATOR ((uint64_t)1 << 4) +#define PP_DEFAULT_MULTICAST_LOCATOR ((uint64_t)1 << 5) +#define PP_METATRAFFIC_UNICAST_LOCATOR ((uint64_t)1 << 6) +#define PP_METATRAFFIC_MULTICAST_LOCATOR ((uint64_t)1 << 7) +#define PP_EXPECTS_INLINE_QOS ((uint64_t)1 << 8) +#define PP_PARTICIPANT_MANUAL_LIVELINESS_COUNT ((uint64_t)1 << 9) +#define PP_PARTICIPANT_BUILTIN_ENDPOINTS ((uint64_t)1 << 10) +#define PP_PARTICIPANT_LEASE_DURATION ((uint64_t)1 << 11) +#define PP_CONTENT_FILTER_PROPERTY ((uint64_t)1 << 12) +#define PP_PARTICIPANT_GUID ((uint64_t)1 << 13) +#define PP_PARTICIPANT_ENTITYID ((uint64_t)1 << 14) +#define PP_GROUP_GUID ((uint64_t)1 << 15) +#define PP_GROUP_ENTITYID ((uint64_t)1 << 16) +#define PP_BUILTIN_ENDPOINT_SET ((uint64_t)1 << 17) +#define PP_PROPERTIES ((uint64_t)1 << 18) +#define PP_TYPE_MAX_SIZE_SERIALIZED ((uint64_t)1 << 19) +#define PP_ENTITY_NAME ((uint64_t)1 << 20) +#define PP_KEYHASH ((uint64_t)1 << 21) +#define PP_STATUSINFO ((uint64_t)1 << 22) +#define PP_ORIGINAL_WRITER_INFO ((uint64_t)1 << 23) +#define PP_ENDPOINT_GUID ((uint64_t)1 << 24) +#define PP_PRISMTECH_WRITER_INFO ((uint64_t)1 << 25) +#define PP_PRISMTECH_PARTICIPANT_VERSION_INFO ((uint64_t)1 << 26) +#define PP_PRISMTECH_NODE_NAME ((uint64_t)1 << 27) +#define PP_PRISMTECH_EXEC_NAME ((uint64_t)1 << 28) +#define PP_PRISMTECH_PROCESS_ID ((uint64_t)1 << 29) +#define PP_PRISMTECH_SERVICE_TYPE ((uint64_t)1 << 30) +#define PP_PRISMTECH_WATCHDOG_SCHEDULING ((uint64_t)1 << 31) +#define PP_PRISMTECH_LISTENER_SCHEDULING ((uint64_t)1 << 32) +#define PP_PRISMTECH_BUILTIN_ENDPOINT_SET ((uint64_t)1 << 33) +#define PP_PRISMTECH_TYPE_DESCRIPTION ((uint64_t)1 << 34) +#define PP_COHERENT_SET ((uint64_t)1 << 37) +#define PP_PRISMTECH_EOTINFO ((uint64_t)1 << 38) +#ifdef DDSI_INCLUDE_SSM +#define PP_READER_FAVOURS_SSM ((uint64_t)1 << 39) +#endif +#define PP_RTI_TYPECODE ((uint64_t)1 << 40) +/* Security extensions. */ +#define PP_IDENTITY_TOKEN ((uint64_t)1 << 41) +#define PP_PERMISSIONS_TOKEN ((uint64_t)1 << 42) +/* Set for unrecognized parameters that are in the reserved space or + in our own vendor-specific space that have the + PID_UNRECOGNIZED_INCOMPATIBLE_FLAG set (see DDSI 2.1 9.6.2.2.1) */ +#define PP_INCOMPATIBLE ((uint64_t)1 << 63) + +#define NN_PRISMTECH_PARTICIPANT_VERSION_INFO_FIXED_CDRSIZE (24) + +#define NN_PRISMTECH_FL_KERNEL_SEQUENCE_NUMBER (1u << 0) +#define NN_PRISMTECH_FL_DISCOVERY_INCLUDES_GID (1u << 1) +#define NN_PRISMTECH_FL_PTBES_FIXED_0 (1u << 2) +#define NN_PRISMTECH_FL_DDSI2_PARTICIPANT_FLAG (1u << 3) +#define NN_PRISMTECH_FL_PARTICIPANT_IS_DDSI2 (1u << 4) +#define NN_PRISMTECH_FL_MINIMAL_BES_MODE (1u << 5) +#define NN_PRISMTECH_FL_SUPPORTS_STATUSINFOX (1u << 5) +/* SUPPORTS_STATUSINFOX: when set, also means any combination of + write/unregister/dispose supported */ + +/* For locators one could patch the received message data to create + singly-linked lists (parameter header -> offset of next entry in + list relative to current), allowing aliasing of the data. But that + requires modifying the data. For string sequences the length does + the same thing. */ +struct nn_locators_one { + struct nn_locators_one *next; + nn_locator_t loc; +}; + +typedef struct nn_locators { + int n; + struct nn_locators_one *first; + struct nn_locators_one *last; +} nn_locators_t; + +typedef unsigned nn_ipv4address_t; + +typedef unsigned nn_port_t; + +typedef struct nn_keyhash { + unsigned char value[16]; +} nn_keyhash_t; + + +#ifdef DDSI_INCLUDE_SSM +typedef struct nn_reader_favours_ssm { + unsigned state; /* default is false */ +} nn_reader_favours_ssm_t; +#endif + +typedef struct nn_dataholder +{ + char *class_id; + nn_propertyseq_t properties; + nn_binarypropertyseq_t binary_properties; +} nn_dataholder_t; + +typedef nn_dataholder_t nn_token_t; + + +typedef struct nn_prismtech_participant_version_info +{ + unsigned version; + unsigned flags; + unsigned unused[3]; + char *internals; +} nn_prismtech_participant_version_info_t; + +typedef struct nn_prismtech_eotgroup_tid { + nn_entityid_t writer_entityid; + uint32_t transactionId; +} nn_prismtech_eotgroup_tid_t; + +typedef struct nn_prismtech_eotinfo { + uint32_t transactionId; + uint32_t n; + nn_prismtech_eotgroup_tid_t *tids; +} nn_prismtech_eotinfo_t; + +typedef struct nn_plist { + uint64_t present; + uint64_t aliased; + int unalias_needs_bswap; + + nn_xqos_t qos; + + nn_protocol_version_t protocol_version; + nn_vendorid_t vendorid; + nn_locators_t unicast_locators; + nn_locators_t multicast_locators; + nn_locators_t default_unicast_locators; + nn_locators_t default_multicast_locators; + nn_locators_t metatraffic_unicast_locators; + nn_locators_t metatraffic_multicast_locators; + + unsigned char expects_inline_qos; + nn_count_t participant_manual_liveliness_count; + unsigned participant_builtin_endpoints; + nn_duration_t participant_lease_duration; + /* nn_content_filter_property_t content_filter_property; */ + nn_guid_t participant_guid; + nn_guid_t endpoint_guid; + nn_guid_t group_guid; +#if 0 /* reserved, rather than NIY */ + nn_entityid_t participant_entityid; + nn_entityid_t group_entityid; +#endif + unsigned builtin_endpoint_set; + unsigned prismtech_builtin_endpoint_set; + /* int type_max_size_serialized; */ + char *entity_name; + nn_keyhash_t keyhash; + unsigned statusinfo; + nn_prismtech_participant_version_info_t prismtech_participant_version_info; + char *node_name; + char *exec_name; + unsigned char is_service; + unsigned service_type; + unsigned process_id; + char *type_description; + nn_sequence_number_t coherent_set_seqno; + nn_prismtech_eotinfo_t eotinfo; + nn_token_t identity_token; + nn_token_t permissions_token; +#ifdef DDSI_INCLUDE_SSM + nn_reader_favours_ssm_t reader_favours_ssm; +#endif +} nn_plist_t; + + +/***/ + + +typedef struct nn_plist_src { + nn_protocol_version_t protocol_version; + nn_vendorid_t vendorid; + int encoding; + unsigned char *buf; + size_t bufsz; +} nn_plist_src_t; + +void nn_plist_init_empty (nn_plist_t *dest); +void nn_plist_mergein_missing (nn_plist_t *a, const nn_plist_t *b); +void nn_plist_copy (nn_plist_t *dst, const nn_plist_t *src); +nn_plist_t *nn_plist_dup (const nn_plist_t *src); +int nn_plist_init_frommsg (nn_plist_t *dest, char **nextafterplist, uint64_t pwanted, uint64_t qwanted, const nn_plist_src_t *src); +void nn_plist_fini (nn_plist_t *ps); +void nn_plist_addtomsg (struct nn_xmsg *m, const nn_plist_t *ps, uint64_t pwanted, uint64_t qwanted); +int nn_plist_init_default_participant (nn_plist_t *plist); + +int validate_history_qospolicy (const nn_history_qospolicy_t *q); +int validate_durability_qospolicy (const nn_durability_qospolicy_t *q); +int validate_resource_limits_qospolicy (const nn_resource_limits_qospolicy_t *q); +int validate_history_and_resource_limits (const nn_history_qospolicy_t *qh, const nn_resource_limits_qospolicy_t *qr); +int validate_durability_service_qospolicy (const nn_durability_service_qospolicy_t *q); +int validate_liveliness_qospolicy (const nn_liveliness_qospolicy_t *q); +int validate_destination_order_qospolicy (const nn_destination_order_qospolicy_t *q); +int validate_ownership_qospolicy (const nn_ownership_qospolicy_t *q); +int validate_ownership_strength_qospolicy (const nn_ownership_strength_qospolicy_t *q); +int validate_presentation_qospolicy (const nn_presentation_qospolicy_t *q); +int validate_transport_priority_qospolicy (const nn_transport_priority_qospolicy_t *q); +int validate_reader_data_lifecycle (const nn_reader_data_lifecycle_qospolicy_t *q); +int validate_duration (const nn_duration_t *d); + + +struct nn_rmsg; +struct nn_rsample_info; +struct nn_rdata; + +unsigned char *nn_plist_quickscan (struct nn_rsample_info *dest, const struct nn_rmsg *rmsg, const nn_plist_src_t *src); + +#if defined (__cplusplus) +} +#endif + +#endif /* NN_PLIST_H */ diff --git a/src/core/ddsi/include/ddsi/q_protocol.h b/src/core/ddsi/include/ddsi/q_protocol.h new file mode 100644 index 0000000..dad83f8 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_protocol.h @@ -0,0 +1,484 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_PROTOCOL_H +#define NN_PROTOCOL_H + +#include "os/os.h" +#include "ddsi/q_feature_check.h" + +#if OS_ENDIANNESS == OS_LITTLE_ENDIAN +#define PLATFORM_IS_LITTLE_ENDIAN 1 +#else +#define PLATFORM_IS_LITTLE_ENDIAN 0 +#endif + +#include "ddsi/q_rtps.h" +#include "ddsi/q_time.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +typedef struct { + unsigned char id[4]; +} nn_protocolid_t; +typedef struct { + int high; + unsigned low; +} nn_sequence_number_t; +#define NN_SEQUENCE_NUMBER_UNKNOWN_HIGH -1 +#define NN_SEQUENCE_NUMBER_UNKNOWN_LOW 0 +#define NN_SEQUENCE_NUMBER_UNKNOWN ((seqno_t) (((uint64_t)NN_SEQUENCE_NUMBER_UNKNOWN_HIGH << 32) | NN_SEQUENCE_NUMBER_UNKNOWN_LOW)) +typedef struct nn_sequence_number_set { + nn_sequence_number_t bitmap_base; + unsigned numbits; + unsigned bits[1]; +} nn_sequence_number_set_t; /* Why strict C90? zero-length/flexible array members are far nicer */ +/* SequenceNumberSet size is base (2 words) + numbits (1 word) + + bitmap ((numbits+31)/32 words), and this at 4 bytes/word */ +#define NN_SEQUENCE_NUMBER_SET_BITS_SIZE(numbits) ((unsigned) (4 * (((numbits) + 31) / 32))) +#define NN_SEQUENCE_NUMBER_SET_SIZE(numbits) (offsetof (nn_sequence_number_set_t, bits) + NN_SEQUENCE_NUMBER_SET_BITS_SIZE (numbits)) +typedef unsigned nn_fragment_number_t; +typedef struct nn_fragment_number_set { + nn_fragment_number_t bitmap_base; + unsigned numbits; + unsigned bits[1]; +} nn_fragment_number_set_t; +/* FragmentNumberSet size is base (2 words) + numbits (1 word) + + bitmap ((numbits+31)/32 words), and this at 4 bytes/word */ +#define NN_FRAGMENT_NUMBER_SET_BITS_SIZE(numbits) ((unsigned) (4 * (((numbits) + 31) / 32))) +#define NN_FRAGMENT_NUMBER_SET_SIZE(numbits) (offsetof (nn_fragment_number_set_t, bits) + NN_FRAGMENT_NUMBER_SET_BITS_SIZE (numbits)) +typedef int nn_count_t; +#define DDSI_COUNT_MIN (-2147483647 - 1) +#define DDSI_COUNT_MAX (2147483647) +/* address field in locator maintained in network byte order, the rest + in host (yes: that's a FIXME) */ +typedef struct { + int32_t kind; + unsigned port; + unsigned char address[16]; +} nn_locator_t; + +typedef struct nn_udpv4mcgen_address { + /* base IPv4 MC address is ipv4, host bits are bits base .. base+count-1, this machine is bit idx */ + struct in_addr ipv4; + unsigned char base; + unsigned char count; + unsigned char idx; /* must be last: then sorting will put them consecutively */ +} nn_udpv4mcgen_address_t; + + +struct cdrstring { + unsigned length; + unsigned char contents[1]; /* C90 does not support flex. array members */ +}; + +#define NN_STATUSINFO_DISPOSE 0x1u +#define NN_STATUSINFO_UNREGISTER 0x2u +#define NN_STATUSINFO_STANDARDIZED (NN_STATUSINFO_DISPOSE | NN_STATUSINFO_UNREGISTER) +#define NN_STATUSINFO_OSPL_AUTO 0x10000000u /* OSPL extension, not on the wire */ +#define NN_STATUSINFOX_OSPL_AUTO 0x1 /* statusinfo word 2, OSPL L_AUTO flag on the wire */ + +#define NN_GUID_PREFIX_UNKNOWN_INITIALIZER {{0,0,0,0, 0,0,0,0, 0,0,0,0}} + +#define NN_DISC_BUILTIN_ENDPOINT_PARTICIPANT_ANNOUNCER (1u << 0) +#define NN_DISC_BUILTIN_ENDPOINT_PARTICIPANT_DETECTOR (1u << 1) +#define NN_DISC_BUILTIN_ENDPOINT_PUBLICATION_ANNOUNCER (1u << 2) +#define NN_DISC_BUILTIN_ENDPOINT_PUBLICATION_DETECTOR (1u << 3) +#define NN_DISC_BUILTIN_ENDPOINT_SUBSCRIPTION_ANNOUNCER (1u << 4) +#define NN_DISC_BUILTIN_ENDPOINT_SUBSCRIPTION_DETECTOR (1u << 5) +#define NN_DISC_BUILTIN_ENDPOINT_PARTICIPANT_PROXY_ANNOUNCER (1u << 6) /* undefined meaning */ +#define NN_DISC_BUILTIN_ENDPOINT_PARTICIPANT_PROXY_DETECTOR (1u << 7) /* undefined meaning */ +#define NN_DISC_BUILTIN_ENDPOINT_PARTICIPANT_STATE_ANNOUNCER (1u << 8) /* undefined meaning */ +#define NN_DISC_BUILTIN_ENDPOINT_PARTICIPANT_STATE_DETECTOR (1u << 9) /* undefined meaning */ +#define NN_BUILTIN_ENDPOINT_PARTICIPANT_MESSAGE_DATA_WRITER (1u << 10) +#define NN_BUILTIN_ENDPOINT_PARTICIPANT_MESSAGE_DATA_READER (1u << 11) +#define NN_DISC_BUILTIN_ENDPOINT_TOPIC_ANNOUNCER (1u << 12) +#define NN_DISC_BUILTIN_ENDPOINT_TOPIC_DETECTOR (1u << 13) + +/* PrismTech extensions: */ +#define NN_DISC_BUILTIN_ENDPOINT_CM_PARTICIPANT_WRITER (1u << 0) +#define NN_DISC_BUILTIN_ENDPOINT_CM_PARTICIPANT_READER (1u << 1) +#define NN_DISC_BUILTIN_ENDPOINT_CM_PUBLISHER_WRITER (1u << 2) +#define NN_DISC_BUILTIN_ENDPOINT_CM_PUBLISHER_READER (1u << 3) +#define NN_DISC_BUILTIN_ENDPOINT_CM_SUBSCRIBER_WRITER (1u << 4) +#define NN_DISC_BUILTIN_ENDPOINT_CM_SUBSCRIBER_READER (1u << 5) + +#define NN_LOCATOR_KIND_INVALID -1 +#define NN_LOCATOR_KIND_RESERVED 0 +#define NN_LOCATOR_KIND_UDPv4 1 +#define NN_LOCATOR_KIND_UDPv6 2 +#define NN_LOCATOR_KIND_TCPv4 4 +#define NN_LOCATOR_KIND_TCPv6 8 +#define NN_LOCATOR_KIND_UDPv4MCGEN 0x4fff0000 +#define NN_LOCATOR_PORT_INVALID 0 + +#define NN_VENDORID_UNKNOWN {{ 0x00, 0x00 }} +#define NN_VENDORID_RTI {{ 0x01, 0x01 }} +#define NN_VENDORID_PRISMTECH_OSPL {{ 0x01, 0x02 }} +#define NN_VENDORID_OCI {{ 0x01, 0x03 }} +#define NN_VENDORID_MILSOFT {{ 0x01, 0x04 }} +#define NN_VENDORID_KONGSBERG {{ 0x01, 0x05 }} +#define NN_VENDORID_TWINOAKS {{ 0x01, 0x06 }} +#define NN_VENDORID_LAKOTA {{ 0x01, 0x07 }} +#define NN_VENDORID_ICOUP {{ 0x01, 0x08 }} +#define NN_VENDORID_ETRI {{ 0x01, 0x09 }} +#define NN_VENDORID_RTI_MICRO {{ 0x01, 0x0a }} +#define NN_VENDORID_PRISMTECH_JAVA {{ 0x01, 0x0b }} +#define NN_VENDORID_PRISMTECH_GATEWAY {{ 0x01, 0x0c }} +#define NN_VENDORID_PRISMTECH_LITE {{ 0x01, 0x0d }} +#define NN_VENDORID_TECHNICOLOR {{ 0x01, 0x0e }} +#define NN_VENDORID_EPROSIMA {{ 0x01, 0x0f }} +#define NN_VENDORID_PRISMTECH_CLOUD {{ 0x01, 0x20 }} +#define NN_VENDORID_ECLIPSE_CYCLONEDDS {{ 0x01, 0x0d }} // Since CYCLONEDDS has no owner yet, it uses the same VENDORID as LITE + +#define MY_VENDOR_ID NN_VENDORID_ECLIPSE_CYCLONEDDS + +/* Only one specific version is grokked */ +#define RTPS_MAJOR 2 +#define RTPS_MINOR 1 + +typedef struct Header { + nn_protocolid_t protocol; + nn_protocol_version_t version; + nn_vendorid_t vendorid; + nn_guid_prefix_t guid_prefix; +} Header_t; +#define NN_PROTOCOLID_INITIALIZER {{ 'R','T','P','S' }} +#define NN_PROTOCOL_VERSION_INITIALIZER { RTPS_MAJOR, RTPS_MINOR } +#define NN_VENDORID_INITIALIER MY_VENDOR_ID +#define NN_HEADER_INITIALIZER { NN_PROTOCOLID_INITIALIZER, NN_PROTOCOL_VERSION_INITIALIZER, NN_VENDORID_INITIALIER, NN_GUID_PREFIX_UNKNOWN_INITIALIZER } +#define RTPS_MESSAGE_HEADER_SIZE (sizeof (Header_t)) + +typedef struct SubmessageHeader { + unsigned char submessageId; + unsigned char flags; + unsigned short octetsToNextHeader; +} SubmessageHeader_t; +#define RTPS_SUBMESSAGE_HEADER_SIZE (sizeof (SubmessageHeader_t)) +#define SMFLAG_ENDIANNESS 0x01u + +typedef enum SubmessageKind { + SMID_PAD = 0x01, + SMID_ACKNACK = 0x06, + SMID_HEARTBEAT = 0x07, + SMID_GAP = 0x08, + SMID_INFO_TS = 0x09, + SMID_INFO_SRC = 0x0c, + SMID_INFO_REPLY_IP4 = 0x0d, + SMID_INFO_DST = 0x0e, + SMID_INFO_REPLY = 0x0f, + SMID_NACK_FRAG = 0x12, + SMID_HEARTBEAT_FRAG = 0x13, + SMID_DATA = 0x15, + SMID_DATA_FRAG = 0x16, + /* vendor-specific sub messages (0x80 .. 0xff) */ + SMID_PT_INFO_CONTAINER = 0x80, + SMID_PT_MSG_LEN = 0x81, + SMID_PT_ENTITY_ID = 0x82 +} SubmessageKind_t; + +typedef struct InfoTimestamp { + SubmessageHeader_t smhdr; + nn_ddsi_time_t time; +} InfoTimestamp_t; + +typedef struct EntityId { + SubmessageHeader_t smhdr; + nn_entityid_t entityid; +} EntityId_t; + +typedef struct InfoDST { + SubmessageHeader_t smhdr; + nn_guid_prefix_t guid_prefix; +} InfoDST_t; + +typedef struct InfoSRC { + SubmessageHeader_t smhdr; + unsigned unused; + nn_protocol_version_t version; + nn_vendorid_t vendorid; + nn_guid_prefix_t guid_prefix; +} InfoSRC_t; + +#if PLATFORM_IS_LITTLE_ENDIAN +#define PL_CDR_BE 0x0200u +#define PL_CDR_LE 0x0300u +#else +#define PL_CDR_BE 0x0002u +#define PL_CDR_LE 0x0003u +#endif + +typedef unsigned short nn_parameterid_t; /* spec says short */ +typedef struct nn_parameter { + nn_parameterid_t parameterid; + unsigned short length; /* spec says short */ + /* char value[]; O! how I long for C99 */ +} nn_parameter_t; + +typedef struct Data_DataFrag_common { + SubmessageHeader_t smhdr; + unsigned short extraFlags; + unsigned short octetsToInlineQos; + nn_entityid_t readerId; + nn_entityid_t writerId; + nn_sequence_number_t writerSN; +} Data_DataFrag_common_t; + +typedef struct Data { + Data_DataFrag_common_t x; +} Data_t; +#define DATA_FLAG_INLINE_QOS 0x02u +#define DATA_FLAG_DATAFLAG 0x04u +#define DATA_FLAG_KEYFLAG 0x08u + +typedef struct DataFrag { + Data_DataFrag_common_t x; + nn_fragment_number_t fragmentStartingNum; + unsigned short fragmentsInSubmessage; + unsigned short fragmentSize; + unsigned sampleSize; +} DataFrag_t; +#define DATAFRAG_FLAG_INLINE_QOS 0x02u +#define DATAFRAG_FLAG_KEYFLAG 0x04u + +typedef struct MsgLen { + SubmessageHeader_t smhdr; + uint32_t length; +} MsgLen_t; + +typedef struct AckNack { + SubmessageHeader_t smhdr; + nn_entityid_t readerId; + nn_entityid_t writerId; + nn_sequence_number_set_t readerSNState; + /* nn_count_t count; */ +} AckNack_t; +#define ACKNACK_FLAG_FINAL 0x02u +#define ACKNACK_SIZE(numbits) (offsetof (AckNack_t, readerSNState) + NN_SEQUENCE_NUMBER_SET_SIZE (numbits) + 4) +#define ACKNACK_SIZE_MAX ACKNACK_SIZE (256u) + +typedef struct Gap { + SubmessageHeader_t smhdr; + nn_entityid_t readerId; + nn_entityid_t writerId; + nn_sequence_number_t gapStart; + nn_sequence_number_set_t gapList; +} Gap_t; +#define GAP_SIZE(numbits) (offsetof (Gap_t, gapList) + NN_SEQUENCE_NUMBER_SET_SIZE (numbits)) +#define GAP_SIZE_MAX GAP_SIZE (256u) + +typedef struct InfoTS { + SubmessageHeader_t smhdr; + nn_ddsi_time_t time; +} InfoTS_t; +#define INFOTS_INVALIDATE_FLAG 0x2u + +typedef struct Heartbeat { + SubmessageHeader_t smhdr; + nn_entityid_t readerId; + nn_entityid_t writerId; + nn_sequence_number_t firstSN; + nn_sequence_number_t lastSN; + nn_count_t count; +} Heartbeat_t; +#define HEARTBEAT_FLAG_FINAL 0x02u +#define HEARTBEAT_FLAG_LIVELINESS 0x04u + +typedef struct HeartbeatFrag { + SubmessageHeader_t smhdr; + nn_entityid_t readerId; + nn_entityid_t writerId; + nn_sequence_number_t writerSN; + nn_fragment_number_t lastFragmentNum; + nn_count_t count; +} HeartbeatFrag_t; + +typedef struct NackFrag { + SubmessageHeader_t smhdr; + nn_entityid_t readerId; + nn_entityid_t writerId; + nn_sequence_number_t writerSN; + nn_fragment_number_set_t fragmentNumberState; + /* nn_count_t count; */ +} NackFrag_t; +#define NACKFRAG_SIZE(numbits) (offsetof (NackFrag_t, fragmentNumberState) + NN_FRAGMENT_NUMBER_SET_SIZE (numbits) + 4) +#define NACKFRAG_SIZE_MAX NACKFRAG_SIZE (256u) + +typedef struct PT_InfoContainer { + SubmessageHeader_t smhdr; + uint32_t id; +} PT_InfoContainer_t; +#define PTINFO_ID_ENCRYPT (0x01u) + +typedef union Submessage { + SubmessageHeader_t smhdr; + AckNack_t acknack; + Data_t data; + DataFrag_t datafrag; + InfoTS_t infots; + InfoDST_t infodst; + InfoSRC_t infosrc; + Heartbeat_t heartbeat; + HeartbeatFrag_t heartbeatfrag; + Gap_t gap; + NackFrag_t nackfrag; + PT_InfoContainer_t pt_infocontainer; +} Submessage_t; + +typedef struct ParticipantMessageData { + nn_guid_prefix_t participantGuidPrefix; + unsigned kind; /* really 4 octets */ + unsigned length; + char value[1 /* length */]; +} ParticipantMessageData_t; +#define PARTICIPANT_MESSAGE_DATA_KIND_UNKNOWN 0x0u +#define PARTICIPANT_MESSAGE_DATA_KIND_AUTOMATIC_LIVELINESS_UPDATE 0x1u +#define PARTICIPANT_MESSAGE_DATA_KIND_MANUAL_LIVELINESS_UPDATE 0x2u +#define PARTICIPANT_MESSAGE_DATA_VENDER_SPECIFIC_KIND_FLAG 0x8000000u + +#define PID_VENDORSPECIFIC_FLAG 0x8000u +#define PID_UNRECOGNIZED_INCOMPATIBLE_FLAG 0x4000u + +#define PID_PAD 0x0u +#define PID_SENTINEL 0x1u +#define PID_USER_DATA 0x2cu +#define PID_TOPIC_NAME 0x5u +#define PID_TYPE_NAME 0x7u +#define PID_GROUP_DATA 0x2du +#define PID_TOPIC_DATA 0x2eu +#define PID_DURABILITY 0x1du +#define PID_DURABILITY_SERVICE 0x1eu +#define PID_DEADLINE 0x23u +#define PID_LATENCY_BUDGET 0x27u +#define PID_LIVELINESS 0x1bu +#define PID_RELIABILITY 0x1au +#define PID_LIFESPAN 0x2bu +#define PID_DESTINATION_ORDER 0x25u +#define PID_HISTORY 0x40u +#define PID_RESOURCE_LIMITS 0x41u +#define PID_OWNERSHIP 0x1fu +#define PID_OWNERSHIP_STRENGTH 0x6u +#define PID_PRESENTATION 0x21u +#define PID_PARTITION 0x29u +#define PID_TIME_BASED_FILTER 0x4u +#define PID_TRANSPORT_PRIORITY 0x49u +#define PID_PROTOCOL_VERSION 0x15u +#define PID_VENDORID 0x16u +#define PID_UNICAST_LOCATOR 0x2fu +#define PID_MULTICAST_LOCATOR 0x30u +#define PID_MULTICAST_IPADDRESS 0x11u +#define PID_DEFAULT_UNICAST_LOCATOR 0x31u +#define PID_DEFAULT_MULTICAST_LOCATOR 0x48u +#define PID_METATRAFFIC_UNICAST_LOCATOR 0x32u +#define PID_METATRAFFIC_MULTICAST_LOCATOR 0x33u +#define PID_DEFAULT_UNICAST_IPADDRESS 0xcu +#define PID_DEFAULT_UNICAST_PORT 0xeu +#define PID_METATRAFFIC_UNICAST_IPADDRESS 0x45u +#define PID_METATRAFFIC_UNICAST_PORT 0xdu +#define PID_METATRAFFIC_MULTICAST_IPADDRESS 0xbu +#define PID_METATRAFFIC_MULTICAST_PORT 0x46u +#define PID_EXPECTS_INLINE_QOS 0x43u +#define PID_PARTICIPANT_MANUAL_LIVELINESS_COUNT 0x34u +#define PID_PARTICIPANT_BUILTIN_ENDPOINTS 0x44u +#define PID_PARTICIPANT_LEASE_DURATION 0x2u +#define PID_CONTENT_FILTER_PROPERTY 0x35u +#define PID_PARTICIPANT_GUID 0x50u +#define PID_PARTICIPANT_ENTITYID 0x51u +#define PID_GROUP_GUID 0x52u +#define PID_GROUP_ENTITYID 0x53u +#define PID_BUILTIN_ENDPOINT_SET 0x58u +#define PID_PROPERTY_LIST 0x59u +#define PID_TYPE_MAX_SIZE_SERIALIZED 0x60u +#define PID_ENTITY_NAME 0x62u +#define PID_KEYHASH 0x70u +#define PID_STATUSINFO 0x71u +#define PID_CONTENT_FILTER_INFO 0x55u +#define PID_COHERENT_SET 0x56u +#define PID_DIRECTED_WRITE 0x57u +#define PID_ORIGINAL_WRITER_INFO 0x61u +#define PID_ENDPOINT_GUID 0x5au /* !SPEC <=> PRISMTECH_ENDPOINT_GUID */ + +/* Security related PID values. */ +#define PID_IDENTITY_TOKEN 0x1001u +#define PID_PERMISSIONS_TOKEN 0x1002u + +#define PID_RTI_TYPECODE (PID_VENDORSPECIFIC_FLAG | 0x4u) + +#ifdef DDSI_INCLUDE_SSM +/* To indicate whether a reader favours the use of SSM. Iff the + reader favours SSM, it will use SSM if available. */ +#define PID_READER_FAVOURS_SSM 0x72u +#endif + +#ifdef DDSI_INCLUDE_SSM +/* To indicate whether a reader favours the use of SSM. Iff the + reader favours SSM, it will use SSM if available. */ +#define PID_READER_FAVOURS_SSM 0x72u +#endif + +/* Deprecated parameter IDs (accepted but ignored) */ +#define PID_PERSISTENCE 0x03u +#define PID_TYPE_CHECKSUM 0x08u +#define PID_TYPE2_NAME 0x09u +#define PID_TYPE2_CHECKSUM 0x0au +#define PID_EXPECTS_ACK 0x10u +#define PID_MANAGER_KEY 0x12u +#define PID_SEND_QUEUE_SIZE 0x13u +#define PID_RELIABILITY_ENABLED 0x14u +#define PID_VARGAPPS_SEQUENCE_NUMBER_LAST 0x17u +#define PID_RECV_QUEUE_SIZE 0x18u +#define PID_RELIABILITY_OFFERED 0x19u + +#define PID_PRISMTECH_BUILTIN_ENDPOINT_SET (PID_VENDORSPECIFIC_FLAG | 0x0u) + +/* parameter ids for READER_DATA_LIFECYCLE & WRITER_DATA_LIFECYCLE are + undefined, but let's publish them anyway */ +#define PID_PRISMTECH_READER_DATA_LIFECYCLE (PID_VENDORSPECIFIC_FLAG | 0x2u) +#define PID_PRISMTECH_WRITER_DATA_LIFECYCLE (PID_VENDORSPECIFIC_FLAG | 0x3u) + +/* ENDPOINT_GUID is formally undefined, so in strictly conforming + mode, we use our own */ +#define PID_PRISMTECH_ENDPOINT_GUID (PID_VENDORSPECIFIC_FLAG | 0x4u) + +#define PID_PRISMTECH_SYNCHRONOUS_ENDPOINT (PID_VENDORSPECIFIC_FLAG | 0x5u) + +/* Relaxed QoS matching readers/writers are best ignored by + implementations that don't understand them. This also covers "old" + DDSI2's, although they may emit an error. */ +#define PID_PRISMTECH_RELAXED_QOS_MATCHING (PID_VENDORSPECIFIC_FLAG | PID_UNRECOGNIZED_INCOMPATIBLE_FLAG | 0x6u) + +#define PID_PRISMTECH_PARTICIPANT_VERSION_INFO (PID_VENDORSPECIFIC_FLAG | 0x7u) + +/* See CMTopics protocol.doc (2013-12-09) */ +#define PID_PRISMTECH_NODE_NAME (PID_VENDORSPECIFIC_FLAG | 0x8u) +#define PID_PRISMTECH_EXEC_NAME (PID_VENDORSPECIFIC_FLAG | 0x9u) +#define PID_PRISMTECH_PROCESS_ID (PID_VENDORSPECIFIC_FLAG | 0xau) +#define PID_PRISMTECH_SERVICE_TYPE (PID_VENDORSPECIFIC_FLAG | 0xbu) +#define PID_PRISMTECH_ENTITY_FACTORY (PID_VENDORSPECIFIC_FLAG | 0xcu) +#define PID_PRISMTECH_WATCHDOG_SCHEDULING (PID_VENDORSPECIFIC_FLAG | 0xdu) +#define PID_PRISMTECH_LISTENER_SCHEDULING (PID_VENDORSPECIFIC_FLAG | 0xeu) +#define PID_PRISMTECH_SUBSCRIPTION_KEYS (PID_VENDORSPECIFIC_FLAG | 0xfu) +#define PID_PRISMTECH_READER_LIFESPAN (PID_VENDORSPECIFIC_FLAG | 0x10u) +#define PID_PRISMTECH_TYPE_DESCRIPTION (PID_VENDORSPECIFIC_FLAG | 0x12u) +#define PID_PRISMTECH_LAN (PID_VENDORSPECIFIC_FLAG | 0x13u) +#define PID_PRISMTECH_ENDPOINT_GID (PID_VENDORSPECIFIC_FLAG | 0x14u) +#define PID_PRISMTECH_GROUP_GID (PID_VENDORSPECIFIC_FLAG | 0x15u) +#define PID_PRISMTECH_EOTINFO (PID_VENDORSPECIFIC_FLAG | 0x16u) +#define PID_PRISMTECH_PART_CERT_NAME (PID_VENDORSPECIFIC_FLAG | 0x17u); +#define PID_PRISMTECH_LAN_CERT_NAME (PID_VENDORSPECIFIC_FLAG | 0x18u); + +#if defined (__cplusplus) +} +#endif + +#endif /* NN_PROTOCOL_H */ diff --git a/src/core/ddsi/include/ddsi/q_qosmatch.h b/src/core/ddsi/include/ddsi/q_qosmatch.h new file mode 100644 index 0000000..df224ed --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_qosmatch.h @@ -0,0 +1,33 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_QOSMATCH_H +#define Q_QOSMATCH_H + +#if defined (__cplusplus) +extern "C" { +#endif + +struct nn_xqos; + +int partition_match_based_on_wildcard_in_left_operand (const struct nn_xqos *a, const struct nn_xqos *b, const char **realname); +int partitions_match_p (const struct nn_xqos *a, const struct nn_xqos *b); +int is_wildcard_partition (const char *str); + +/* Returns -1 on success, or QoS id of first mismatch (>=0) */ + +int32_t qos_match_p (const struct nn_xqos *rd, const struct nn_xqos *wr); + +#if defined (__cplusplus) +} +#endif + +#endif /* Q_QOSMATCH_H */ diff --git a/src/core/ddsi/include/ddsi/q_radmin.h b/src/core/ddsi/include/ddsi/q_radmin.h new file mode 100644 index 0000000..1561491 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_radmin.h @@ -0,0 +1,250 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_RADMIN_H +#define NN_RADMIN_H + +#include "os/os_thread.h" +#include "ddsi/q_rtps.h" +#include "ddsi/ddsi_tran.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +struct nn_rbufpool; +struct nn_rbuf; +struct nn_rmsg; +struct nn_rdata; +struct nn_rsample; +struct nn_rsample_chain; +struct nn_rsample_info; +struct nn_defrag; +struct nn_reorder; +struct nn_dqueue; +struct nn_guid; + +struct proxy_writer; + +struct nn_fragment_number_set; +struct nn_sequence_number_set; + +typedef int (*nn_dqueue_handler_t) (const struct nn_rsample_info *sampleinfo, const struct nn_rdata *fragchain, const struct nn_guid *rdguid, void *qarg); + +struct nn_rmsg_chunk { + struct nn_rbuf *rbuf; + struct nn_rmsg_chunk *next; + + /* Size is 0 after initial allocation, must be set with + nn_rmsg_setsize after receiving a packet from the kernel and + before processing it. */ + uint32_t size; + union { + /* payload array stretched to whatever it really is */ + unsigned char payload[1]; + + /* to ensure reasonable alignment of payload[] */ + int64_t l; + double d; + void *p; + } u; +}; + +struct nn_rmsg { + /* Reference count: all references to rdatas of this message are + counted. The rdatas themselves do not have a reference count. + + The refcount is biased by RMSG_REFCOUNT_UNCOMMITED_BIAS while + still being inserted to allow verifying it is still uncommitted + when allocating memory, increasing refcounts, &c. + + Each rdata adds RMS_REFCOUNT_RDATA_BIAS when it leaves + defragmentation until it has been rejected by reordering or has + been scheduled for delivery. This allows delaying the + decrementing of refcounts until after a sample has been added to + all radmins even though be delivery of it may take place in + concurrently. */ + os_atomic_uint32_t refcount; + + /* Worst-case memory requirement is gigantic (64kB UDP packet, only + 1-byte final fragments, each of one a new interval, or maybe 1 + byte messages, destined for many readers and in each case + introducing a new interval, with receiver state changes in + between, &c.), so we can either: + + - allocate a _lot_ and cover the worst case + + - allocate enough for all "reasonable" cases, discarding data when that limit is hit + + - dynamically add chunks of memory, and even entire receive buffers. + + The latter seems the best approach, especially because it also + covers the second one. We treat the other chunks specially, + which is not strictly required but also not entirely + unreasonable, considering that the first chunk has the refcount & + the real packet. */ + struct nn_rmsg_chunk *lastchunk; + + struct nn_rmsg_chunk chunk; +}; +#define NN_RMSG_PAYLOAD(m) ((m)->chunk.u.payload) +#define NN_RMSG_PAYLOADOFF(m, o) (NN_RMSG_PAYLOAD (m) + (o)) + +struct receiver_state { + nn_guid_prefix_t src_guid_prefix; /* 12 */ + nn_guid_prefix_t dst_guid_prefix; /* 12 */ + struct addrset *reply_locators; /* 4/8 */ + int forme; /* 4 */ + nn_vendorid_t vendor; /* 2 */ + nn_protocol_version_t protocol_version; /* 2 => 44/48 */ + ddsi_tran_conn_t conn; /* Connection for request */ +}; + +struct proxy_writer_info +{ + nn_guid_t guid; + bool auto_dispose; + int32_t ownership_strength; + uint64_t iid; +}; + +struct nn_rsample_info { + seqno_t seq; + struct receiver_state *rst; + struct proxy_writer *pwr; + uint32_t size; + uint32_t fragsize; + nn_ddsi_time_t timestamp; + nn_wctime_t reception_timestamp; /* OpenSplice extension -- but we get it essentially for free, so why not? */ + unsigned statusinfo: 2; /* just the two defined bits from the status info */ + unsigned pt_wr_info_zoff: 16; /* PrismTech writer info offset */ + unsigned bswap: 1; /* so we can extract well formatted writer info quicker */ + unsigned complex_qos: 1; /* includes QoS other than keyhash, 2-bit statusinfo, PT writer info */ + unsigned hashash: 1; /* Do we have a key hash */ + nn_keyhash_t keyhash; /* Key hash. Not currently used by OSPL */ + struct proxy_writer_info pwr_info; +}; + +struct nn_rdata { + struct nn_rmsg *rmsg; /* received (and refcounted) in rmsg */ + struct nn_rdata *nextfrag; /* fragment chain */ + uint32_t min, maxp1; /* fragment as byte offsets */ + uint16_t submsg_zoff; /* offset to submessage from packet start, or 0 */ + uint16_t payload_zoff; /* offset to payload from packet start */ +#ifndef NDEBUG + os_atomic_uint32_t refcount_bias_added; +#endif +}; + +/* All relative offsets in packets that we care about (submessage + header, payload, writer info) are at multiples of 4 bytes and + within 64kB, so technically we can make do with 14 bits instead of + 16, in case we run out of space. + + If we _really_ need to squeeze out every last bit, only the submsg + offset really requires 14 bits, the for the others we could use an + offset relative to the submessage header so that it is limited by + the maximum size of the inline QoS ... Defining the macros now, so + we have the option to do wild things. */ +#ifndef NDEBUG +#define NN_ZOFF_TO_OFF(zoff) ((unsigned) (zoff)) +#define NN_OFF_TO_ZOFF(off) (assert ((off) < 65536 && ((off) % 4) == 0), ((unsigned short) (off))) +#else +#define NN_ZOFF_TO_OFF(zoff) ((unsigned) (zoff)) +#define NN_OFF_TO_ZOFF(off) ((unsigned short) (off)) +#endif +#define NN_SAMPLEINFO_HAS_WRINFO(rsi) ((rsi)->pt_wr_info_zoff != NN_OFF_TO_ZOFF (0)) +#define NN_SAMPLEINFO_WRINFO_OFF(rsi) NN_ZOFF_TO_OFF ((rsi)->pt_wr_info_zoff) +#define NN_RDATA_PAYLOAD_OFF(rdata) NN_ZOFF_TO_OFF ((rdata)->payload_zoff) +#define NN_RDATA_SUBMSG_OFF(rdata) NN_ZOFF_TO_OFF ((rdata)->submsg_zoff) + +struct nn_rsample_chain_elem { + /* FIXME: evidently smaller than a defrag_iv, but maybe better to + merge it with defrag_iv in a union anyway. */ + struct nn_rdata *fragchain; + struct nn_rsample_chain_elem *next; + /* Gaps have sampleinfo = NULL, but nonetheless a fragchain with 1 + rdata with min=maxp1 (length 0) and valid rmsg pointer. And (see + DQUEUE) its lsb gets abused so we can queue "bubbles" in addition + to data). */ + struct nn_rsample_info *sampleinfo; +}; + +struct nn_rsample_chain { + struct nn_rsample_chain_elem *first; + struct nn_rsample_chain_elem *last; +}; + +enum nn_reorder_mode { + NN_REORDER_MODE_NORMAL, + NN_REORDER_MODE_MONOTONICALLY_INCREASING, + NN_REORDER_MODE_ALWAYS_DELIVER +}; + +enum nn_defrag_drop_mode { + NN_DEFRAG_DROP_OLDEST, /* (believed to be) best for unreliable */ + NN_DEFRAG_DROP_LATEST /* (...) best for reliable */ +}; + +typedef int32_t nn_reorder_result_t; +/* typedef of reorder result serves as a warning that it is to be + interpreted as follows: */ +/* REORDER_DELIVER > 0 -- number of samples in sample chain */ +#define NN_REORDER_ACCEPT 0 /* accepted/stored (for gap: also adjusted next_expected) */ +#define NN_REORDER_TOO_OLD -1 /* discarded because it was too old */ +#define NN_REORDER_REJECT -2 /* caller may reuse memory ("real" reject for data, "fake" for gap) */ + +typedef void (*nn_dqueue_callback_t) (void *arg); + +struct nn_rbufpool *nn_rbufpool_new (uint32_t rbuf_size, uint32_t max_rmsg_size); +void nn_rbufpool_setowner (struct nn_rbufpool *rbp, os_threadId tid); +void nn_rbufpool_free (struct nn_rbufpool *rbp); + +struct nn_rmsg *nn_rmsg_new (struct nn_rbufpool *rbufpool); +void nn_rmsg_setsize (struct nn_rmsg *rmsg, uint32_t size); +void nn_rmsg_commit (struct nn_rmsg *rmsg); +void nn_rmsg_free (struct nn_rmsg *rmsg); +void *nn_rmsg_alloc (struct nn_rmsg *rmsg, uint32_t size); + +struct nn_rdata *nn_rdata_new (struct nn_rmsg *rmsg, uint32_t start, uint32_t endp1, uint32_t submsg_offset, uint32_t payload_offset); +struct nn_rdata *nn_rdata_newgap (struct nn_rmsg *rmsg); +void nn_fragchain_adjust_refcount (struct nn_rdata *frag, int adjust); +void nn_fragchain_unref (struct nn_rdata *frag); + +struct nn_defrag *nn_defrag_new (enum nn_defrag_drop_mode drop_mode, uint32_t max_samples); +void nn_defrag_free (struct nn_defrag *defrag); +struct nn_rsample *nn_defrag_rsample (struct nn_defrag *defrag, struct nn_rdata *rdata, const struct nn_rsample_info *sampleinfo); +void nn_defrag_notegap (struct nn_defrag *defrag, seqno_t min, seqno_t maxp1); +int nn_defrag_nackmap (struct nn_defrag *defrag, seqno_t seq, uint32_t maxfragnum, struct nn_fragment_number_set *map, uint32_t maxsz); + +struct nn_reorder *nn_reorder_new (enum nn_reorder_mode mode, uint32_t max_samples); +void nn_reorder_free (struct nn_reorder *r); +struct nn_rsample *nn_reorder_rsample_dup (struct nn_rmsg *rmsg, struct nn_rsample *rsampleiv); +struct nn_rdata *nn_rsample_fragchain (struct nn_rsample *rsample); +nn_reorder_result_t nn_reorder_rsample (struct nn_rsample_chain *sc, struct nn_reorder *reorder, struct nn_rsample *rsampleiv, int *refcount_adjust, int delivery_queue_full_p); +nn_reorder_result_t nn_reorder_gap (struct nn_rsample_chain *sc, struct nn_reorder *reorder, struct nn_rdata *rdata, seqno_t min, seqno_t maxp1, int *refcount_adjust); +int nn_reorder_wantsample (struct nn_reorder *reorder, seqno_t seq); +unsigned nn_reorder_nackmap (struct nn_reorder *reorder, seqno_t base, seqno_t maxseq, struct nn_sequence_number_set *map, uint32_t maxsz, int notail); +seqno_t nn_reorder_next_seq (const struct nn_reorder *reorder); + +struct nn_dqueue *nn_dqueue_new (const char *name, uint32_t max_samples, nn_dqueue_handler_t handler, void *arg); +void nn_dqueue_free (struct nn_dqueue *q); +void nn_dqueue_enqueue (struct nn_dqueue *q, struct nn_rsample_chain *sc, nn_reorder_result_t rres); +void nn_dqueue_enqueue1 (struct nn_dqueue *q, const nn_guid_t *rdguid, struct nn_rsample_chain *sc, nn_reorder_result_t rres); +void nn_dqueue_enqueue_callback (struct nn_dqueue *q, nn_dqueue_callback_t cb, void *arg); +int nn_dqueue_is_full (struct nn_dqueue *q); +void nn_dqueue_wait_until_empty_if_full (struct nn_dqueue *q); + +#if defined (__cplusplus) +} +#endif + +#endif /* NN_RADMIN_H */ diff --git a/src/core/ddsi/include/ddsi/q_receive.h b/src/core/ddsi/include/ddsi/q_receive.h new file mode 100644 index 0000000..24992ef --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_receive.h @@ -0,0 +1,32 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_RECEIVE_H +#define Q_RECEIVE_H + +#if defined (__cplusplus) +extern "C" { +#endif + +struct nn_rbufpool; +struct nn_rsample_info; +struct nn_rdata; +struct ddsi_tran_listener; + +uint32_t recv_thread (struct nn_rbufpool *rbpool); +uint32_t listen_thread (struct ddsi_tran_listener * listener); +int user_dqueue_handler (const struct nn_rsample_info *sampleinfo, const struct nn_rdata *fragchain, const nn_guid_t *rdguid, void *qarg); + +#if defined (__cplusplus) +} +#endif + +#endif /* Q_RECEIVE_H */ diff --git a/src/core/ddsi/include/ddsi/q_rtps.h b/src/core/ddsi/include/ddsi/q_rtps.h new file mode 100644 index 0000000..786890b --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_rtps.h @@ -0,0 +1,90 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_RTPS_H +#define NN_RTPS_H + +#include "os/os_defs.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +typedef struct { + unsigned char id[2]; +} nn_vendorid_t; +typedef struct { + unsigned char major, minor; +} nn_protocol_version_t; +typedef union nn_guid_prefix { + unsigned char s[12]; + unsigned u[3]; +} nn_guid_prefix_t; +typedef union nn_entityid { + unsigned u; +} nn_entityid_t; +typedef struct nn_guid { + nn_guid_prefix_t prefix; + nn_entityid_t entityid; +} nn_guid_t; +typedef int64_t seqno_t; +#define MAX_SEQ_NUMBER INT64_MAX + +#define PGUIDPREFIX(gp) (gp).u[0], (gp).u[1], (gp).u[2] +#define PGUID(g) PGUIDPREFIX ((g).prefix), (g).entityid.u + +/* predefined entity ids; here viewed as an unsigned int, on the + network as four bytes corresponding to the integer in network byte + order */ +#define NN_ENTITYID_UNKNOWN 0x0 +#define NN_ENTITYID_PARTICIPANT 0x1c1 +#define NN_ENTITYID_SEDP_BUILTIN_TOPIC_WRITER 0x2c2 +#define NN_ENTITYID_SEDP_BUILTIN_TOPIC_READER 0x2c7 +#define NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_WRITER 0x3c2 +#define NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_READER 0x3c7 +#define NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_WRITER 0x4c2 +#define NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_READER 0x4c7 +#define NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER 0x100c2 +#define NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_READER 0x100c7 +#define NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_WRITER 0x200c2 +#define NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_READER 0x200c7 +#define NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_WRITER 0x142 +#define NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_READER 0x147 +#define NN_ENTITYID_SEDP_BUILTIN_CM_PUBLISHER_WRITER 0x242 +#define NN_ENTITYID_SEDP_BUILTIN_CM_PUBLISHER_READER 0x247 +#define NN_ENTITYID_SEDP_BUILTIN_CM_SUBSCRIBER_WRITER 0x342 +#define NN_ENTITYID_SEDP_BUILTIN_CM_SUBSCRIBER_READER 0x347 +#define NN_ENTITYID_SOURCE_MASK 0xc0 +#define NN_ENTITYID_SOURCE_USER 0x00 +#define NN_ENTITYID_SOURCE_BUILTIN 0xc0 +#define NN_ENTITYID_SOURCE_VENDOR 0x40 +#define NN_ENTITYID_KIND_MASK 0x3f +#define NN_ENTITYID_KIND_WRITER_WITH_KEY 0x02 +#define NN_ENTITYID_KIND_WRITER_NO_KEY 0x03 +#define NN_ENTITYID_KIND_READER_NO_KEY 0x04 +#define NN_ENTITYID_KIND_READER_WITH_KEY 0x07 +#define NN_ENTITYID_KIND_PRISMTECH_SUBSCRIBER 0x0a /* source = VENDOR */ +#define NN_ENTITYID_KIND_PRISMTECH_PUBLISHER 0x0b /* source = VENDOR */ +#define NN_ENTITYID_ALLOCSTEP 0x100 + +struct cfgst; +int rtps_config_prep (struct cfgst *cfgst); +int rtps_config_open (void); +int rtps_init (void); +void ddsi_plugin_init (void); +void rtps_term_prep (void); +void rtps_term (void); + +#if defined (__cplusplus) +} +#endif + +#endif /* NN_RTPS_H */ diff --git a/src/core/ddsi/include/ddsi/q_security.h b/src/core/ddsi/include/ddsi/q_security.h new file mode 100644 index 0000000..a01dbe1 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_security.h @@ -0,0 +1,46 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifdef DDSI_INCLUDE_ENCRYPTION +#ifndef Q_SECURITY_H +#define Q_SECURITY_H + +#include "c_typebase.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +/* Generic class */ +C_CLASS(q_securityEncoderSet); +C_CLASS(q_securityDecoderSet); + +/* Set of supported ciphers */ +typedef enum +{ + Q_CIPHER_UNDEFINED, + Q_CIPHER_NULL, + Q_CIPHER_BLOWFISH, + Q_CIPHER_AES128, + Q_CIPHER_AES192, + Q_CIPHER_AES256, + Q_CIPHER_NONE, + Q_CIPHER_MAX +} q_cipherType; + +void ddsi_security_plugin (void); + +#if defined (__cplusplus) +} +#endif + +#endif +#endif diff --git a/src/core/ddsi/include/ddsi/q_servicelease.h b/src/core/ddsi/include/ddsi/q_servicelease.h new file mode 100644 index 0000000..c4e8be3 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_servicelease.h @@ -0,0 +1,30 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_SERVICELEASE_H +#define NN_SERVICELEASE_H + +#if defined (__cplusplus) +extern "C" { +#endif + +struct nn_servicelease; + +struct nn_servicelease *nn_servicelease_new (void (*renew_cb) (void *arg), void *renew_arg); +int nn_servicelease_start_renewing (struct nn_servicelease *sl); +void nn_servicelease_free (struct nn_servicelease *sl); +void nn_servicelease_statechange_barrier (struct nn_servicelease *sl); + +#if defined (__cplusplus) +} +#endif + +#endif /* NN_SERVICELEASE_H */ diff --git a/src/core/ddsi/include/ddsi/q_sockwaitset.h b/src/core/ddsi/include/ddsi/q_sockwaitset.h new file mode 100644 index 0000000..c50adf4 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_sockwaitset.h @@ -0,0 +1,111 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_SOCKWAITSET_H +#define Q_SOCKWAITSET_H + +#include "os/os_defs.h" +#include "ddsi/sysdeps.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +typedef struct os_sockWaitset * os_sockWaitset; +typedef struct os_sockWaitsetCtx * os_sockWaitsetCtx; +struct ddsi_tran_conn; + +/* + Allocates a new connection waitset. The waitset is thread-safe in + that multiple threads may add and remove connections from the wait set + or trigger it. However only a single thread may process events from + the wait set using the Wait and NextEvent functions in a single handling + loop. +*/ +os_sockWaitset os_sockWaitsetNew (void); + +/* + Frees the waitset WS. Any connections associated with it will + be closed. +*/ +void os_sockWaitsetFree (os_sockWaitset ws); + +/* + Triggers the waitset, from any thread. It is level + triggered, when called while no thread is waiting in + os_sockWaitsetWait the trigger will cause an (early) wakeup on the + next call to os_sockWaitsetWait. Returns os_resultSuccess if + successfully triggered, os_resultInvalid if an error occurs. + + Triggering a waitset may require resources and they may be counted. + Do not trigger a waitset arbitrarily often without ensuring + os_sockWaitsetWait is called often enough to let it release any + resources used. + + Shared state updates preceding os_sockWaitsetTrigger are visible + following os_sockWaitsetWait. +*/ +void os_sockWaitsetTrigger (os_sockWaitset ws); + +/* + A connection may be associated with only one waitset at any time, and + may be added to the waitset only once. Failure to comply with this + restriction results in undefined behaviour. + + Closing a connection associated with a waitset is handled gracefully: no + operations will signal errors because of it. +*/ +void os_sockWaitsetAdd (os_sockWaitset ws, struct ddsi_tran_conn * conn); + +/* + Drops all connections from the waitset from index onwards. Index + 0 corresponds to the first connection added to the waitset, index 1 to + the second, etc. Behaviour is undefined when called after a successful wait + but before all events had been enumerated. +*/ +void os_sockWaitsetPurge (os_sockWaitset ws, unsigned index); + +/* + Waits until some of the connections in WS have data to be read. + + Returns a new wait set context if one or more connections have data to read. + However, the return may be spurious (NULL) (i.e., no events) + + If a context is returned it must be enumerated before os_sockWaitsetWait + may be called again. + + Shared state updates preceding os_sockWaitsetTrigger are visible + following os_sockWaitsetWait. +*/ +os_sockWaitsetCtx os_sockWaitsetWait (os_sockWaitset ws); + +/* + Returns the index of the next triggered connection in the + waitset contect ctx, or -1 if the set of available events has been + exhausted. Index 0 is the first connection added to the waitset, index + 1 the second, &c. + + Following a call to os_sockWaitsetWait on waitset that returned + a context, one MUST enumerate all available events before + os_sockWaitsetWait may be called again. + + If the return value is >= 0, *conn contains the connection on which + data is available. +*/ +int os_sockWaitsetNextEvent (os_sockWaitsetCtx ctx, struct ddsi_tran_conn ** conn); + +/* Remove connection */ +void os_sockWaitsetRemove (os_sockWaitset ws, struct ddsi_tran_conn * conn); + +#if defined (__cplusplus) +} +#endif +#endif /* Q_SOCKWAITSET_H */ diff --git a/src/core/ddsi/include/ddsi/q_static_assert.h b/src/core/ddsi/include/ddsi/q_static_assert.h new file mode 100644 index 0000000..078ed62 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_static_assert.h @@ -0,0 +1,37 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_STATIC_ASSERT_H +#define Q_STATIC_ASSERT_H + +/* There are many tricks to use a constant expression to yield an + illegal type or expression at compile time, such as zero-sized + arrays and duplicate case or enum labels. So this is but one of the + many tricks. */ + +#ifndef _MSC_VER + +#define Q_STATIC_ASSERT_CODE(pred) do { switch(0) { case 0: case pred: ; } } while (0) + +#else + +/* Temporarily disabling warning C6326: Potential comparison of a + constant with another constant. */ +#define Q_STATIC_ASSERT_CODE(pred) do { \ + __pragma (warning (push)) \ + __pragma (warning (disable : 6326)) \ + switch(0) { case 0: case pred: ; } \ + __pragma (warning (pop)) \ + } while (0) + +#endif + +#endif /* Q_STATIC_ASSERT_H */ diff --git a/src/core/ddsi/include/ddsi/q_thread.h b/src/core/ddsi/include/ddsi/q_thread.h new file mode 100644 index 0000000..4291fd7 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_thread.h @@ -0,0 +1,128 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_THREAD_H +#define Q_THREAD_H + +#include "os/os.h" + +#include "ddsi/q_inline.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +/* Things don't go wrong if CACHE_LINE_SIZE is defined incorrectly, + they just run slower because of false cache-line sharing. It can be + discovered at run-time, but in practice it's 64 for most CPUs and + 128 for some. */ +#define CACHE_LINE_SIZE 64 + +typedef uint32_t vtime_t; +typedef int32_t svtime_t; /* signed version */ + +/* GCC has a nifty feature allowing the specification of the required + alignment: __attribute__ ((aligned (CACHE_LINE_SIZE))) in this + case. Many other compilers implement it as well, but it is by no + means a standard feature. So we do it the old-fashioned way. */ + + +/* These strings are used to indicate the required scheduling class to the "create_thread()" */ +#define Q_THREAD_SCHEDCLASS_REALTIME "Realtime" +#define Q_THREAD_SCHEDCLASS_TIMESHARE "Timeshare" + +/* When this value is used, the platform default for scheduling priority will be used */ +#define Q_THREAD_SCHEDPRIO_DEFAULT 0 + +enum thread_state { + THREAD_STATE_ZERO, + THREAD_STATE_ALIVE +}; + +struct logbuf; + +/* + * watchdog indicates progress for the service lease liveliness mechsanism, while vtime + * indicates progress for the Garbage collection purposes. + * vtime even : thread awake + * vtime odd : thread asleep + */ +#define THREAD_BASE \ + volatile vtime_t vtime; \ + volatile vtime_t watchdog; \ + os_threadId tid; \ + os_threadId extTid; \ + enum thread_state state; \ + struct logbuf *lb; \ + char *name /* note: no semicolon! */ + +struct thread_state_base { + THREAD_BASE; +}; + +struct thread_state1 { + THREAD_BASE; + char pad[CACHE_LINE_SIZE + * ((sizeof (struct thread_state_base) + CACHE_LINE_SIZE - 1) + / CACHE_LINE_SIZE) + - sizeof (struct thread_state_base)]; +}; +#undef THREAD_BASE + +struct thread_states { + os_mutex lock; + unsigned nthreads; + struct thread_state1 *ts; /* [nthreads] */ +}; + +extern struct thread_states thread_states; +extern os_threadLocal struct thread_state1 *tsd_thread_state; + +void thread_states_init (_In_ unsigned maxthreads); +void thread_states_fini (void); + +void upgrade_main_thread (void); +void downgrade_main_thread (void); +const struct config_thread_properties_listelem *lookup_thread_properties (_In_z_ const char *name); +_Success_(return != NULL) _Ret_maybenull_ struct thread_state1 *create_thread (_In_z_ const char *name, _In_ uint32_t (*f) (void *arg), _In_opt_ void *arg); +_Ret_valid_ struct thread_state1 *lookup_thread_state (void); +_Success_(return != NULL) _Ret_maybenull_ struct thread_state1 *lookup_thread_state_real (void); +_Success_(return == 0) int join_thread (_Inout_ struct thread_state1 *ts1); +void log_stack_traces (void); +struct thread_state1 *get_thread_state (_In_ os_threadId id); +struct thread_state1 * init_thread_state (_In_z_ const char *tname); +void reset_thread_state (_Inout_opt_ struct thread_state1 *ts1); +int thread_exists (_In_z_ const char *name); + +#if defined (__cplusplus) +} +#endif + +#if NN_HAVE_C99_INLINE && !defined SUPPRESS_THREAD_INLINES +#include "q_thread_template.h" +#else +#if defined (__cplusplus) +extern "C" { +#endif +int vtime_awake_p (_In_ vtime_t vtime); +int vtime_asleep_p (_In_ vtime_t vtime); +int vtime_gt (_In_ vtime_t vtime1, _In_ vtime_t vtime0); + +void thread_state_asleep (_Inout_ struct thread_state1 *ts1); +void thread_state_awake (_Inout_ struct thread_state1 *ts1); +void thread_state_blocked (_Inout_ struct thread_state1 *ts1); +void thread_state_unblocked (_Inout_ struct thread_state1 *ts1); +#if defined (__cplusplus) +} +#endif +#endif + +#endif /* Q_THREAD_H */ diff --git a/src/core/ddsi/include/ddsi/q_thread_template.h b/src/core/ddsi/include/ddsi/q_thread_template.h new file mode 100644 index 0000000..957891d --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_thread_template.h @@ -0,0 +1,101 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +/* -*- c -*- */ + +#include "ddsi/sysdeps.h" +#include "os/os_atomics.h" +#include "ddsi/q_static_assert.h" + +#if defined SUPPRESS_THREAD_INLINES && defined NN_C99_INLINE +#undef NN_C99_INLINE +#define NN_C99_INLINE +#endif + +NN_C99_INLINE int vtime_awake_p (_In_ vtime_t vtime) +{ + return (vtime % 2) == 0; +} + +NN_C99_INLINE int vtime_asleep_p (_In_ vtime_t vtime) +{ + return (vtime % 2) == 1; +} + +NN_C99_INLINE int vtime_gt (_In_ vtime_t vtime1, _In_ vtime_t vtime0) +{ + Q_STATIC_ASSERT_CODE (sizeof (vtime_t) == sizeof (svtime_t)); + return (svtime_t) (vtime1 - vtime0) > 0; +} + +NN_C99_INLINE void thread_state_asleep (_Inout_ struct thread_state1 *ts1) +{ + vtime_t vt = ts1->vtime; + vtime_t wd = ts1->watchdog; + if (vtime_awake_p (vt)) + { + os_atomic_fence_rel (); + ts1->vtime = vt + 1; + } + else + { + os_atomic_fence_rel (); + ts1->vtime = vt + 2; + os_atomic_fence_acq (); + } + + if ( wd % 2 ){ + ts1->watchdog = wd + 2; + } else { + ts1->watchdog = wd + 1; + } + } + +NN_C99_INLINE void thread_state_awake (_Inout_ struct thread_state1 *ts1) +{ + vtime_t vt = ts1->vtime; + vtime_t wd = ts1->watchdog; + if (vtime_asleep_p (vt)) + ts1->vtime = vt + 1; + else + { + os_atomic_fence_rel (); + ts1->vtime = vt + 2; + } + os_atomic_fence_acq (); + + if ( wd % 2 ){ + ts1->watchdog = wd + 1; + } else { + ts1->watchdog = wd + 2; + } + +} + +NN_C99_INLINE void thread_state_blocked (_Inout_ struct thread_state1 *ts1) +{ + vtime_t wd = ts1->watchdog; + if ( wd % 2 ){ + ts1->watchdog = wd + 2; + } else { + ts1->watchdog = wd + 1; + } +} + +NN_C99_INLINE void thread_state_unblocked (_Inout_ struct thread_state1 *ts1) +{ + vtime_t wd = ts1->watchdog; + if ( wd % 2 ){ + ts1->watchdog = wd + 1; + } else { + ts1->watchdog = wd + 2; + } +} diff --git a/src/core/ddsi/include/ddsi/q_time.h b/src/core/ddsi/include/ddsi/q_time.h new file mode 100644 index 0000000..e36f7af --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_time.h @@ -0,0 +1,78 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_TIME_H +#define NN_TIME_H + +#include "os/os.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +#define T_NEVER 0x7fffffffffffffffll +#define T_MILLISECOND 1000000ll +#define T_SECOND (1000 * T_MILLISECOND) +#define T_MICROSECOND (T_MILLISECOND/1000) + +typedef struct { + int seconds; + unsigned fraction; +} nn_ddsi_time_t; + +#if DDSI_DURATION_ACCORDING_TO_SPEC /* what the spec says */ +typedef struct { /* why different from ddsi_time_t? */ + int sec; + int nanosec; +} nn_duration_t; +#else /* this is what I used to do & what wireshark does - probably right */ +typedef nn_ddsi_time_t nn_duration_t; +#endif + +typedef struct { + int64_t v; +} nn_mtime_t; + +typedef struct { + int64_t v; +} nn_wctime_t; + +typedef struct { + int64_t v; +} nn_etime_t; + +extern const nn_ddsi_time_t invalid_ddsi_timestamp; +extern const nn_ddsi_time_t ddsi_time_infinite; +extern const nn_duration_t duration_infinite; + +int valid_ddsi_timestamp (nn_ddsi_time_t t); + +OSAPI_EXPORT nn_wctime_t now (void); /* wall clock time */ +nn_mtime_t now_mt (void); /* monotonic time */ +nn_etime_t now_et (void); /* elapsed time */ +void mtime_to_sec_usec (_Out_ int * __restrict sec, _Out_ int * __restrict usec, _In_ nn_mtime_t t); +void wctime_to_sec_usec (_Out_ int * __restrict sec, _Out_ int * __restrict usec, _In_ nn_wctime_t t); +void etime_to_sec_usec (_Out_ int * __restrict sec, _Out_ int * __restrict usec, _In_ nn_etime_t t); +nn_mtime_t mtime_round_up (nn_mtime_t t, int64_t round); +nn_mtime_t add_duration_to_mtime (nn_mtime_t t, int64_t d); +nn_wctime_t add_duration_to_wctime (nn_wctime_t t, int64_t d); +nn_etime_t add_duration_to_etime (nn_etime_t t, int64_t d); + +nn_ddsi_time_t nn_wctime_to_ddsi_time (nn_wctime_t t); +OSAPI_EXPORT nn_wctime_t nn_wctime_from_ddsi_time (nn_ddsi_time_t x); +OSAPI_EXPORT nn_duration_t nn_to_ddsi_duration (int64_t t); +OSAPI_EXPORT int64_t nn_from_ddsi_duration (nn_duration_t x); + +#if defined (__cplusplus) +} +#endif + +#endif /* NN_TIME_H */ diff --git a/src/core/ddsi/include/ddsi/q_transmit.h b/src/core/ddsi/include/ddsi/q_transmit.h new file mode 100644 index 0000000..0070000 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_transmit.h @@ -0,0 +1,50 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_TRANSMIT_H +#define Q_TRANSMIT_H + +#include "os/os_defs.h" +#include "ddsi/q_rtps.h" /* for nn_entityid_t */ + +#if defined (__cplusplus) +extern "C" { +#endif + +struct nn_xpack; +struct nn_xmsg; +struct writer; +struct proxy_reader; +struct serdata; +struct tkmap_instance; + +/* Writing new data; serdata_twrite (serdata) is assumed to be really + recentish; serdata is unref'd. If xp == NULL, data is queued, else + packed. + + "nogc": no GC may occur, so it may not block to throttle the writer if the high water mark of the WHC is reached, which implies true KEEP_LAST behaviour. This is true for all the DDSI built-in writers. + "gc": GC may occur, which means the writer history and watermarks can be anything. This must be used for all application data. + */ +int write_sample_gc (struct nn_xpack *xp, struct writer *wr, struct serdata *serdata, struct tkmap_instance *tk); +int write_sample_nogc (struct nn_xpack *xp, struct writer *wr, struct serdata *serdata, struct tkmap_instance *tk); +int write_sample_gc_notk (struct nn_xpack *xp, struct writer *wr, struct serdata *serdata); +int write_sample_nogc_notk (struct nn_xpack *xp, struct writer *wr, struct serdata *serdata); + +/* When calling the following functions, wr->lock must be held */ +int create_fragment_message (struct writer *wr, seqno_t seq, const struct nn_plist *plist, struct serdata *serdata, unsigned fragnum, struct proxy_reader *prd,struct nn_xmsg **msg, int isnew); +int enqueue_sample_wrlock_held (struct writer *wr, seqno_t seq, const struct nn_plist *plist, struct serdata *serdata, struct proxy_reader *prd, int isnew); +void add_Heartbeat (struct nn_xmsg *msg, struct writer *wr, int hbansreq, nn_entityid_t dst, int issync); + +#if defined (__cplusplus) +} +#endif + +#endif /* Q_TRANSMIT_H */ diff --git a/src/core/ddsi/include/ddsi/q_unused.h b/src/core/ddsi/include/ddsi/q_unused.h new file mode 100644 index 0000000..5c987f8 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_unused.h @@ -0,0 +1,27 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_UNUSED_H +#define NN_UNUSED_H + +#ifdef __GNUC__ +#define UNUSED_ARG(x) x __attribute__ ((unused)) +#else +#define UNUSED_ARG(x) x +#endif + +#ifndef NDEBUG +#define UNUSED_ARG_NDEBUG(x) x +#else +#define UNUSED_ARG_NDEBUG(x) UNUSED_ARG (x) +#endif + +#endif /* NN_UNUSED_H */ diff --git a/src/core/ddsi/include/ddsi/q_whc.h b/src/core/ddsi/include/ddsi/q_whc.h new file mode 100644 index 0000000..8045087 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_whc.h @@ -0,0 +1,122 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef Q_WHC_H +#define Q_WHC_H + +#include "util/ut_avl.h" +#include "util/ut_hopscotch.h" +#include "ddsi/q_time.h" +#include "ddsi/q_rtps.h" +#include "ddsi/q_freelist.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +struct serdata; +struct nn_plist; +struct whc_idxnode; + +#define USE_EHH 0 + +struct whc_node { + struct whc_node *next_seq; /* next in this interval */ + struct whc_node *prev_seq; /* prev in this interval */ + struct whc_idxnode *idxnode; /* NULL if not in index */ + unsigned idxnode_pos; /* index in idxnode.hist */ + seqno_t seq; + uint64_t total_bytes; /* cumulative number of bytes up to and including this node */ + size_t size; + struct nn_plist *plist; /* 0 if nothing special */ + unsigned unacked: 1; /* counted in whc::unacked_bytes iff 1 */ + nn_mtime_t last_rexmit_ts; + unsigned rexmit_count; + struct serdata *serdata; +}; + +struct whc_intvnode { + ut_avlNode_t avlnode; + seqno_t min; + seqno_t maxp1; + struct whc_node *first; /* linked list of seqs with contiguous sequence numbers [min,maxp1) */ + struct whc_node *last; /* valid iff first != NULL */ +}; + +struct whc_idxnode { + int64_t iid; + seqno_t prune_seq; + struct tkmap_instance *tk; + unsigned headidx; +#if __STDC_VERSION__ >= 199901L + struct whc_node *hist[]; +#else + struct whc_node *hist[1]; +#endif +}; + +#if USE_EHH +struct whc_seq_entry { + seqno_t seq; + struct whc_node *whcn; +}; +#endif + +struct whc { + unsigned seq_size; + size_t unacked_bytes; + size_t sample_overhead; + uint64_t total_bytes; /* total number of bytes pushed in */ + unsigned is_transient_local: 1; + unsigned hdepth; /* 0 = unlimited */ + unsigned tldepth; /* 0 = disabled/unlimited (no need to maintain an index if KEEP_ALL <=> is_transient_local + tldepth=0) */ + unsigned idxdepth; /* = max(hdepth, tldepth) */ + seqno_t max_drop_seq; /* samples in whc with seq <= max_drop_seq => transient-local */ + struct whc_intvnode *open_intv; /* interval where next sample will go (usually) */ + struct whc_node *maxseq_node; /* NULL if empty; if not in open_intv, open_intv is empty */ + struct nn_freelist freelist; /* struct whc_node *; linked via whc_node::next_seq */ +#if USE_EHH + struct ut_ehh *seq_hash; +#else + struct ut_hh *seq_hash; +#endif + struct ut_hh *idx_hash; + ut_avlTree_t seq; +}; + +struct whc *whc_new (int is_transient_local, unsigned hdepth, unsigned tldepth, size_t sample_overhead); +void whc_free (struct whc *whc); +int whc_empty (const struct whc *whc); +seqno_t whc_min_seq (const struct whc *whc); +seqno_t whc_max_seq (const struct whc *whc); +seqno_t whc_next_seq (const struct whc *whc, seqno_t seq); +size_t whc_unacked_bytes (struct whc *whc); + +struct whc_node *whc_findseq (const struct whc *whc, seqno_t seq); +struct whc_node *whc_findmax (const struct whc *whc); +struct whc_node *whc_findkey (const struct whc *whc, const struct serdata *serdata_key); + +struct whc_node *whc_next_node (const struct whc *whc, seqno_t seq); + +/* min_seq is lowest sequence number that must be retained because of + reliable readers that have not acknowledged all data */ +/* max_drop_seq must go soon, it's way too ugly. */ +/* plist may be NULL or os_malloc'd, WHC takes ownership of plist */ +int whc_insert (struct whc *whc, seqno_t max_drop_seq, seqno_t seq, struct nn_plist *plist, struct serdata *serdata, struct tkmap_instance *tk); +void whc_downgrade_to_volatile (struct whc *whc); +unsigned whc_remove_acked_messages (struct whc *whc, seqno_t max_drop_seq, struct whc_node **deferred_free_list); +void whc_free_deferred_free_list (struct whc *whc, struct whc_node *deferred_free_list); + +#if defined (__cplusplus) +} +#endif + +#endif /* Q_WHC_H */ diff --git a/src/core/ddsi/include/ddsi/q_xevent.h b/src/core/ddsi/include/ddsi/q_xevent.h new file mode 100644 index 0000000..497b8eb --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_xevent.h @@ -0,0 +1,73 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_XEVENT_H +#define NN_XEVENT_H + +#if defined (__cplusplus) +extern "C" { +#endif + +/* NOTE: xevents scheduled with the same tsched used to always be + executed in the order of scheduling, but that is no longer true. + With the messages now via the "untimed" path, that should not + introduce any issues. */ + +struct writer; +struct pwr_rd_match; +struct participant; +struct proxy_participant; +struct ddsi_tran_conn; +struct xevent; +struct xeventq; +struct proxy_writer; +struct proxy_reader; + +struct xeventq *xeventq_new +( + struct ddsi_tran_conn * conn, + size_t max_queued_rexmit_bytes, + size_t max_queued_rexmit_msgs, + uint32_t auxiliary_bandwidth_limit +); + +/* xeventq_free calls callback handlers with t = T_NEVER, at which point they are required to free + whatever memory is claimed for the argument and call delete_xevent. */ +void xeventq_free (struct xeventq *evq); +int xeventq_start (struct xeventq *evq, const char *name); /* <0 => error, =0 => ok */ +void xeventq_stop (struct xeventq *evq); + +void qxev_msg (struct xeventq *evq, struct nn_xmsg *msg); +void qxev_pwr_entityid (struct proxy_writer * pwr, nn_guid_prefix_t * id); +void qxev_prd_entityid (struct proxy_reader * prd, nn_guid_prefix_t * id); + +/* Returns 1 if queued, 0 otherwise (no point in returning the + event, you can't do anything with it anyway) */ +int qxev_msg_rexmit_wrlock_held (struct xeventq *evq, struct nn_xmsg *msg, int force); + +/* All of the following lock EVQ for the duration of the operation */ +void delete_xevent (struct xevent *ev); +int resched_xevent_if_earlier (struct xevent *ev, nn_mtime_t tsched); + +struct xevent *qxev_heartbeat (struct xeventq *evq, nn_mtime_t tsched, const nn_guid_t *wr_guid); +struct xevent *qxev_acknack (struct xeventq *evq, nn_mtime_t tsched, const nn_guid_t *pwr_guid, const nn_guid_t *rd_guid); +struct xevent *qxev_spdp (nn_mtime_t tsched, const nn_guid_t *pp_guid, const nn_guid_t *proxypp_guid); +struct xevent *qxev_pmd_update (nn_mtime_t tsched, const nn_guid_t *pp_guid); +struct xevent *qxev_end_startup_mode (nn_mtime_t tsched); +struct xevent *qxev_delete_writer (nn_mtime_t tsched, const nn_guid_t *guid); + +/* cb will be called with now = T_NEVER if the event is still enqueued when when xeventq_free starts cleaning up */ +struct xevent *qxev_callback (nn_mtime_t tsched, void (*cb) (struct xevent *xev, void *arg, nn_mtime_t now), void *arg); + +#if defined (__cplusplus) +} +#endif +#endif /* NN_XEVENT_H */ diff --git a/src/core/ddsi/include/ddsi/q_xmsg.h b/src/core/ddsi/include/ddsi/q_xmsg.h new file mode 100644 index 0000000..4e9ed19 --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_xmsg.h @@ -0,0 +1,158 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_XMSG_H +#define NN_XMSG_H + +#include + +#include "ddsi/q_protocol.h" /* for, e.g., SubmessageKind_t */ +#include "ddsi/q_xqos.h" /* for, e.g., octetseq, stringseq */ +#include "ddsi/ddsi_tran.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +struct serdata; +struct addrset; +struct proxy_reader; +struct proxy_writer; + +struct nn_prismtech_participant_version_info; +struct nn_prismtech_writer_info; +struct nn_xmsgpool; +struct nn_xmsg_data; +struct nn_xmsg; +struct nn_xpack; + +struct nn_xmsg_marker { + size_t offset; +}; + +enum nn_xmsg_kind { + NN_XMSG_KIND_CONTROL, + NN_XMSG_KIND_DATA, + NN_XMSG_KIND_DATA_REXMIT +}; + +/* XMSGPOOL */ + +struct nn_xmsgpool *nn_xmsgpool_new (void); +void nn_xmsgpool_free (struct nn_xmsgpool *pool); + +/* XMSG */ + +/* To allocate a new xmsg from the pool; if expected_size is NOT + exceeded, no reallocs will be performed, else the address of the + xmsg may change because of reallocing when appending to it. */ +struct nn_xmsg *nn_xmsg_new (struct nn_xmsgpool *pool, const nn_guid_prefix_t *src_guid_prefix, size_t expected_size, enum nn_xmsg_kind kind); + +/* For sending to a particular destination (participant) */ +void nn_xmsg_setdst1 (struct nn_xmsg *m, const nn_guid_prefix_t *gp, const nn_locator_t *addr); + +/* For sending to a particular proxy reader; this is a convenience + routine that extracts a suitable address from the proxy reader's + address sets and calls setdst1. */ +int nn_xmsg_setdstPRD (struct nn_xmsg *m, const struct proxy_reader *prd); +int nn_xmsg_setdstPWR (struct nn_xmsg *m, const struct proxy_writer *pwr); + +/* For sending to all in the address set AS -- typically, the writer's + address set to multicast to all matched readers */ +void nn_xmsg_setdstN (struct nn_xmsg *msg, struct addrset *as, struct addrset *as_group); + +int nn_xmsg_setmaxdelay (struct nn_xmsg *msg, int64_t maxdelay); + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS +int nn_xmsg_setencoderid (struct nn_xmsg *msg, uint32_t encoderid); +#endif + +/* Sets the location of the destination readerId within the message + (address changes because of reallocations are handled correctly). + M must be a rexmit, and for all rexmits this must be called. It is + a separate function because the location may only become known at a + late-ish stage in the construction of the message. */ +void nn_xmsg_set_data_readerId (struct nn_xmsg *m, nn_entityid_t *readerId); + +/* If M and MADD are both xmsg's containing the same retransmit + message, this will merge the destination embedded in MADD into M. + Typically, this will cause the readerId of M to be cleared and the + destination to change to the writer's address set. + + M and MADD *must* contain the same sample/fragment of a sample. + + Returns 1 if merge was successful, else 0. On failure, neither + message will have been changed and both should be sent as if there + had been no merging. */ +int nn_xmsg_merge_rexmit_destinations_wrlock_held (struct nn_xmsg *m, const struct nn_xmsg *madd); + +/* To set writer ids for updating last transmitted sequence number; + wrfragid is 0 based, unlike DDSI but like other places where + fragment numbers are handled internally. */ +void nn_xmsg_setwriterseq (struct nn_xmsg *msg, const nn_guid_t *wrguid, seqno_t wrseq); +void nn_xmsg_setwriterseq_fragid (struct nn_xmsg *msg, const nn_guid_t *wrguid, seqno_t wrseq, nn_fragment_number_t wrfragid); + +/* Comparison function for retransmits: orders messages on writer + guid, sequence number and fragment id */ +int nn_xmsg_compare_fragid (const struct nn_xmsg *a, const struct nn_xmsg *b); + +void nn_xmsg_free (struct nn_xmsg *msg); +size_t nn_xmsg_size (const struct nn_xmsg *m); +void *nn_xmsg_payload (size_t *sz, struct nn_xmsg *m); +enum nn_xmsg_kind nn_xmsg_kind (const struct nn_xmsg *m); +void nn_xmsg_guid_seq_fragid (const struct nn_xmsg *m, nn_guid_t *wrguid, seqno_t *wrseq, nn_fragment_number_t *wrfragid); + +void *nn_xmsg_submsg_from_marker (struct nn_xmsg *msg, struct nn_xmsg_marker marker); +void *nn_xmsg_append (struct nn_xmsg *m, struct nn_xmsg_marker *marker, size_t sz); +void nn_xmsg_shrink (struct nn_xmsg *m, struct nn_xmsg_marker marker, size_t sz); +void nn_xmsg_serdata (struct nn_xmsg *m, struct serdata *serdata, unsigned off, unsigned len); +void nn_xmsg_submsg_setnext (struct nn_xmsg *msg, struct nn_xmsg_marker marker); +void nn_xmsg_submsg_init (struct nn_xmsg *msg, struct nn_xmsg_marker marker, SubmessageKind_t smkind); +void nn_xmsg_add_timestamp (struct nn_xmsg *m, nn_wctime_t t); +void nn_xmsg_add_entityid (struct nn_xmsg * m); +void *nn_xmsg_addpar (struct nn_xmsg *m, unsigned pid, size_t len); +void nn_xmsg_addpar_string (struct nn_xmsg *m, unsigned pid, const char *str); +void nn_xmsg_addpar_octetseq (struct nn_xmsg *m, unsigned pid, const nn_octetseq_t *oseq); +void nn_xmsg_addpar_stringseq (struct nn_xmsg *m, unsigned pid, const nn_stringseq_t *sseq); +void nn_xmsg_addpar_guid (struct nn_xmsg *m, unsigned pid, const nn_guid_t *guid); +void nn_xmsg_addpar_BE4u (struct nn_xmsg *m, unsigned pid, unsigned x); +void nn_xmsg_addpar_4u (struct nn_xmsg *m, unsigned pid, unsigned x); +void nn_xmsg_addpar_keyhash (struct nn_xmsg *m, const struct serdata *serdata); +void nn_xmsg_addpar_statusinfo (struct nn_xmsg *m, unsigned statusinfo); +void nn_xmsg_addpar_reliability (struct nn_xmsg *m, unsigned pid, const struct nn_reliability_qospolicy *rq); +void nn_xmsg_addpar_share (struct nn_xmsg *m, unsigned pid, const struct nn_share_qospolicy *rq); +void nn_xmsg_addpar_subscription_keys (struct nn_xmsg *m, unsigned pid, const struct nn_subscription_keys_qospolicy *rq); + +void nn_xmsg_addpar_parvinfo (struct nn_xmsg *m, unsigned pid, const struct nn_prismtech_participant_version_info *pvi); +void nn_xmsg_addpar_eotinfo (struct nn_xmsg *m, unsigned pid, const struct nn_prismtech_eotinfo *txnid); +void nn_xmsg_addpar_dataholder (_In_ struct nn_xmsg *m, _In_ unsigned pid, _In_ const struct nn_dataholder *dh); +void nn_xmsg_addpar_sentinel (struct nn_xmsg *m); +int nn_xmsg_addpar_sentinel_ifparam (struct nn_xmsg *m); + +/* XPACK */ + +struct nn_xpack * nn_xpack_new (ddsi_tran_conn_t conn, uint32_t bw_limit, bool async_mode); +void nn_xpack_free (struct nn_xpack *xp); +void nn_xpack_send (struct nn_xpack *xp, bool immediately /* unused */); +int nn_xpack_addmsg (struct nn_xpack *xp, struct nn_xmsg *m, const uint32_t flags); +int64_t nn_xpack_maxdelay (const struct nn_xpack *xp); +unsigned nn_xpack_packetid (const struct nn_xpack *xp); + +/* SENDQ */ +void nn_xpack_sendq_init (void); +void nn_xpack_sendq_start (void); +void nn_xpack_sendq_stop (void); +void nn_xpack_sendq_fini (void); + +#if defined (__cplusplus) +} +#endif +#endif /* NN_XMSG_H */ diff --git a/src/core/ddsi/include/ddsi/q_xqos.h b/src/core/ddsi/include/ddsi/q_xqos.h new file mode 100644 index 0000000..8e2679b --- /dev/null +++ b/src/core/ddsi/include/ddsi/q_xqos.h @@ -0,0 +1,349 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef NN_XQOS_H +#define NN_XQOS_H + +/*XXX*/ +#include "ddsi/q_protocol.h" +#include "ddsi/q_rtps.h" +/*XXX*/ +#include "ddsi/q_log.h" + +#if defined (__cplusplus) +extern "C" { +#endif + +#define NN_DDS_LENGTH_UNLIMITED -1 + +typedef struct nn_octetseq { + unsigned length; + unsigned char *value; +} nn_octetseq_t; + +typedef nn_octetseq_t nn_userdata_qospolicy_t; +typedef nn_octetseq_t nn_topicdata_qospolicy_t; +typedef nn_octetseq_t nn_groupdata_qospolicy_t; + +typedef struct nn_property { + char *name; + char *value; + bool propagate; +} nn_property_t; + +typedef struct nn_propertyseq { + unsigned n; + nn_property_t *props; +} nn_propertyseq_t; + +typedef struct nn_binaryproperty { + char *name; + nn_octetseq_t value; + bool propagate; +} nn_binaryproperty_t; + +typedef struct nn_binarypropertyseq { + unsigned n; + nn_binaryproperty_t *props; +} nn_binarypropertyseq_t; + +typedef struct nn_property_qospolicy { + nn_propertyseq_t value; + nn_binarypropertyseq_t binary_value; +} nn_property_qospolicy_t; + +typedef enum nn_durability_kind { + NN_VOLATILE_DURABILITY_QOS, + NN_TRANSIENT_LOCAL_DURABILITY_QOS, + NN_TRANSIENT_DURABILITY_QOS, + NN_PERSISTENT_DURABILITY_QOS +} nn_durability_kind_t; + +typedef struct nn_durability_qospolicy { + nn_durability_kind_t kind; +} nn_durability_qospolicy_t; + +typedef enum nn_history_kind { + NN_KEEP_LAST_HISTORY_QOS, + NN_KEEP_ALL_HISTORY_QOS +} nn_history_kind_t; + +typedef struct nn_history_qospolicy { + nn_history_kind_t kind; + int depth; +} nn_history_qospolicy_t; + +typedef struct nn_resource_limits_qospolicy { + int max_samples; + int max_instances; + int max_samples_per_instance; +} nn_resource_limits_qospolicy_t; + +typedef struct nn_durability_service_qospolicy { + nn_duration_t service_cleanup_delay; + nn_history_qospolicy_t history; + nn_resource_limits_qospolicy_t resource_limits; +} nn_durability_service_qospolicy_t; + +typedef enum nn_presentation_access_scope_kind { + NN_INSTANCE_PRESENTATION_QOS, + NN_TOPIC_PRESENTATION_QOS, + NN_GROUP_PRESENTATION_QOS +} nn_presentation_access_scope_kind_t; + +typedef struct nn_presentation_qospolicy { + nn_presentation_access_scope_kind_t access_scope; + unsigned char coherent_access; + unsigned char ordered_access; +} nn_presentation_qospolicy_t; + +typedef struct nn_deadline_qospolicy { + nn_duration_t deadline; +} nn_deadline_qospolicy_t; + +typedef struct nn_latency_budget_qospolicy { + nn_duration_t duration; +} nn_latency_budget_qospolicy_t; + +typedef enum nn_ownership_kind { + NN_SHARED_OWNERSHIP_QOS, + NN_EXCLUSIVE_OWNERSHIP_QOS +} nn_ownership_kind_t; + +typedef struct nn_ownership_qospolicy { + nn_ownership_kind_t kind; +} nn_ownership_qospolicy_t; + +typedef struct nn_ownership_strength_qospolicy { + int value; +} nn_ownership_strength_qospolicy_t; + +typedef enum nn_liveliness_kind { + NN_AUTOMATIC_LIVELINESS_QOS, + NN_MANUAL_BY_PARTICIPANT_LIVELINESS_QOS, + NN_MANUAL_BY_TOPIC_LIVELINESS_QOS +} nn_liveliness_kind_t; + +typedef struct nn_liveliness_qospolicy { + nn_liveliness_kind_t kind; + nn_duration_t lease_duration; +} nn_liveliness_qospolicy_t; + +typedef struct nn_time_based_filter_qospolicy { + nn_duration_t minimum_separation; +} nn_time_based_filter_qospolicy_t; + +typedef struct nn_stringseq { + unsigned n; + char **strs; +} nn_stringseq_t; + +typedef nn_stringseq_t nn_partition_qospolicy_t; + +typedef enum nn_reliability_kind { + NN_BEST_EFFORT_RELIABILITY_QOS, + NN_RELIABLE_RELIABILITY_QOS +} nn_reliability_kind_t; + +typedef struct nn_reliability_qospolicy { + nn_reliability_kind_t kind; + nn_duration_t max_blocking_time; +} nn_reliability_qospolicy_t; + +typedef struct nn_external_reliability_qospolicy { + int kind; + nn_duration_t max_blocking_time; +} nn_external_reliability_qospolicy_t; + +#define NN_PEDANTIC_BEST_EFFORT_RELIABILITY_QOS 1 +#define NN_PEDANTIC_RELIABLE_RELIABILITY_QOS 3 /* <= see DDSI 2.1, table 9.4 */ +#define NN_INTEROP_BEST_EFFORT_RELIABILITY_QOS 1 +#define NN_INTEROP_RELIABLE_RELIABILITY_QOS 2 + +typedef struct nn_transport_priority_qospolicy { + int value; +} nn_transport_priority_qospolicy_t; + +typedef struct nn_lifespan_qospolicy { + nn_duration_t duration; +} nn_lifespan_qospolicy_t; + +typedef enum nn_destination_order_kind { + NN_BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS, + NN_BY_SOURCE_TIMESTAMP_DESTINATIONORDER_QOS +} nn_destination_order_kind_t; + +typedef struct nn_destination_order_qospolicy { + nn_destination_order_kind_t kind; +} nn_destination_order_qospolicy_t; + +typedef struct nn_entity_factory_qospolicy { + unsigned char autoenable_created_entities; +} nn_entity_factory_qospolicy_t; + +typedef struct nn_writer_data_lifecycle_qospolicy { + unsigned char autodispose_unregistered_instances; + nn_duration_t autopurge_suspended_samples_delay; /* OpenSplice extension */ + nn_duration_t autounregister_instance_delay; /* OpenSplice extension */ +} nn_writer_data_lifecycle_qospolicy_t; + +typedef enum nn_invalid_sample_visibility_kind { + NN_NO_INVALID_SAMPLE_VISIBILITY_QOS, + NN_MINIMUM_INVALID_SAMPLE_VISIBILITY_QOS, + NN_ALL_INVALID_SAMPLE_VISIBILITY_QOS +} nn_invalid_sample_visibility_kind_t; + +typedef struct nn_reader_data_lifecycle_qospolicy { + nn_duration_t autopurge_nowriter_samples_delay; + nn_duration_t autopurge_disposed_samples_delay; + unsigned char autopurge_dispose_all; /* OpenSplice extension */ + unsigned char enable_invalid_samples; /* OpenSplice extension */ + nn_invalid_sample_visibility_kind_t invalid_sample_visibility; /* OpenSplice extension */ +} nn_reader_data_lifecycle_qospolicy_t; + +typedef struct nn_synchronous_endpoint_qospolicy { + unsigned char value; +} nn_synchronous_endpoint_qospolicy_t; + +typedef struct nn_relaxed_qos_matching_qospolicy { + unsigned char value; +} nn_relaxed_qos_matching_qospolicy_t; + +typedef struct nn_subscription_keys_qospolicy { + unsigned char use_key_list; + nn_stringseq_t key_list; +} nn_subscription_keys_qospolicy_t; + +typedef struct nn_reader_lifespan_qospolicy { + unsigned char use_lifespan; + nn_duration_t duration; +} nn_reader_lifespan_qospolicy_t; + +typedef struct nn_share_qospolicy { + unsigned char enable; + char *name; +} nn_share_qospolicy_t; + +/***/ + +/* Qos Present bit indices */ +#define QP_TOPIC_NAME ((uint64_t)1 << 0) +#define QP_TYPE_NAME ((uint64_t)1 << 1) +#define QP_PRESENTATION ((uint64_t)1 << 2) +#define QP_PARTITION ((uint64_t)1 << 3) +#define QP_GROUP_DATA ((uint64_t)1 << 4) +#define QP_TOPIC_DATA ((uint64_t)1 << 5) +#define QP_DURABILITY ((uint64_t)1 << 6) +#define QP_DURABILITY_SERVICE ((uint64_t)1 << 7) +#define QP_DEADLINE ((uint64_t)1 << 8) +#define QP_LATENCY_BUDGET ((uint64_t)1 << 9) +#define QP_LIVELINESS ((uint64_t)1 << 10) +#define QP_RELIABILITY ((uint64_t)1 << 11) +#define QP_DESTINATION_ORDER ((uint64_t)1 << 12) +#define QP_HISTORY ((uint64_t)1 << 13) +#define QP_RESOURCE_LIMITS ((uint64_t)1 << 14) +#define QP_TRANSPORT_PRIORITY ((uint64_t)1 << 15) +#define QP_LIFESPAN ((uint64_t)1 << 16) +#define QP_USER_DATA ((uint64_t)1 << 17) +#define QP_OWNERSHIP ((uint64_t)1 << 18) +#define QP_OWNERSHIP_STRENGTH ((uint64_t)1 << 19) +#define QP_TIME_BASED_FILTER ((uint64_t)1 << 20) +#define QP_PRISMTECH_WRITER_DATA_LIFECYCLE ((uint64_t)1 << 21) +#define QP_PRISMTECH_READER_DATA_LIFECYCLE ((uint64_t)1 << 22) +#define QP_PRISMTECH_RELAXED_QOS_MATCHING ((uint64_t)1 << 23) +#define QP_PRISMTECH_READER_LIFESPAN ((uint64_t)1 << 24) +#define QP_PRISMTECH_SUBSCRIPTION_KEYS ((uint64_t)1 << 25) +#define QP_PRISMTECH_ENTITY_FACTORY ((uint64_t)1 << 27) +#define QP_PRISMTECH_SYNCHRONOUS_ENDPOINT ((uint64_t)1 << 28) +#define QP_RTI_TYPECODE ((uint64_t)1 << 29) +#define QP_PROPERTY ((uint64_t)1 << 30) + +/* Partition QoS is not RxO according to the specification (DDS 1.2, + section 7.1.3), but communication will not take place unless it + matches. Same for topic and type. Relaxed qos matching is a bit of + a weird one, but it affects matching, so ... */ +#define QP_RXO_MASK (QP_DURABILITY | QP_PRESENTATION | QP_DEADLINE | QP_LATENCY_BUDGET | QP_OWNERSHIP | QP_LIVELINESS | QP_RELIABILITY | QP_DESTINATION_ORDER | QP_PRISMTECH_RELAXED_QOS_MATCHING | QP_PRISMTECH_SYNCHRONOUS_ENDPOINT) +#define QP_CHANGEABLE_MASK (QP_USER_DATA | QP_TOPIC_DATA | QP_GROUP_DATA | QP_DEADLINE | QP_LATENCY_BUDGET | QP_OWNERSHIP_STRENGTH | QP_TIME_BASED_FILTER | QP_PARTITION | QP_TRANSPORT_PRIORITY | QP_LIFESPAN | QP_PRISMTECH_ENTITY_FACTORY | QP_PRISMTECH_WRITER_DATA_LIFECYCLE | QP_PRISMTECH_READER_DATA_LIFECYCLE) +#define QP_UNRECOGNIZED_INCOMPATIBLE_MASK (QP_PRISMTECH_RELAXED_QOS_MATCHING) + +/* readers & writers have an extended qos, hence why it is a separate + type */ +typedef struct nn_xqos { + /* Entries present, for sparse QoS */ + uint64_t present; + uint64_t aliased; + + /*v---- in ...Qos + v--- in ...BuiltinTopicData + v-- mapped in DDSI + v- reader/writer/publisher/subscriber/participant specific */ + /* Extras: */ + /* xx */char *topic_name; + /* xx */char *type_name; + /* PublisherQos, SubscriberQos: */ + /*xxx */nn_presentation_qospolicy_t presentation; + /*xxx */nn_partition_qospolicy_t partition; + /*xxx */nn_groupdata_qospolicy_t group_data; + /*x xX*/nn_entity_factory_qospolicy_t entity_factory; + /* TopicQos: */ + /*xxx */nn_topicdata_qospolicy_t topic_data; + /* DataWriterQos, DataReaderQos: */ + /*xxx */nn_durability_qospolicy_t durability; + /*xxx */nn_durability_service_qospolicy_t durability_service; + /*xxx */nn_deadline_qospolicy_t deadline; + /*xxx */nn_latency_budget_qospolicy_t latency_budget; + /*xxx */nn_liveliness_qospolicy_t liveliness; + /*xxx */nn_reliability_qospolicy_t reliability; + /*xxx */nn_destination_order_qospolicy_t destination_order; + /*x x */nn_history_qospolicy_t history; + /*x x */nn_resource_limits_qospolicy_t resource_limits; + /*x x */nn_transport_priority_qospolicy_t transport_priority; + /*xxx */nn_lifespan_qospolicy_t lifespan; + /*xxx */nn_userdata_qospolicy_t user_data; + /*xxx */nn_ownership_qospolicy_t ownership; + /*xxxW*/nn_ownership_strength_qospolicy_t ownership_strength; + /*xxxR*/nn_time_based_filter_qospolicy_t time_based_filter; + /*x W*/nn_writer_data_lifecycle_qospolicy_t writer_data_lifecycle; + /*x xR*/nn_reader_data_lifecycle_qospolicy_t reader_data_lifecycle; + /*x x */nn_relaxed_qos_matching_qospolicy_t relaxed_qos_matching; + /*x xR*/nn_subscription_keys_qospolicy_t subscription_keys; + /*x xR*/nn_reader_lifespan_qospolicy_t reader_lifespan; + /*x xR*/nn_share_qospolicy_t share; + /*xxx */nn_synchronous_endpoint_qospolicy_t synchronous_endpoint; + + /*xxx */nn_property_qospolicy_t property; + + /* X*/nn_octetseq_t rti_typecode; +} nn_xqos_t; + +struct nn_xmsg; + +void nn_xqos_init_empty (nn_xqos_t *xqos); +void nn_xqos_init_default_reader (nn_xqos_t *xqos); +void nn_xqos_init_default_writer (nn_xqos_t *xqos); +void nn_xqos_init_default_writer_noautodispose (nn_xqos_t *xqos); +void nn_xqos_init_default_subscriber (nn_xqos_t *xqos); +void nn_xqos_init_default_publisher (nn_xqos_t *xqos); +void nn_xqos_init_default_topic (nn_xqos_t *xqos); +void nn_xqos_copy (nn_xqos_t *dst, const nn_xqos_t *src); +void nn_xqos_unalias (nn_xqos_t *xqos); +void nn_xqos_fini (nn_xqos_t *xqos); +void nn_xqos_mergein_missing (nn_xqos_t *a, const nn_xqos_t *b); +uint64_t nn_xqos_delta (const nn_xqos_t *a, const nn_xqos_t *b, uint64_t mask); +void nn_xqos_addtomsg (struct nn_xmsg *m, const nn_xqos_t *xqos, uint64_t wanted); +void nn_log_xqos (logcat_t cat, const nn_xqos_t *xqos); +nn_xqos_t *nn_xqos_dup (const nn_xqos_t *src); + +#if defined (__cplusplus) +} +#endif + +#endif /* NN_XQOS_H */ diff --git a/src/core/ddsi/include/ddsi/sysdeps.h b/src/core/ddsi/include/ddsi/sysdeps.h new file mode 100644 index 0000000..a333f34 --- /dev/null +++ b/src/core/ddsi/include/ddsi/sysdeps.h @@ -0,0 +1,195 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifndef SYSDEPS_H +#define SYSDEPS_H + +#include "os/os.h" + +#include "ddsi/q_inline.h" + +#ifndef os_sockECONNRESET +#ifdef WSAECONNRESET +#define os_sockECONNRESET WSAECONNRESET +#else +#define os_sockECONNRESET ECONNRESET +#endif +#endif + +#ifndef os_sockEPERM +#ifdef WSAEACCES +#define os_sockEPERM WSAEACCES +#else +#define os_sockEPERM EPERM +#endif +#endif + +#if defined (__linux) || defined (__sun) || defined (__APPLE__) || defined (INTEGRITY) || defined (AIX) || defined (OS_RTEMS_DEFS_H) || defined (__VXWORKS__) || defined (__Lynx__) || defined (__linux__) || defined (OS_QNX_DEFS_H) +#define SYSDEPS_HAVE_MSGHDR 1 +#define SYSDEPS_HAVE_RECVMSG 1 +#define SYSDEPS_HAVE_SENDMSG 1 +#endif + +#if defined (__linux) || defined (__sun) || defined (__APPLE__) || defined (INTEGRITY) || defined (AIX) || defined (OS_RTEMS_DEFS_H) || defined (__VXWORKS__) || defined (__linux__) || defined (OS_QNX_DEFS_H) +#define SYSDEPS_HAVE_IOVEC 1 +#endif + +#if defined (__linux) || defined (__sun) || defined (__APPLE__) || defined (AIX) || defined (__Lynx__) || defined (OS_QNX_DEFS_H) +#define SYSDEPS_HAVE_RANDOM 1 +#include +#endif + +#if defined (__linux) || defined (__sun) || defined (__APPLE__) || defined (AIX) || defined (OS_QNX_DEFS_H) +#define SYSDEPS_HAVE_GETRUSAGE 1 +#include /* needed for Linux, exists on all four */ +#include /* needed for AIX, exists on all four */ +#include +#endif + +#if defined (__linux) && defined (CLOCK_THREAD_CPUTIME_ID) +#define SYSDEPS_HAVE_CLOCK_THREAD_CPUTIME 1 +#endif + +#if defined (INTEGRITY) +#include +#include +#endif + +#if defined (VXWORKS_RTP) +#include +#endif + +#if defined (_WIN32) +typedef SOCKET os_handle; +#define Q_VALID_SOCKET(s) ((s) != INVALID_SOCKET) +#define Q_INVALID_SOCKET INVALID_SOCKET +#else /* All Unixes have socket() return -1 on error */ +typedef int os_handle; +#define Q_VALID_SOCKET(s) ((s) != -1) +#define Q_INVALID_SOCKET -1 +#endif + +/* From MSDN: from Vista & 2k3 onwards, a macro named MemoryBarrier is + defined, XP needs inline assembly. Unfortunately, MemoryBarrier() + is a function on x86 ... + + Definition below is taken from the MSDN page on MemoryBarrier() */ +#ifndef MemoryBarrier +#if NTDDI_VERSION >= NTDDI_WS03 && defined _M_IX86 +#define MemoryBarrier() do { \ + LONG Barrier; \ + __asm { \ + xchg Barrier, eax \ + } \ + } while (0) +#endif /* x86 */ + +/* Don't try interworking with thumb - one thing at a time. Do a DMB + SY if supported, else no need for a memory barrier. (I think.) */ +#if defined _M_ARM && ! defined _M_ARMT +#define MemoryBarrierARM __emit (0xf57ff05f) /* 0xf57ff05f or 0x5ff07ff5 */ +#if _M_ARM > 7 +/* if targetting ARMv7 the dmb instruction is available */ +#define MemoryBarrier() MemoryBarrierARM +#else +/* else conditional on actual hardware platform */ +extern void (*q_maybe_membar) (void); +#define MemoryBarrier() q_maybe_membar () +#define NEED_ARM_MEMBAR_SUPPORT 1 +#endif /* ARM version */ +#endif /* ARM */ + +#endif /* !def MemoryBarrier */ + +#if defined (__sun) && !defined (_XPG4_2) +#define SYSDEPS_MSGHDR_ACCRIGHTS 1 +#else +#define SYSDEPS_MSGHDR_ACCRIGHTS 0 +#endif +#if SYSDEPS_MSGHDR_ACCRIGHTS +#define SYSDEPS_MSGHDR_FLAGS 0 +#else +#define SYSDEPS_MSGHDR_FLAGS 1 +#endif + +#if defined (__cplusplus) +} +#endif + +#if defined (__cplusplus) +extern "C" { +#endif + +#define ASSERT_RDLOCK_HELD(x) ((void) 0) +#define ASSERT_WRLOCK_HELD(x) ((void) 0) +#define ASSERT_MUTEX_HELD(x) ((void) 0) + +#if ! SYSDEPS_HAVE_IOVEC +struct iovec { + void *iov_base; + size_t iov_len; +}; +#endif + +#if ! SYSDEPS_HAVE_MSGHDR +struct msghdr +{ + void *msg_name; + socklen_t msg_namelen; + struct iovec *msg_iov; + size_t msg_iovlen; + void *msg_control; + size_t msg_controllen; + int msg_flags; +}; +#endif + +#ifndef MSG_TRUNC +#define MSG_TRUNC 1 +#endif + +#if ! SYSDEPS_HAVE_RECVMSG +/* Only implements iovec of length 1, no control */ +ssize_t recvmsg (os_handle fd, struct msghdr *message, int flags); +#endif +#if ! SYSDEPS_HAVE_SENDMSG +ssize_t sendmsg (os_handle fd, const struct msghdr *message, int flags); +#endif +#if ! SYSDEPS_HAVE_RANDOM +long random (void); +#endif + +int64_t get_thread_cputime (void); + +int os_threadEqual (os_threadId a, os_threadId b); +void log_stacktrace (const char *name, os_threadId tid); + +#if (_LP64 && __GCC_HAVE_SYNC_COMPARE_AND_SWAP_16) || (!_LP64 && __GCC_HAVE_SYNC_COMPARE_AND_SWAP_8) + #define HAVE_ATOMIC_LIFO 1 + #if _LP64 + typedef union { __int128 x; struct { uintptr_t a, b; } s; } os_atomic_uintptr2_t; + #else + typedef union { uint64_t x; struct { uintptr_t a, b; } s; } os_atomic_uintptr2_t; + #endif + typedef struct os_atomic_lifo { + os_atomic_uintptr2_t aba_head; + } os_atomic_lifo_t; + void os_atomic_lifo_init (os_atomic_lifo_t *head); + void os_atomic_lifo_push (os_atomic_lifo_t *head, void *elem, size_t linkoff); + void os_atomic_lifo_pushmany (os_atomic_lifo_t *head, void *first, void *last, size_t linkoff); + void *os_atomic_lifo_pop (os_atomic_lifo_t *head, size_t linkoff); +#endif + +#if defined (__cplusplus) +} +#endif + +#endif /* SYSDEPS_H */ diff --git a/src/core/ddsi/src/ddsi_ser.c b/src/core/ddsi/src/ddsi_ser.c new file mode 100644 index 0000000..eef360e --- /dev/null +++ b/src/core/ddsi/src/ddsi_ser.c @@ -0,0 +1,226 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsi/ddsi_ser.h" + +#include +#include +#include +#include + +#include "os/os_stdlib.h" +#include "os/os_defs.h" +#include "os/os_thread.h" +#include "os/os_heap.h" +#include "os/os_atomics.h" +#include "ddsi/sysdeps.h" +#include "ddsi/q_md5.h" +#include "ddsi/q_bswap.h" +#include "ddsi/q_config.h" +#include "ddsi/q_freelist.h" +#include "q__osplser.h" + +#define MAX_POOL_SIZE 16384 + +#ifndef NDEBUG +static int ispowerof2_size (size_t x) +{ + return x > 0 && !(x & (x-1)); +} +#endif + +static size_t alignup_size (size_t x, size_t a); +static serstate_t serstate_allocnew (serstatepool_t pool, const struct sertopic * topic); + +serstatepool_t ddsi_serstatepool_new (void) +{ + serstatepool_t pool; + pool = os_malloc (sizeof (*pool)); + nn_freelist_init (&pool->freelist, MAX_POOL_SIZE, offsetof (struct serstate, next)); + return pool; +} + +static void serstate_free_wrap (void *elem) +{ + serstate_free (elem); +} + +void ddsi_serstatepool_free (serstatepool_t pool) +{ + nn_freelist_fini (&pool->freelist, serstate_free_wrap); + TRACE (("ddsi_serstatepool_free(%p)\n", pool)); + os_free (pool); +} + +int ddsi_serdata_refcount_is_1 (serdata_t serdata) +{ + return (os_atomic_ld32 (&serdata->v.st->refcount) == 1); +} + +serdata_t ddsi_serdata_ref (serdata_t serdata) +{ + os_atomic_inc32 (&serdata->v.st->refcount); + return serdata; +} + +void ddsi_serdata_unref (serdata_t serdata) +{ + ddsi_serstate_release (serdata->v.st); +} + +nn_mtime_t ddsi_serdata_twrite (const struct serdata *serdata) +{ + return ddsi_serstate_twrite (serdata->v.st); +} + +void ddsi_serdata_set_twrite (serdata_t serdata, nn_mtime_t twrite) +{ + ddsi_serstate_set_twrite (serdata->v.st, twrite); +} + +serstate_t ddsi_serstate_new (serstatepool_t pool, const struct sertopic * topic) +{ + serstate_t st; + if ((st = nn_freelist_pop (&pool->freelist)) != NULL) + serstate_init (st, topic); + else + st = serstate_allocnew (pool, topic); + return st; +} + +serdata_t ddsi_serstate_fix (serstate_t st) +{ + /* see serialize_raw_private() */ + ddsi_serstate_append_aligned (st, 0, 4); + return st->data; +} + +nn_mtime_t ddsi_serstate_twrite (const struct serstate *serstate) +{ + assert (serstate->twrite.v >= 0); + return serstate->twrite; +} + +void ddsi_serstate_set_twrite (serstate_t st, nn_mtime_t twrite) +{ + st->twrite = twrite; +} + +void ddsi_serstate_append_blob (serstate_t st, size_t align, size_t sz, const void *data) +{ + char *p = ddsi_serstate_append_aligned (st, sz, align); + memcpy (p, data, sz); +} + +void ddsi_serstate_set_msginfo +( + serstate_t st, unsigned statusinfo, nn_wctime_t timestamp, + void * dummy +) +{ + serdata_t d = st->data; + d->v.msginfo.statusinfo = statusinfo; + d->v.msginfo.timestamp = timestamp; +} + +uint32_t ddsi_serdata_size (const struct serdata *serdata) +{ + const struct serstate *st = serdata->v.st; + if (serdata->v.st->kind == STK_EMPTY) + return 0; + else + return (uint32_t) (sizeof (struct CDRHeader) + st->pos); +} + +int ddsi_serdata_is_key (const struct serdata * serdata) +{ + return serdata->v.st->kind == STK_KEY; +} + +int ddsi_serdata_is_empty (const struct serdata * serdata) +{ + return serdata->v.st->kind == STK_EMPTY; +} + +/* Internal static functions */ + +static serstate_t serstate_allocnew (serstatepool_t pool, const struct sertopic * topic) +{ + serstate_t st = os_malloc (sizeof (*st)); + size_t size; + + memset (st, 0, sizeof (*st)); + + st->size = 128; + st->pool = pool; + + size = offsetof (struct serdata, data) + st->size; + st->data = os_malloc (size); + memset (st->data, 0, size); + st->data->v.st = st; + serstate_init (st, topic); + return st; +} + +void * ddsi_serstate_append (serstate_t st, size_t n) +{ + char *p; + if (st->pos + n > st->size) + { + size_t size1 = alignup_size (st->pos + n, 128); + serdata_t data1 = os_realloc (st->data, offsetof (struct serdata, data) + size1); + st->data = data1; + st->size = size1; + } + assert (st->pos + n <= st->size); + p = st->data->data + st->pos; + st->pos += n; + return p; +} + +void ddsi_serstate_release (serstate_t st) +{ + if (os_atomic_dec32_ov (&st->refcount) == 1) + { + serstatepool_t pool = st->pool; + sertopic_free ((sertopic_t) st->topic); + if (!nn_freelist_push (&pool->freelist, st)) + serstate_free (st); + } +} + +void * ddsi_serstate_append_align (serstate_t st, size_t sz) +{ + return ddsi_serstate_append_aligned (st, sz, sz); +} + +void * ddsi_serstate_append_aligned (serstate_t st, size_t n, size_t a) +{ + /* Simply align st->pos, without verifying it fits in the allocated + buffer: ddsi_serstate_append() is called immediately afterward and will + grow the buffer as soon as the end of the requested space no + longer fits. */ + size_t pos0 = st->pos; + char *p; + assert (ispowerof2_size (a)); + st->pos = alignup_size (st->pos, a); + p = ddsi_serstate_append (st, n); + if (p && st->pos > pos0) + memset (st->data->data + pos0, 0, st->pos - pos0); + return p; +} + +static size_t alignup_size (size_t x, size_t a) +{ + size_t m = a-1; + assert (ispowerof2_size (a)); + return (x+m) & ~m; +} diff --git a/src/core/ddsi/src/ddsi_ssl.c b/src/core/ddsi/src/ddsi_ssl.c new file mode 100644 index 0000000..4af1cd2 --- /dev/null +++ b/src/core/ddsi/src/ddsi_ssl.c @@ -0,0 +1,437 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsi/ddsi_ssl.h" +#include "ddsi/q_config.h" +#include "ddsi/q_log.h" +#include "os/os_heap.h" +#include "ddsi/ddsi_tcp.h" + +#ifdef DDSI_INCLUDE_SSL + +#include +#include + +static SSL_CTX * ddsi_ssl_ctx = NULL; + +static SSL * ddsi_ssl_new (void) +{ + return SSL_new (ddsi_ssl_ctx); +} + +static void ddsi_ssl_error (SSL * ssl, const char * str, int err) +{ + char buff [128]; + ERR_error_string ((unsigned) SSL_get_error (ssl, err), buff); + nn_log (LC_ERROR, "tcp/ssl %s %s %d\n", str, buff, err); +} + +static int ddsi_ssl_verify (int ok, X509_STORE_CTX * store) +{ + if (!ok) + { + char issuer[256]; + X509 * cert = X509_STORE_CTX_get_current_cert (store); + int err = X509_STORE_CTX_get_error (store); + + /* Check if allowing self-signed certificates */ + + if + ( + config.ssl_self_signed && + ((err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT) || + (err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN)) + ) + { + ok = 1; + } + else + { + X509_NAME_oneline (X509_get_issuer_name (cert), issuer, sizeof (issuer)); + nn_log + ( + LC_ERROR, + "tcp/ssl failed to verify certificate from %s : %s\n", + issuer, + X509_verify_cert_error_string (err) + ); + } + } + return ok; +} + +static os_ssize_t ddsi_ssl_read (SSL * ssl, void * buf, os_size_t len, int * err) +{ + int ret; + + assert (len <= INT32_MAX); + + if (SSL_get_shutdown (ssl) != 0) + { + return -1; + } + + /* Returns -1 on error or 0 on shutdown */ + + ret = SSL_read (ssl, buf, (int) len); + switch (SSL_get_error (ssl, ret)) + { + case SSL_ERROR_NONE: + { + /* Success */ + + break; + } + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + { + *err = os_sockEAGAIN; + ret = -1; + break; + } + case SSL_ERROR_ZERO_RETURN: + default: + { + /* Connection closed or error */ + + *err = os_getErrno (); + ret = -1; + break; + } + } + + return ret; +} + +static os_ssize_t ddsi_ssl_write (SSL * ssl, const void * buf, os_size_t len, int * err) +{ + int ret; + + assert(len <= INT32_MAX); + + if (SSL_get_shutdown (ssl) != 0) + { + return -1; + } + + /* Returns -1 on error or 0 on shutdown */ + + ret = SSL_write (ssl, buf, (int) len); + switch (SSL_get_error (ssl, ret)) + { + case SSL_ERROR_NONE: + { + /* Success */ + + break; + } + case SSL_ERROR_WANT_READ: + case SSL_ERROR_WANT_WRITE: + { + *err = os_sockEAGAIN; + ret = -1; + break; + } + case SSL_ERROR_ZERO_RETURN: + default: + { + /* Connection closed or error */ + + *err = os_getErrno (); + ret = -1; + break; + } + } + + return ret; +} + +/* Standard OpenSSL init and thread support routines. See O'Reilly. */ + +static unsigned long ddsi_ssl_id (void) +{ + return os_threadIdToInteger (os_threadIdSelf ()); +} + +typedef struct CRYPTO_dynlock_value +{ + os_mutex m_mutex; +} +CRYPTO_dynlock_value; + +static CRYPTO_dynlock_value * ddsi_ssl_locks = NULL; + +static void ddsi_ssl_dynlock_lock (int mode, CRYPTO_dynlock_value * lock, const char * file, int line) +{ + (void) file; + (void) line; + if (mode & CRYPTO_LOCK) + { + os_mutexLock (&lock->m_mutex); + } + else + { + os_mutexUnlock (&lock->m_mutex); + } +} + +static void ddsi_ssl_lock (int mode, int n, const char * file, int line) +{ + ddsi_ssl_dynlock_lock (mode, &ddsi_ssl_locks[n], file, line); +} + +static CRYPTO_dynlock_value * ddsi_ssl_dynlock_create (const char * file, int line) +{ + CRYPTO_dynlock_value * val = os_malloc (sizeof (*val)); + + (void) file; + (void) line; + os_mutexInit (&val->m_mutex); + return val; +} + +static void ddsi_ssl_dynlock_destroy (CRYPTO_dynlock_value * lock, const char * file, int line) +{ + (void) file; + (void) line; + os_mutexDestroy (&lock->m_mutex); + os_free (lock); +} + +static int ddsi_ssl_password (char * buf, int num, int rwflag, void * udata) +{ + (void) rwflag; + (void) udata; + if ((unsigned int) num < strlen (config.ssl_key_pass) + 1) + { + return (0); + } + strcpy (buf, config.ssl_key_pass); + return (int) strlen (config.ssl_key_pass); +} + +static SSL_CTX * ddsi_ssl_ctx_init (void) +{ + int i; + SSL_CTX * ctx = SSL_CTX_new (TLSv1_method ()); + + /* Load certificates */ + + if (! SSL_CTX_use_certificate_file (ctx, config.ssl_keystore, SSL_FILETYPE_PEM)) + { + nn_log + ( + LC_ERROR | LC_CONFIG, + "tcp/ssl failed to load certificate from file: %s\n", + config.ssl_keystore + ); + goto fail; + } + + /* Set password and callback */ + + SSL_CTX_set_default_passwd_cb (ctx, ddsi_ssl_password); + + /* Get private key */ + + if (! SSL_CTX_use_PrivateKey_file (ctx, config.ssl_keystore, SSL_FILETYPE_PEM)) + { + nn_log + ( + LC_ERROR | LC_CONFIG, + "tcp/ssl failed to load private key from file: %s\n", + config.ssl_keystore + ); + goto fail; + } + + /* Load CAs */ + + if (! SSL_CTX_load_verify_locations (ctx, config.ssl_keystore, 0)) + { + nn_log + ( + LC_ERROR | LC_CONFIG, + "tcp/ssl failed to load CA from file: %s\n", + config.ssl_keystore + ); + goto fail; + } + + /* Set ciphers */ + + if (! SSL_CTX_set_cipher_list (ctx, config.ssl_ciphers)) + { + nn_log + ( + LC_ERROR | LC_CONFIG, + "tcp/ssl failed to set ciphers: %s\n", + config.ssl_ciphers + ); + goto fail; + } + + /* Load randomness from file (optional) */ + + if (config.ssl_rand_file[0] != '\0') + { + if (! RAND_load_file (config.ssl_rand_file, 4096)) + { + nn_log + ( + LC_ERROR | LC_CONFIG, + "tcp/ssl failed to load random seed from file: %s\n", + config.ssl_rand_file + ); + goto fail; + } + } + + /* Set certificate verification policy from configuration */ + + if (config.ssl_verify) + { + i = SSL_VERIFY_PEER; + if (config.ssl_verify_client) + { + i |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + } + SSL_CTX_set_verify (ctx, i, ddsi_ssl_verify); + } + else + { + SSL_CTX_set_verify (ctx, SSL_VERIFY_NONE, NULL); + } + SSL_CTX_set_options (ctx, SSL_OP_ALL | SSL_OP_NO_SSLv2); + + return ctx; + +fail: + + SSL_CTX_free (ctx); + return NULL; +} + +static SSL * ddsi_ssl_connect (os_socket sock) +{ + SSL * ssl; + int err; + + /* Connect SSL over connected socket */ + + ssl = ddsi_ssl_new (); + SSL_set_fd (ssl, sock); + err = SSL_connect (ssl); + if (err != 1) + { + ddsi_ssl_error (ssl, "connect failed", err); + SSL_free (ssl); + ssl = NULL; + } + return ssl; +} + +static BIO * ddsi_ssl_listen (os_socket sock) +{ + BIO * bio = BIO_new (BIO_s_accept ()); + BIO_set_fd (bio, sock, BIO_NOCLOSE); + return bio; +} + +static SSL * ddsi_ssl_accept (BIO * bio, os_socket * sock) +{ + SSL * ssl = NULL; + BIO * nbio; + int err; + + if (BIO_do_accept (bio) > 0) + { + nbio = BIO_pop (bio); + *sock = (os_socket) BIO_get_fd (nbio, NULL); + ssl = ddsi_ssl_new (); + SSL_set_bio (ssl, nbio, nbio); + err = SSL_accept (ssl); + if (err <= 0) + { + SSL_free (ssl); + *sock = Q_INVALID_SOCKET; + ssl = NULL; + } + } + return ssl; +} + +static c_bool ddsi_ssl_init (void) +{ + unsigned locks = (unsigned) CRYPTO_num_locks (); + unsigned i; + + ddsi_ssl_locks = os_malloc (sizeof (CRYPTO_dynlock_value) * locks); + for (i = 0; i < locks; i++) + { + os_mutexInit (&ddsi_ssl_locks[i].m_mutex); + } + ERR_load_BIO_strings (); + SSL_load_error_strings (); + SSL_library_init (); + OpenSSL_add_all_algorithms (); + CRYPTO_set_id_callback (ddsi_ssl_id); + CRYPTO_set_locking_callback (ddsi_ssl_lock); + CRYPTO_set_dynlock_create_callback (ddsi_ssl_dynlock_create); + CRYPTO_set_dynlock_lock_callback (ddsi_ssl_dynlock_lock); + CRYPTO_set_dynlock_destroy_callback (ddsi_ssl_dynlock_destroy); + ddsi_ssl_ctx = ddsi_ssl_ctx_init (); + + return (ddsi_ssl_ctx != NULL); +} + +static void ddsi_ssl_fini (void) +{ + unsigned locks = (unsigned) CRYPTO_num_locks (); + unsigned i; + + SSL_CTX_free (ddsi_ssl_ctx); + CRYPTO_set_id_callback (NULL); + CRYPTO_set_locking_callback (NULL); + CRYPTO_set_dynlock_create_callback (NULL); + CRYPTO_set_dynlock_lock_callback (NULL); + CRYPTO_set_dynlock_destroy_callback (NULL); + ERR_free_strings (); + EVP_cleanup (); + for (i = 0; i < locks; i++) + { + os_mutexDestroy (&ddsi_ssl_locks[i].m_mutex); + } + os_free (ddsi_ssl_locks); +} + +static void ddsi_ssl_config (void) +{ + if (config.ssl_enable) + { + ddsi_tcp_ssl_plugin.init = ddsi_ssl_init; + ddsi_tcp_ssl_plugin.fini = ddsi_ssl_fini; + ddsi_tcp_ssl_plugin.ssl_free = SSL_free; + ddsi_tcp_ssl_plugin.bio_vfree = BIO_vfree; + ddsi_tcp_ssl_plugin.read = ddsi_ssl_read; + ddsi_tcp_ssl_plugin.write = ddsi_ssl_write; + ddsi_tcp_ssl_plugin.connect = ddsi_ssl_connect; + ddsi_tcp_ssl_plugin.listen = ddsi_ssl_listen; + ddsi_tcp_ssl_plugin.accept = ddsi_ssl_accept; + } +} + +void ddsi_ssl_plugin (void) +{ + ddsi_tcp_ssl_plugin.config = ddsi_ssl_config; +} + +#endif /* DDSI_INCLUDE_SSL */ diff --git a/src/core/ddsi/src/ddsi_tcp.c b/src/core/ddsi/src/ddsi_tcp.c new file mode 100644 index 0000000..885d5e1 --- /dev/null +++ b/src/core/ddsi/src/ddsi_tcp.c @@ -0,0 +1,1068 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include + +#include "ddsi/ddsi_tran.h" +#include "ddsi/ddsi_tcp.h" +#include "util/ut_avl.h" +#include "ddsi/q_nwif.h" +#include "ddsi/q_config.h" +#include "ddsi/q_log.h" +#include "ddsi/q_entity.h" +#include "os/os.h" + +#define INVALID_PORT (~0u) + +typedef struct ddsi_tran_factory * ddsi_tcp_factory_g_t; + +#ifdef DDSI_INCLUDE_SSL +struct ddsi_ssl_plugins ddsi_tcp_ssl_plugin = + { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; +#endif + +static const char * ddsi_name = "tcp"; + +/* Stateless singleton instance handed out as client connection */ + +static struct ddsi_tran_conn ddsi_tcp_conn_client; + +/* + ddsi_tcp_conn: TCP connection for reading and writing. Mutex prevents concurrent + writes to socket. Is reference counted. Peer port is actually contained in peer + address but is extracted for convenience and for faster cache lookup + (see ddsi_tcp_cmp_entry). Where connection is server side socket (for bi-dir) + is flagged as such to avoid connection attempts and for same reason, on failure, + is not removed from cache but simply flagged as failed (may be subsequently + replaced). Similarly server side sockets are not closed as are also used in socket + wait set that manages their lifecycle. +*/ + +typedef struct ddsi_tcp_conn +{ + struct ddsi_tran_conn m_base; + os_sockaddr_storage m_peer_addr; + uint32_t m_peer_port; + os_mutex m_mutex; + os_socket m_sock; +#ifdef DDSI_INCLUDE_SSL + SSL * m_ssl; +#endif +} +* ddsi_tcp_conn_t; + +typedef struct ddsi_tcp_listener +{ + struct ddsi_tran_listener m_base; + os_socket m_sock; +#ifdef DDSI_INCLUDE_SSL + BIO * m_bio; +#endif +} +* ddsi_tcp_listener_t; + +static void nn_trace_tcp (const char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + nn_vlog (LC_TCP, fmt, ap); + va_end (ap); +} + +#define TRACE_TCP(args) ((config.enabled_logcats & LC_TCP) ? (nn_trace_tcp args) : (void) 0) + +static int ddsi_tcp_cmp_conn (const ddsi_tcp_conn_t c1, const ddsi_tcp_conn_t c2) +{ + const os_sockaddr_storage *a1s = &c1->m_peer_addr; + const os_sockaddr_storage *a2s = &c2->m_peer_addr; + if (a1s->ss_family != a2s->ss_family) + return (a1s->ss_family < a2s->ss_family) ? -1 : 1; + else if (c1->m_peer_port != c2->m_peer_port) + return (c1->m_peer_port < c2->m_peer_port) ? -1 : 1; + else if (a1s->ss_family == AF_INET) + { + const os_sockaddr_in *a1 = (const os_sockaddr_in *) a1s; + const os_sockaddr_in *a2 = (const os_sockaddr_in *) a2s; + return (a1->sin_addr.s_addr == a2->sin_addr.s_addr) ? 0 : (a1->sin_addr.s_addr < a2->sin_addr.s_addr) ? -1 : 1; + } +#if OS_SOCKET_HAS_IPV6 + else if (a1s->ss_family == AF_INET6) + { + const os_sockaddr_in6 *a1 = (const os_sockaddr_in6 *) a1s; + const os_sockaddr_in6 *a2 = (const os_sockaddr_in6 *) a2s; + return memcmp (&a1->sin6_addr, &a2->sin6_addr, 16); + } +#endif + else + { + assert (0); + return 0; + } +} + +typedef struct ddsi_tcp_node +{ + ut_avlNode_t m_avlnode; + ddsi_tcp_conn_t m_conn; +} +* ddsi_tcp_node_t; + +static const ut_avlTreedef_t ddsi_tcp_treedef = UT_AVL_TREEDEF_INITIALIZER_INDKEY +( + offsetof (struct ddsi_tcp_node, m_avlnode), + offsetof (struct ddsi_tcp_node, m_conn), + (ut_avlCompare_t) ddsi_tcp_cmp_conn, + 0 +); + +static os_mutex ddsi_tcp_cache_lock_g; +static ut_avlTree_t ddsi_tcp_cache_g; +static struct ddsi_tran_factory ddsi_tcp_factory_g; + +static ddsi_tcp_conn_t ddsi_tcp_new_conn (os_socket, bool, os_sockaddr_storage *); +extern void ddsi_factory_conn_init (ddsi_tran_factory_t, ddsi_tran_conn_t); + +/* Connection cache dump routine for debugging + +static void ddsi_tcp_cache_dump (void) +{ + char buff[64]; + ut_avlIter_t iter; + ddsi_tcp_node_t n; + unsigned i = 0; + + n = ut_avlIterFirst (&ddsi_tcp_treedef, &ddsi_tcp_cache_g, &iter); + while (n) + { + os_sockaddrAddressPortToString ((const os_sockaddr *) &n->m_conn->m_peer_addr, buff, sizeof (buff)); + nn_log + ( + LC_INFO, + "%s cache #%d: %s sock %d port %u peer %s\n", + ddsi_name, i++, n->m_conn->m_base.m_server ? "server" : "client", + n->m_conn->m_sock, n->m_conn->m_base.m_base.m_port, buff + ); + n = ut_avlIterNext (&iter); + } +} +*/ + +static void ddsi_tcp_conn_set_socket (ddsi_tcp_conn_t conn, os_socket sock) +{ + conn->m_sock = sock; + conn->m_base.m_base.m_port = (sock == Q_INVALID_SOCKET) ? INVALID_PORT : get_socket_port (sock); +} + +static void ddsi_tcp_sock_free (os_socket sock, const char * msg) +{ + if (sock != Q_INVALID_SOCKET) + { + if (msg) + { + nn_log (LC_INFO, "%s %s free socket %"PRIsock"\n", ddsi_name, msg, sock); + } + os_sockFree (sock); + } +} + +static void ddsi_tcp_sock_new (os_socket * sock, unsigned short port) +{ + if (make_socket (sock, port, true, true) != 0) + { + *sock = Q_INVALID_SOCKET; + } +} + +static void ddsi_tcp_node_free (void * ptr) +{ + ddsi_tcp_node_t node = (ddsi_tcp_node_t) ptr; + ddsi_conn_free ((ddsi_tran_conn_t) node->m_conn); + os_free (node); +} + +static void ddsi_tcp_conn_connect (ddsi_tcp_conn_t conn, const struct msghdr * msg) +{ + int ret; + char buff[INET6_ADDRSTRLEN_EXTENDED]; + os_socket sock; + + ddsi_tcp_sock_new (&sock, 0); + if (sock != Q_INVALID_SOCKET) + { + /* Attempt to connect, expected that may fail */ + + do + { + ret = connect (sock, msg->msg_name, msg->msg_namelen); + } + while ((ret == -1) && (os_getErrno() == os_sockEINTR)); + + if (ret != 0) + { + ddsi_tcp_sock_free (sock, NULL); + return; + } + ddsi_tcp_conn_set_socket (conn, sock); + +#ifdef DDSI_INCLUDE_SSL + if (ddsi_tcp_ssl_plugin.connect) + { + conn->m_ssl = (ddsi_tcp_ssl_plugin.connect) (sock); + if (conn->m_ssl == NULL) + { + ddsi_tcp_conn_set_socket (conn, Q_INVALID_SOCKET); + return; + } + } +#endif + + sockaddr_to_string_with_port(buff, (const os_sockaddr_storage *) msg->msg_name); + nn_log (LC_INFO, "%s connect socket %"PRIsock" port %u to %s\n", ddsi_name, sock, get_socket_port (sock), buff); + + /* Also may need to receive on connection so add to waitset */ + + os_sockSetNonBlocking (conn->m_sock, true); + os_sockWaitsetAdd (gv.waitset, &conn->m_base); + os_sockWaitsetTrigger (gv.waitset); + } +} + +static void ddsi_tcp_cache_add (ddsi_tcp_conn_t conn, ut_avlIPath_t * path) +{ + const char * action = "added"; + ddsi_tcp_node_t node; + char buff[INET6_ADDRSTRLEN_EXTENDED]; + + os_atomic_inc32 (&conn->m_base.m_count); + + /* If path set, then cache does not contain connection */ + if (path) + { + node = os_malloc (sizeof (*node)); + node->m_conn = conn; + ut_avlInsertIPath (&ddsi_tcp_treedef, &ddsi_tcp_cache_g, node, path); + } + else + { + node = ut_avlLookup (&ddsi_tcp_treedef, &ddsi_tcp_cache_g, conn); + if (node) + { + /* Replace connection in cache */ + + ddsi_conn_free ((ddsi_tran_conn_t) node->m_conn); + node->m_conn = conn; + action = "updated"; + } + else + { + node = os_malloc (sizeof (*node)); + node->m_conn = conn; + ut_avlInsert (&ddsi_tcp_treedef, &ddsi_tcp_cache_g, node); + } + } + + sockaddr_to_string_with_port(buff, &conn->m_peer_addr); + nn_log (LC_INFO, "%s cache %s %s socket %"PRIsock" to %s\n", ddsi_name, action, conn->m_base.m_server ? "server" : "client", conn->m_sock, buff); +} + +static void ddsi_tcp_cache_remove (ddsi_tcp_conn_t conn) +{ + char buff[INET6_ADDRSTRLEN_EXTENDED]; + ddsi_tcp_node_t node; + ut_avlDPath_t path; + + os_mutexLock (&ddsi_tcp_cache_lock_g); + node = ut_avlLookupDPath (&ddsi_tcp_treedef, &ddsi_tcp_cache_g, conn, &path); + if (node) + { + sockaddr_to_string_with_port(buff, &conn->m_peer_addr); + nn_log (LC_INFO, "%s cache removed socket %"PRIsock" to %s\n", ddsi_name, conn->m_sock, buff); + ut_avlDeleteDPath (&ddsi_tcp_treedef, &ddsi_tcp_cache_g, node, &path); + ddsi_tcp_node_free (node); + } + os_mutexUnlock (&ddsi_tcp_cache_lock_g); +} + +/* + ddsi_tcp_cache_find: Find existing connection to target, or if possible + create new connection. +*/ + +static ddsi_tcp_conn_t ddsi_tcp_cache_find (const struct msghdr * msg) +{ + ut_avlIPath_t path; + ddsi_tcp_node_t node; + struct ddsi_tcp_conn key; + ddsi_tcp_conn_t ret = NULL; + + memset (&key, 0, sizeof (key)); + key.m_peer_port = sockaddr_get_port (msg->msg_name); + memcpy (&key.m_peer_addr, msg->msg_name, msg->msg_namelen); + + /* Check cache for existing connection to target */ + + os_mutexLock (&ddsi_tcp_cache_lock_g); + node = ut_avlLookupIPath (&ddsi_tcp_treedef, &ddsi_tcp_cache_g, &key, &path); + if (node) + { + if (node->m_conn->m_base.m_closed) + { + ut_avlDelete (&ddsi_tcp_treedef, &ddsi_tcp_cache_g, node); + ddsi_tcp_node_free (node); + } + else + { + ret = node->m_conn; + } + } + if (ret == NULL) + { + ret = ddsi_tcp_new_conn (Q_INVALID_SOCKET, false, &key.m_peer_addr); + ddsi_tcp_cache_add (ret, &path); + } + os_mutexUnlock (&ddsi_tcp_cache_lock_g); + + return ret; +} + +static ssize_t ddsi_tcp_conn_read_plain (ddsi_tcp_conn_t tcp, void * buf, size_t len, int * err) +{ +OS_WARNING_MSVC_OFF(4267); + ssize_t ret = recv (tcp->m_sock, buf, len, 0); + *err = (ret == -1) ? os_getErrno () : 0; + return ret; +OS_WARNING_MSVC_ON(4267); +} + +#ifdef DDSI_INCLUDE_SSL +static os_ssize_t ddsi_tcp_conn_read_ssl (ddsi_tcp_conn_t tcp, void * buf, os_size_t len, int * err) +{ + return (ddsi_tcp_ssl_plugin.read) (tcp->m_ssl, buf, len, err); +} +#endif + +static bool ddsi_tcp_select (os_socket sock, bool read, size_t pos) +{ + int ret; + fd_set fds; + os_time timeout; + fd_set * rdset = read ? &fds : NULL; + fd_set * wrset = read ? NULL : &fds; + int64_t tval = read ? config.tcp_read_timeout : config.tcp_write_timeout; + + FD_ZERO (&fds); + FD_SET (sock, &fds); + timeout.tv_sec = (int) (tval / T_SECOND); + timeout.tv_nsec = (int) (tval % T_SECOND); + + TRACE_TCP (("%s blocked %s: sock %d\n", ddsi_name, read ? "read" : "write", (int) sock)); + do + { + ret = os_sockSelect ((int32_t)sock + 1, rdset, wrset, NULL, &timeout); /* The variable "sock" with os_socket type causes the possible loss of data. So type casting done */ + } + while (ret == -1 && os_getErrno () == os_sockEINTR); + + if (ret <= 0) + { + nn_log + ( + LC_WARNING, "%s abandoning %s on blocking socket %d after %"PRIuSIZE" bytes\n", + ddsi_name, read ? "read" : "write", (int) sock, pos + ); + } + + return (ret > 0); +} + +static int err_is_AGAIN_or_WOULDBLOCK (int err) +{ + if (err == os_sockEAGAIN) + return 1; + if (err == os_sockEWOULDBLOCK) + return 1; + return 0; +} + +static ssize_t ddsi_tcp_conn_read (ddsi_tran_conn_t conn, unsigned char * buf, size_t len) +{ + ddsi_tcp_conn_t tcp = (ddsi_tcp_conn_t) conn; + ssize_t (*rd) (ddsi_tcp_conn_t, void *, size_t, int * err) = ddsi_tcp_conn_read_plain; + size_t pos = 0; + ssize_t n; + int err; + +#ifdef DDSI_INCLUDE_SSL + if (ddsi_tcp_ssl_plugin.read) + { + rd = ddsi_tcp_conn_read_ssl; + } +#endif + + while (true) + { + n = rd (tcp, (char *) buf + pos, len - pos, &err); + if (n > 0) + { + pos += (size_t) n; + if (pos == len) + { + return (ssize_t) pos; + } + } + else if (n == 0) + { + TRACE_TCP (("%s read: sock %"PRIsock" closed-by-peer\n", ddsi_name, tcp->m_sock)); + break; + } + else + { + if (err != os_sockEINTR) + { + if (err_is_AGAIN_or_WOULDBLOCK (err)) + { + if (ddsi_tcp_select (tcp->m_sock, true, pos) == false) + { + break; + } + } + else + { + TRACE_TCP (("%s read: sock %"PRIsock" error %d\n", ddsi_name, tcp->m_sock, err)); + break; + } + } + } + } + + ddsi_tcp_cache_remove (tcp); + return -1; +} + +static ssize_t ddsi_tcp_conn_write_plain (ddsi_tcp_conn_t conn, const void * buf, size_t len, int * err) +{ + ssize_t ret; + int sendflags = 0; + +#ifdef MSG_NOSIGNAL + sendflags |= MSG_NOSIGNAL; +#endif +OS_WARNING_MSVC_OFF(4267); + ret = send (conn->m_sock, buf, len, sendflags); + *err = (ret == -1) ? os_getErrno () : 0; + return ret; + OS_WARNING_MSVC_ON(4267); +} + +#ifdef DDSI_INCLUDE_SSL +static os_ssize_t ddsi_tcp_conn_write_ssl (ddsi_tcp_conn_t conn, const void * buf, os_size_t len, int * err) +{ + return (ddsi_tcp_ssl_plugin.write) (conn->m_ssl, buf, len, err); +} +#endif + +static ssize_t ddsi_tcp_block_write +( + ssize_t (*wr) (ddsi_tcp_conn_t, const void *, size_t, int *), + ddsi_tcp_conn_t conn, + const void * buf, + size_t sz +) +{ + /* Write all bytes of buf even in the presence of signals, + partial writes and blocking (typically write buffer full) */ + + size_t pos = 0; + ssize_t n; + int err; + + while (pos != sz) + { + n = (wr) (conn, (const char *) buf + pos, sz - pos, &err); + if (n > 0) + { + pos += (size_t) n; + } + else if (n == -1) + { + if (err != os_sockEINTR) + { + if (err_is_AGAIN_or_WOULDBLOCK (err)) + { + if (ddsi_tcp_select (conn->m_sock, false, pos) == false) + { + break; + } + } + else + { + TRACE_TCP (("%s write: sock %"PRIsock" error %d\n", ddsi_name, conn->m_sock, err)); + break; + } + } + } + } + + return (pos == sz) ? (ssize_t) pos : -1; +} + +static ssize_t ddsi_tcp_conn_write (ddsi_tran_conn_t base, const struct msghdr * msg, size_t len, uint32_t flags) +{ +#ifdef DDSI_INCLUDE_SSL + char msgbuf[4096]; /* stack buffer for merging smallish writes without requiring allocations */ + struct iovec iovec; /* iovec used for msgbuf */ +#endif + struct msghdr msgcopy = *msg; + ssize_t ret; + ddsi_tcp_conn_t conn; + int piecewise; + bool connect = false; + + (void) base; + + conn = ddsi_tcp_cache_find (msg); + if (conn == NULL) + { + return -1; + } + + os_mutexLock (&conn->m_mutex); + + /* If not connected attempt to conect */ + + if ((conn->m_sock == Q_INVALID_SOCKET) && ! conn->m_base.m_server) + { + ddsi_tcp_conn_connect (conn, msg); + if (conn->m_sock == Q_INVALID_SOCKET) + { + os_mutexUnlock (&conn->m_mutex); + return -1; + } + connect = true; + } + + /* Check if filtering out message from existing connections */ + + if (!connect && ((flags & DDSI_TRAN_ON_CONNECT) != 0)) + { + TRACE_TCP (("%s write: sock %"PRIsock" message filtered\n", ddsi_name, conn->m_sock)); + os_mutexUnlock (&conn->m_mutex); + return (ssize_t) len; + } + +#ifdef DDSI_INCLUDE_SSL + if (config.ssl_enable) + { + /* SSL doesn't have sendmsg, ret = 0 so writing starts at first byte. + Rumor is that it is much better to merge small writes, which do here + rather in than in SSL-specific code for simplicity - perhaps ought + to move this copying into xpack_send */ + if (msg->msg_iovlen > 1) + { + int i; + char * ptr; + iovec.iov_len = len; + iovec.iov_base = (len <= sizeof (msgbuf)) ? msgbuf : os_malloc (len); + ptr = iovec.iov_base; + for (i = 0; i < (int) msg->msg_iovlen; i++) + { + memcpy (ptr, msg->msg_iov[i].iov_base, msg->msg_iov[i].iov_len); + ptr += msg->msg_iov[i].iov_len; + } + msgcopy.msg_iov = &iovec; + msgcopy.msg_iovlen = 1; + } + piecewise = 1; + ret = 0; + } + else +#endif + { + int sendflags = 0; + int err; +#ifdef MSG_NOSIGNAL + sendflags |= MSG_NOSIGNAL; +#endif + msgcopy.msg_name = NULL; + msgcopy.msg_namelen = 0; + do + { + ret = sendmsg (conn->m_sock, &msgcopy, sendflags); + err = (ret == -1) ? os_getErrno () : 0; + } + while ((ret == -1) && (err == os_sockEINTR)); + if (ret == -1) + { + if (err_is_AGAIN_or_WOULDBLOCK (err)) + { + piecewise = 1; + ret = 0; + } + else + { + piecewise = 0; + if (err != os_sockECONNRESET) + { + if (! conn->m_base.m_closed && (conn->m_sock != Q_INVALID_SOCKET)) + { + nn_log + ( + LC_WARNING, "%s write failed on socket %"PRIsock" with errno %d\n", + ddsi_name, conn->m_sock, err + ); + } + } + else + { + TRACE_TCP (("%s write: sock %"PRIsock" ECONNRESET\n", ddsi_name, conn->m_sock)); + } + } + } + else + { + if (ret == 0) + { + TRACE_TCP (("%s write: sock %"PRIsock" eof\n", ddsi_name, conn->m_sock)); + } + piecewise = (ret > 0 && (size_t) ret < len); + } + } + + if (piecewise) + { + ssize_t (*wr) (ddsi_tcp_conn_t, const void *, size_t, int *) = ddsi_tcp_conn_write_plain; + int i = 0; +#ifdef DDSI_INCLUDE_SSL + if (ddsi_tcp_ssl_plugin.write) + { + wr = ddsi_tcp_conn_write_ssl; + } +#endif + + assert (msgcopy.msg_iov[i].iov_len > 0); + while (ret >= (ssize_t) msgcopy.msg_iov[i].iov_len) + { + ret -= (ssize_t) msgcopy.msg_iov[i++].iov_len; + } + assert (i < (int) msgcopy.msg_iovlen); + ret = ddsi_tcp_block_write (wr, conn, (const char *) msgcopy.msg_iov[i].iov_base + ret, msgcopy.msg_iov[i].iov_len - (size_t) ret); + while (ret > 0 && ++i < (int) msgcopy.msg_iovlen) + { + ret = ddsi_tcp_block_write (wr, conn, msgcopy.msg_iov[i].iov_base, msgcopy.msg_iov[i].iov_len); + } + } + +#ifdef DDSI_INCLUDE_SSL + /* If allocated memory for merging original fragments into a single buffer, free it */ + if (msgcopy.msg_iov == &iovec && iovec.iov_base != msgbuf) + { + os_free (iovec.iov_base); + } +#endif + + os_mutexUnlock (&conn->m_mutex); + + if (ret == -1) + { + ddsi_tcp_cache_remove (conn); + } + + return ((size_t) ret == len) ? ret : -1; +} + +static os_handle ddsi_tcp_conn_handle (ddsi_tran_base_t base) +{ + return ((ddsi_tcp_conn_t) base)->m_sock; +} + +static bool ddsi_tcp_supports (int32_t kind) +{ + return + ( + (!config.useIpv6 && (kind == NN_LOCATOR_KIND_TCPv4)) +#if OS_SOCKET_HAS_IPV6 + || (config.useIpv6 && (kind == NN_LOCATOR_KIND_TCPv6)) +#endif + ); +} + +static int ddsi_tcp_locator (ddsi_tran_base_t base, nn_locator_t *loc) +{ + nn_address_to_loc (loc, &gv.extip, ddsi_tcp_factory_g.m_kind); + loc->port = base->m_port; + return 0; +} + +static ddsi_tran_conn_t ddsi_tcp_create_conn (uint32_t port, ddsi_tran_qos_t qos) +{ + (void) qos; + (void) port; + + return (ddsi_tran_conn_t) &ddsi_tcp_conn_client; +} + +static int ddsi_tcp_listen (ddsi_tran_listener_t listener) +{ + ddsi_tcp_listener_t tl = (ddsi_tcp_listener_t) listener; + int ret = listen (tl->m_sock, 4); + +#ifdef DDSI_INCLUDE_SSL + if ((ret == 0) && ddsi_tcp_ssl_plugin.listen) + { + tl->m_bio = (ddsi_tcp_ssl_plugin.listen) (tl->m_sock); + } +#endif + + return ret; +} + +static ddsi_tran_conn_t ddsi_tcp_accept (ddsi_tran_listener_t listener) +{ + ddsi_tcp_listener_t tl = (ddsi_tcp_listener_t) listener; + ddsi_tcp_conn_t tcp = NULL; + os_socket sock = Q_INVALID_SOCKET; + os_sockaddr_storage addr; + socklen_t addrlen = sizeof (addr); + char buff[INET6_ADDRSTRLEN_EXTENDED]; + int err = 0; +#ifdef DDSI_INCLUDE_SSL + SSL * ssl = NULL; +#endif + + memset (&addr, 0, addrlen); + do + { +#ifdef DDSI_INCLUDE_SSL + if (ddsi_tcp_ssl_plugin.accept) + { + ssl = (ddsi_tcp_ssl_plugin.accept) (tl->m_bio, &sock); + } + else +#endif + { + sock = accept (tl->m_sock, NULL, NULL); + } + if (! gv.rtps_keepgoing) + { + ddsi_tcp_sock_free (sock, NULL); + return NULL; + } + err = (sock == Q_INVALID_SOCKET) ? os_getErrno () : 0; + } + while ((err == os_sockEINTR) || (err == os_sockEAGAIN) || (err == os_sockEWOULDBLOCK)); + + if (sock == Q_INVALID_SOCKET) + { + getsockname (tl->m_sock, (struct sockaddr *) &addr, &addrlen); + sockaddr_to_string_with_port(buff, &addr); + nn_log ((err == 0) ? LC_ERROR : LC_FATAL, "%s accept failed on socket %"PRIsock" at %s errno %d\n", ddsi_name, tl->m_sock, buff, err); + } + else if (getpeername (sock, (struct sockaddr *) &addr, &addrlen) == -1) + { + nn_log (LC_WARNING, "%s accepted new socket %"PRIsock" on socket %"PRIsock" but no peer address, errno %d\n", ddsi_name, sock, tl->m_sock, os_getErrno()); + os_sockFree (sock); + } + else + { + sockaddr_to_string_with_port(buff, &addr); + nn_log (LC_INFO, "%s accept new socket %"PRIsock" on socket %"PRIsock" from %s\n", ddsi_name, sock, tl->m_sock, buff); + + os_sockSetNonBlocking (sock, true); + tcp = ddsi_tcp_new_conn (sock, true, &addr); +#ifdef DDSI_INCLUDE_SSL + tcp->m_ssl = ssl; +#endif + tcp->m_base.m_listener = listener; + tcp->m_base.m_conn = listener->m_connections; + listener->m_connections = &tcp->m_base; + + /* Add connection to cache for bi-dir */ + + os_mutexLock (&ddsi_tcp_cache_lock_g); + ddsi_tcp_cache_add (tcp, NULL); + os_mutexUnlock (&ddsi_tcp_cache_lock_g); + } + return tcp ? &tcp->m_base : NULL; +} + +static os_handle ddsi_tcp_listener_handle (ddsi_tran_base_t base) +{ + return ((ddsi_tcp_listener_t) base)->m_sock; +} + +/* + ddsi_tcp_conn_address: This function is called when an entity had been discovered + with an empty locator list and the locator is being set to the address of the + caller (supporting call back over NAT). +*/ + +static void ddsi_tcp_conn_peer_locator (ddsi_tran_conn_t conn, nn_locator_t * loc) +{ + char buff[INET6_ADDRSTRLEN_EXTENDED]; + ddsi_tcp_conn_t tc = (ddsi_tcp_conn_t) conn; + + assert (tc->m_base.m_server); + assert (tc->m_sock != Q_INVALID_SOCKET); + + sockaddr_to_string_with_port(buff, &tc->m_peer_addr); + TRACE (("(%s EP:%s)", ddsi_name, buff)); + nn_address_to_loc (loc, &tc->m_peer_addr, tc->m_peer_addr.ss_family == AF_INET ? NN_LOCATOR_KIND_TCPv4 : NN_LOCATOR_KIND_TCPv6); +} + +static void ddsi_tcp_base_init (struct ddsi_tran_conn * base) +{ + ddsi_factory_conn_init (&ddsi_tcp_factory_g, base); + base->m_base.m_trantype = DDSI_TRAN_CONN; + base->m_base.m_handle_fn = ddsi_tcp_conn_handle; + base->m_base.m_locator_fn = ddsi_tcp_locator; + base->m_read_fn = ddsi_tcp_conn_read; + base->m_write_fn = ddsi_tcp_conn_write; + base->m_peer_locator_fn = ddsi_tcp_conn_peer_locator; +} + +static ddsi_tcp_conn_t ddsi_tcp_new_conn (os_socket sock, bool server, os_sockaddr_storage * peer) +{ + ddsi_tcp_conn_t conn = (ddsi_tcp_conn_t) os_malloc (sizeof (*conn)); + + memset (conn, 0, sizeof (*conn)); + ddsi_tcp_base_init (&conn->m_base); + os_mutexInit (&conn->m_mutex); + conn->m_sock = Q_INVALID_SOCKET; + conn->m_peer_addr = *peer; + conn->m_peer_port = sockaddr_get_port (peer); + conn->m_base.m_server = server; + conn->m_base.m_base.m_port = INVALID_PORT; + ddsi_tcp_conn_set_socket (conn, sock); + + return conn; +} + +static ddsi_tran_listener_t ddsi_tcp_create_listener (int port, ddsi_tran_qos_t qos) +{ + char buff[INET6_ADDRSTRLEN_EXTENDED]; + os_socket sock; + os_sockaddr_storage addr; + socklen_t addrlen = sizeof (addr); + ddsi_tcp_listener_t tl = NULL; + + (void) qos; + + ddsi_tcp_sock_new (&sock, (unsigned short) port); + + if (sock != Q_INVALID_SOCKET) + { + tl = (ddsi_tcp_listener_t) os_malloc (sizeof (*tl)); + memset (tl, 0, sizeof (*tl)); + + tl->m_sock = sock; + + tl->m_base.m_listen_fn = ddsi_tcp_listen; + tl->m_base.m_accept_fn = ddsi_tcp_accept; + tl->m_base.m_factory = &ddsi_tcp_factory_g; + + tl->m_base.m_base.m_port = get_socket_port (sock); + tl->m_base.m_base.m_trantype = DDSI_TRAN_LISTENER; + tl->m_base.m_base.m_handle_fn = ddsi_tcp_listener_handle; + tl->m_base.m_base.m_locator_fn = ddsi_tcp_locator; + + if (getsockname (sock, (os_sockaddr *) &addr, &addrlen) == -1) + { + print_sockerror ("ddsi_tcp_create_listener: getsockname"); + ddsi_tcp_sock_free (sock, NULL); + os_free (tl); + return NULL; + } + + sockaddr_to_string_with_port(buff, &addr); + nn_log (LC_INFO, "%s create listener socket %"PRIsock" on %s\n", ddsi_name, sock, buff); + } + + return tl ? &tl->m_base : NULL; +} + +static void ddsi_tcp_conn_delete (ddsi_tcp_conn_t conn) +{ + char buff[INET6_ADDRSTRLEN_EXTENDED]; + sockaddr_to_string_with_port(buff, &conn->m_peer_addr); + nn_log (LC_INFO, "%s free %s connnection on socket %"PRIsock" to %s\n", ddsi_name, conn->m_base.m_server ? "server" : "client", conn->m_sock, buff); + +#ifdef DDSI_INCLUDE_SSL + if (ddsi_tcp_ssl_plugin.ssl_free) + { + (ddsi_tcp_ssl_plugin.ssl_free) (conn->m_ssl); + } + else +#endif + { + ddsi_tcp_sock_free (conn->m_sock, "connection"); + } + os_mutexDestroy (&conn->m_mutex); + os_free (conn); +} + +static void ddsi_tcp_close_conn (ddsi_tran_conn_t tc) +{ + if (tc != (ddsi_tran_conn_t) &ddsi_tcp_conn_client) + { + char buff[INET6_ADDRSTRLEN_EXTENDED]; + nn_locator_t loc; + ddsi_tcp_conn_t conn = (ddsi_tcp_conn_t) tc; + sockaddr_to_string_with_port(buff, &conn->m_peer_addr); + nn_log (LC_INFO, "%s close %s connnection on socket %"PRIsock" to %s\n", ddsi_name, conn->m_base.m_server ? "server" : "client", conn->m_sock, buff); + (void) shutdown (conn->m_sock, 2); + nn_address_to_loc(&loc, &conn->m_peer_addr, conn->m_peer_addr.ss_family == AF_INET ? NN_LOCATOR_KIND_TCPv4 : NN_LOCATOR_KIND_TCPv6); + loc.port = conn->m_peer_port; + purge_proxy_participants (&loc, conn->m_base.m_server); + } +} + +static void ddsi_tcp_release_conn (ddsi_tran_conn_t conn) +{ + if (conn != (ddsi_tran_conn_t) &ddsi_tcp_conn_client) + { + ddsi_tcp_conn_delete ((ddsi_tcp_conn_t) conn); + } +} + +static void ddsi_tcp_unblock_listener (ddsi_tran_listener_t listener) +{ + ddsi_tcp_listener_t tl = (ddsi_tcp_listener_t) listener; + os_socket sock; + int ret; + +#ifdef DDSI_INCLUDE_SSL + if (ddsi_tcp_ssl_plugin.bio_vfree) + { + (ddsi_tcp_ssl_plugin.bio_vfree) (tl->m_bio); + } +#endif + + /* Connect to own listener socket to wake listener from blocking 'accept()' */ + ddsi_tcp_sock_new (&sock, 0); + if (sock != Q_INVALID_SOCKET) + { + os_sockaddr_storage addr; + socklen_t addrlen = sizeof (addr); + if (getsockname (tl->m_sock, (os_sockaddr *) &addr, &addrlen) == -1) + nn_log (LC_WARNING, "%s failed to get listener address error %d\n", ddsi_name, os_getErrno()); + else + { + switch (addr.ss_family) { + case AF_INET: + { + os_sockaddr_in *socketname = (os_sockaddr_in*)&addr; + if (socketname->sin_addr.s_addr == htonl (INADDR_ANY)) { + socketname->sin_addr.s_addr = htonl (INADDR_LOOPBACK); + } + } + break; +#if OS_SOCKET_HAS_IPV6 + case AF_INET6: + { + os_sockaddr_in6 *socketname = (os_sockaddr_in6*)&addr; + if (memcmp(&socketname->sin6_addr, &os_in6addr_any, sizeof(socketname->sin6_addr)) == 0) { + socketname->sin6_addr = os_in6addr_loopback; + } + } + break; +#endif + } + do + { + ret = connect (sock, (struct sockaddr *) &addr, (unsigned) os_sockaddrSizeof((os_sockaddr *)&addr)); + } + while ((ret == -1) && (os_getErrno() == os_sockEINTR)); + if (ret == -1) + { + char buff[INET6_ADDRSTRLEN_EXTENDED]; + sockaddr_to_string_with_port(buff, &addr); + nn_log (LC_WARNING, "%s failed to connect to own listener (%s) error %d\n", ddsi_name, buff, os_getErrno()); + } + } + ddsi_tcp_sock_free (sock, NULL); + } +} + +static void ddsi_tcp_release_listener (ddsi_tran_listener_t listener) +{ + ddsi_tcp_listener_t tl = (ddsi_tcp_listener_t) listener; + ddsi_tcp_sock_free (tl->m_sock, "listener"); + os_free (tl); +} + +static void ddsi_tcp_release_factory (void) +{ + ut_avlFree (&ddsi_tcp_treedef, &ddsi_tcp_cache_g, ddsi_tcp_node_free); + os_mutexDestroy (&ddsi_tcp_cache_lock_g); +#ifdef DDSI_INCLUDE_SSL + if (ddsi_tcp_ssl_plugin.fini) + { + (ddsi_tcp_ssl_plugin.fini) (); + } +#endif +} + +int ddsi_tcp_init (void) +{ + static bool init = false; + if (!init) + { + init = true; + ddsi_tcp_factory_g.m_kind = NN_LOCATOR_KIND_TCPv4; + ddsi_tcp_factory_g.m_typename = "tcp"; + ddsi_tcp_factory_g.m_stream = true; + ddsi_tcp_factory_g.m_connless = false; + ddsi_tcp_factory_g.m_supports_fn = ddsi_tcp_supports; + ddsi_tcp_factory_g.m_create_listener_fn = ddsi_tcp_create_listener; + ddsi_tcp_factory_g.m_create_conn_fn = ddsi_tcp_create_conn; + ddsi_tcp_factory_g.m_release_conn_fn = ddsi_tcp_release_conn; + ddsi_tcp_factory_g.m_close_conn_fn = ddsi_tcp_close_conn; + ddsi_tcp_factory_g.m_unblock_listener_fn = ddsi_tcp_unblock_listener; + ddsi_tcp_factory_g.m_release_listener_fn = ddsi_tcp_release_listener; + ddsi_tcp_factory_g.m_free_fn = ddsi_tcp_release_factory; + ddsi_tcp_factory_g.m_join_mc_fn = 0; + ddsi_tcp_factory_g.m_leave_mc_fn = 0; + ddsi_factory_add (&ddsi_tcp_factory_g); + +#if OS_SOCKET_HAS_IPV6 + if (config.useIpv6) + { + ddsi_tcp_factory_g.m_kind = NN_LOCATOR_KIND_TCPv6; + } +#endif + + memset (&ddsi_tcp_conn_client, 0, sizeof (ddsi_tcp_conn_client)); + ddsi_tcp_base_init (&ddsi_tcp_conn_client); + +#ifdef DDSI_INCLUDE_SSL + if (ddsi_tcp_ssl_plugin.config) + { + (ddsi_tcp_ssl_plugin.config) (); + } + if (ddsi_tcp_ssl_plugin.init) + { + ddsi_name = "tcp/ssl"; + if (! (ddsi_tcp_ssl_plugin.init) ()) + { + NN_ERROR ("Failed to initialize OpenSSL\n"); + return -1; + } + } +#endif + + ut_avlInit (&ddsi_tcp_treedef, &ddsi_tcp_cache_g); + os_mutexInit (&ddsi_tcp_cache_lock_g); + + nn_log (LC_INFO | LC_CONFIG, "%s initialized\n", ddsi_name); + } + return 0; +} diff --git a/src/core/ddsi/src/ddsi_tran.c b/src/core/ddsi/src/ddsi_tran.c new file mode 100644 index 0000000..d8b65d0 --- /dev/null +++ b/src/core/ddsi/src/ddsi_tran.c @@ -0,0 +1,206 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include "os/os.h" +#include "ddsi/ddsi_tran.h" +#include "ddsi/q_config.h" +#include "ddsi/q_log.h" + +static ddsi_tran_factory_t ddsi_tran_factories = NULL; + +void ddsi_factory_add (ddsi_tran_factory_t factory) +{ + factory->m_factory = ddsi_tran_factories; + ddsi_tran_factories = factory; +} + +ddsi_tran_factory_t ddsi_factory_find (const char * type) +{ + ddsi_tran_factory_t factory = ddsi_tran_factories; + + while (factory) + { + if (strcmp (factory->m_typename, type) == 0) + { + break; + } + factory = factory->m_factory; + } + + return factory; +} + +void ddsi_tran_factories_fini (void) +{ + ddsi_tran_factory_t factory; + + while ((factory = ddsi_tran_factories) != NULL) { + ddsi_tran_factories = ddsi_tran_factories->m_factory; + + ddsi_factory_free(factory); + } +} + +void ddsi_factory_free (ddsi_tran_factory_t factory) +{ + if (factory && factory->m_free_fn) + { + (factory->m_free_fn) (); + } +} + +void ddsi_conn_free (ddsi_tran_conn_t conn) +{ + if (conn) + { + if (! conn->m_closed) + { + conn->m_closed = true; + if (conn->m_factory->m_close_conn_fn) + { + (conn->m_factory->m_close_conn_fn) (conn); + } + } + if (os_atomic_dec32_ov (&conn->m_count) == 1) + { + (conn->m_factory->m_release_conn_fn) (conn); + } + } +} + +void ddsi_conn_add_ref (ddsi_tran_conn_t conn) +{ + os_atomic_inc32 (&conn->m_count); +} + +extern void ddsi_factory_conn_init (ddsi_tran_factory_t factory, ddsi_tran_conn_t conn) +{ + os_atomic_st32 (&conn->m_count, 1); + conn->m_connless = factory->m_connless; + conn->m_stream = factory->m_stream; + conn->m_factory = factory; +} + +ssize_t ddsi_conn_read (ddsi_tran_conn_t conn, unsigned char * buf, size_t len) +{ + return (conn->m_closed) ? -1 : (conn->m_read_fn) (conn, buf, len); +} + +ssize_t ddsi_conn_write (ddsi_tran_conn_t conn, const struct msghdr * msg, size_t len, uint32_t flags) +{ + ssize_t ret = -1; + if (! conn->m_closed) + { + ret = (conn->m_write_fn) (conn, msg, len, flags); + } + + /* Check that write function is atomic (all or nothing) */ + + assert (ret == -1 || (size_t) ret == len); + return ret; +} + +bool ddsi_conn_peer_locator (ddsi_tran_conn_t conn, nn_locator_t * loc) +{ + if (conn->m_peer_locator_fn) + { + (conn->m_peer_locator_fn) (conn, loc); + return true; + } + return false; +} + +void ddsi_tran_free_qos (ddsi_tran_qos_t qos) +{ + os_free (qos); +} + +int ddsi_conn_join_mc (ddsi_tran_conn_t conn, const nn_locator_t *srcloc, const nn_locator_t *mcloc) +{ + return conn->m_factory->m_join_mc_fn (conn, srcloc, mcloc); +} + +int ddsi_conn_leave_mc (ddsi_tran_conn_t conn, const nn_locator_t *srcloc, const nn_locator_t *mcloc) +{ + return conn->m_factory->m_leave_mc_fn (conn, srcloc, mcloc); +} + +os_handle ddsi_tran_handle (ddsi_tran_base_t base) +{ + return (base->m_handle_fn) (base); +} + +ddsi_tran_qos_t ddsi_tran_create_qos (void) +{ + ddsi_tran_qos_t qos; + qos = (ddsi_tran_qos_t) os_malloc (sizeof (*qos)); + memset (qos, 0, sizeof (*qos)); + return qos; +} + +ddsi_tran_conn_t ddsi_factory_create_conn +( + ddsi_tran_factory_t factory, + uint32_t port, + ddsi_tran_qos_t qos +) +{ + return factory->m_create_conn_fn (port, qos); +} + +int ddsi_tran_locator (ddsi_tran_base_t base, nn_locator_t * loc) +{ + return (base->m_locator_fn) (base, loc); +} + +int ddsi_listener_listen (ddsi_tran_listener_t listener) +{ + return (listener->m_listen_fn) (listener); +} + +ddsi_tran_conn_t ddsi_listener_accept (ddsi_tran_listener_t listener) +{ + return (listener->m_accept_fn) (listener); +} + +void ddsi_tran_free (ddsi_tran_base_t base) +{ + if (base) + { + if (base->m_trantype == DDSI_TRAN_CONN) + { + ddsi_conn_free ((ddsi_tran_conn_t) base); + } + else + { + ddsi_listener_unblock ((ddsi_tran_listener_t) base); + ddsi_listener_free ((ddsi_tran_listener_t) base); + } + } +} + +void ddsi_listener_unblock (ddsi_tran_listener_t listener) +{ + if (listener) + { + (listener->m_factory->m_unblock_listener_fn) (listener); + } +} + +void ddsi_listener_free (ddsi_tran_listener_t listener) +{ + if (listener) + { + (listener->m_factory->m_release_listener_fn) (listener); + } +} diff --git a/src/core/ddsi/src/ddsi_udp.c b/src/core/ddsi/src/ddsi_udp.c new file mode 100644 index 0000000..8f480ae --- /dev/null +++ b/src/core/ddsi/src/ddsi_udp.c @@ -0,0 +1,337 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include "os/os.h" +#include "ddsi/ddsi_tran.h" +#include "ddsi/ddsi_udp.h" +#include "ddsi/q_nwif.h" +#include "ddsi/q_config.h" +#include "ddsi/q_log.h" +#include "ddsi/q_pcap.h" + +extern void ddsi_factory_conn_init (ddsi_tran_factory_t factory, ddsi_tran_conn_t conn); + +typedef struct ddsi_tran_factory * ddsi_udp_factory_t; + +typedef struct ddsi_udp_config +{ + struct nn_group_membership *mship; +} +* ddsi_udp_config_t; + +typedef struct ddsi_udp_conn +{ + struct ddsi_tran_conn m_base; + os_socket m_sock; +#if defined _WIN32 && !defined WINCE + WSAEVENT m_sockEvent; +#endif + int m_diffserv; +} +* ddsi_udp_conn_t; + +static struct ddsi_udp_config ddsi_udp_config_g; +static struct ddsi_tran_factory ddsi_udp_factory_g; +static os_atomic_uint32_t ddsi_udp_init_g = OS_ATOMIC_UINT32_INIT(0); + +static ssize_t ddsi_udp_conn_read (ddsi_tran_conn_t conn, unsigned char * buf, size_t len) +{ + int err; + ssize_t ret; + struct msghdr msghdr; + os_sockaddr_storage src; + struct iovec msg_iov; + socklen_t srclen = (socklen_t) sizeof (src); + + msg_iov.iov_base = (void*) buf; + msg_iov.iov_len = len; + + memset (&msghdr, 0, sizeof (msghdr)); + + msghdr.msg_name = &src; + msghdr.msg_namelen = srclen; + msghdr.msg_iov = &msg_iov; + msghdr.msg_iovlen = 1; + + do { + ret = recvmsg(((ddsi_udp_conn_t) conn)->m_sock, &msghdr, 0); + err = (ret == -1) ? os_getErrno() : 0; + } while (err == os_sockEINTR); + + if (ret > 0) + { + /* Check for udp packet truncation */ + if ((((size_t) ret) > len) +#if SYSDEPS_MSGHDR_FLAGS + || (msghdr.msg_flags & MSG_TRUNC) +#endif + ) + { + char addrbuf[INET6_ADDRSTRLEN_EXTENDED]; + sockaddr_to_string_with_port (addrbuf, &src); + NN_WARNING ("%s => %d truncated to %d\n", addrbuf, (int)ret, (int)len); + } + } + else if (err != os_sockENOTSOCK && err != os_sockECONNRESET) + { + NN_ERROR ("UDP recvmsg sock %d: ret %d errno %d\n", (int) ((ddsi_udp_conn_t) conn)->m_sock, (int) ret, err); + } + return ret; +} + +static ssize_t ddsi_udp_conn_write (ddsi_tran_conn_t conn, const struct msghdr * msg, size_t len, uint32_t flags) +{ + int err; + ssize_t ret; + unsigned retry = 2; + int sendflags = 0; + (void) flags; + (void) len; +#ifdef MSG_NOSIGNAL + sendflags |= MSG_NOSIGNAL; +#endif + do { + ddsi_udp_conn_t uc = (ddsi_udp_conn_t) conn; + ret = sendmsg (uc->m_sock, msg, sendflags); + err = (ret == -1) ? os_getErrno() : 0; +#if defined _WIN32 && !defined WINCE + if (err == os_sockEWOULDBLOCK) { + WSANETWORKEVENTS ev; + WaitForSingleObject(uc->m_sockEvent, INFINITE); + WSAEnumNetworkEvents(uc->m_sock, uc->m_sockEvent, &ev); + } +#endif + } while (err == os_sockEINTR || err == os_sockEWOULDBLOCK || (err == os_sockEPERM && retry-- > 0)); + if (ret > 0 && gv.pcap_fp) + { + os_sockaddr_storage sa; + socklen_t alen = sizeof (sa); + if (getsockname (((ddsi_udp_conn_t) conn)->m_sock, (struct sockaddr *) &sa, &alen) == -1) + memset(&sa, 0, sizeof(sa)); + write_pcap_sent (gv.pcap_fp, now (), &sa, msg, (size_t) ret); + } + else if (ret == -1) + { + switch (err) + { + case os_sockEPERM: + case os_sockECONNRESET: +#ifdef os_sockENETUNREACH + case os_sockENETUNREACH: +#endif +#ifdef os_sockEHOSTUNREACH + case os_sockEHOSTUNREACH: +#endif + break; + default: + NN_ERROR("ddsi_udp_conn_write failed with error code %d", err); + } + } + return ret; +} + +static os_handle ddsi_udp_conn_handle (ddsi_tran_base_t base) +{ + return ((ddsi_udp_conn_t) base)->m_sock; +} + +static bool ddsi_udp_supports (int32_t kind) +{ + return + ( + (!config.useIpv6 && (kind == NN_LOCATOR_KIND_UDPv4)) +#if OS_SOCKET_HAS_IPV6 + || (config.useIpv6 && (kind == NN_LOCATOR_KIND_UDPv6)) +#endif + ); +} + +static int ddsi_udp_conn_locator (ddsi_tran_base_t base, nn_locator_t *loc) +{ + int ret = -1; + ddsi_udp_conn_t uc = (ddsi_udp_conn_t) base; + os_sockaddr_storage * addr = &gv.extip; + + memset (loc, 0, sizeof (*loc)); + if (uc->m_sock != Q_INVALID_SOCKET) + { + loc->kind = ddsi_udp_factory_g.m_kind; + loc->port = uc->m_base.m_base.m_port; + + if (loc->kind == NN_LOCATOR_KIND_UDPv4) + { + memcpy (loc->address + 12, &((os_sockaddr_in*) addr)->sin_addr, 4); + } + else + { + memcpy (loc->address, &((os_sockaddr_in6*) addr)->sin6_addr, 16); + } + ret = 0; + } + return ret; +} + +static ddsi_tran_conn_t ddsi_udp_create_conn +( + uint32_t port, + ddsi_tran_qos_t qos +) +{ + int ret; + os_socket sock; + ddsi_udp_conn_t uc = NULL; + bool mcast = (bool) (qos ? qos->m_multicast : false); + + /* If port is zero, need to create dynamic port */ + + ret = make_socket + ( + &sock, + (unsigned short) port, + false, + mcast + ); + + if (ret == 0) + { + uc = (ddsi_udp_conn_t) os_malloc (sizeof (*uc)); + memset (uc, 0, sizeof (*uc)); + + uc->m_sock = sock; + uc->m_diffserv = qos ? qos->m_diffserv : 0; +#if defined _WIN32 && !defined WINCE + uc->m_sockEvent = WSACreateEvent(); + WSAEventSelect(uc->m_sock, uc->m_sockEvent, FD_WRITE); +#endif + + ddsi_factory_conn_init (&ddsi_udp_factory_g, &uc->m_base); + uc->m_base.m_base.m_port = get_socket_port (sock); + uc->m_base.m_base.m_trantype = DDSI_TRAN_CONN; + uc->m_base.m_base.m_multicast = mcast; + uc->m_base.m_base.m_handle_fn = ddsi_udp_conn_handle; + uc->m_base.m_base.m_locator_fn = ddsi_udp_conn_locator; + + uc->m_base.m_read_fn = ddsi_udp_conn_read; + uc->m_base.m_write_fn = ddsi_udp_conn_write; + + nn_log + ( + LC_INFO, + "ddsi_udp_create_conn %s socket %"PRIsock" port %u\n", + mcast ? "multicast" : "unicast", + uc->m_sock, + uc->m_base.m_base.m_port + ); +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + if ((uc->m_diffserv != 0) && (ddsi_udp_factory_g.m_kind == NN_LOCATOR_KIND_UDPv4)) + { + set_socket_diffserv (uc->m_sock, uc->m_diffserv); + } +#endif + } + else + { + if (config.participantIndex != PARTICIPANT_INDEX_AUTO) + { + NN_ERROR + ( + "UDP make_socket failed for %s port %u\n", + mcast ? "multicast" : "unicast", + port + ); + } + } + + return uc ? &uc->m_base : NULL; +} + +static int ddsi_udp_join_mc (ddsi_tran_conn_t conn, const nn_locator_t *srcloc, const nn_locator_t *mcloc) +{ + ddsi_udp_conn_t uc = (ddsi_udp_conn_t) conn; + os_sockaddr_storage mcip, srcip; + nn_loc_to_address (&mcip, mcloc); + if (srcloc) + nn_loc_to_address (&srcip, srcloc); + return join_mcgroups (ddsi_udp_config_g.mship, uc->m_sock, srcloc ? &srcip : NULL, &mcip); +} + +static int ddsi_udp_leave_mc (ddsi_tran_conn_t conn, const nn_locator_t *srcloc, const nn_locator_t *mcloc) +{ + ddsi_udp_conn_t uc = (ddsi_udp_conn_t) conn; + os_sockaddr_storage mcip, srcip; + nn_loc_to_address (&mcip, mcloc); + if (srcloc) + nn_loc_to_address (&srcip, srcloc); + return leave_mcgroups (ddsi_udp_config_g.mship, uc->m_sock, srcloc ? &srcip : NULL, &mcip); +} + +static void ddsi_udp_release_conn (ddsi_tran_conn_t conn) +{ + ddsi_udp_conn_t uc = (ddsi_udp_conn_t) conn; + nn_log + ( + LC_INFO, + "ddsi_udp_release_conn %s socket %"PRIsock" port %u\n", + conn->m_base.m_multicast ? "multicast" : "unicast", + uc->m_sock, + uc->m_base.m_base.m_port + ); + os_sockFree (uc->m_sock); +#if defined _WIN32 && !defined WINCE + WSACloseEvent(uc->m_sockEvent); +#endif + os_free (conn); +} + +void ddsi_udp_fini (void) +{ + if(os_atomic_dec32_nv (&ddsi_udp_init_g) == 0) { + free_group_membership(ddsi_udp_config_g.mship); + memset (&ddsi_udp_factory_g, 0, sizeof (ddsi_udp_factory_g)); + nn_log (LC_INFO | LC_CONFIG, "udp finalized\n"); + } +} + +int ddsi_udp_init (void) +{ + /* TODO: proper init_once. Either the call doesn't need it, in which case + * this can be removed. Or the call does, in which case it should be done right. + * The lack of locking suggests it isn't needed. + */ + if (os_atomic_inc32_nv (&ddsi_udp_init_g) == 1) + { + memset (&ddsi_udp_factory_g, 0, sizeof (ddsi_udp_factory_g)); + ddsi_udp_factory_g.m_kind = NN_LOCATOR_KIND_UDPv4; + ddsi_udp_factory_g.m_typename = "udp"; + ddsi_udp_factory_g.m_connless = true; + ddsi_udp_factory_g.m_supports_fn = ddsi_udp_supports; + ddsi_udp_factory_g.m_create_conn_fn = ddsi_udp_create_conn; + ddsi_udp_factory_g.m_release_conn_fn = ddsi_udp_release_conn; + ddsi_udp_factory_g.m_free_fn = ddsi_udp_fini; + ddsi_udp_factory_g.m_join_mc_fn = ddsi_udp_join_mc; + ddsi_udp_factory_g.m_leave_mc_fn = ddsi_udp_leave_mc; +#if OS_SOCKET_HAS_IPV6 + if (config.useIpv6) + { + ddsi_udp_factory_g.m_kind = NN_LOCATOR_KIND_UDPv6; + } +#endif + + ddsi_udp_config_g.mship = new_group_membership(); + + ddsi_factory_add (&ddsi_udp_factory_g); + + nn_log (LC_INFO | LC_CONFIG, "udp initialized\n"); + } + return 0; +} diff --git a/src/core/ddsi/src/q_addrset.c b/src/core/ddsi/src/q_addrset.c new file mode 100644 index 0000000..dbdbe06 --- /dev/null +++ b/src/core/ddsi/src/q_addrset.c @@ -0,0 +1,681 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include + +#include "os/os.h" + +#include "util/ut_avl.h" +#include "ddsi/q_log.h" +#include "ddsi/q_misc.h" +#include "ddsi/q_config.h" +#include "ddsi/q_addrset.h" +#include "ddsi/q_globals.h" /* gv.mattr */ + +/* So what does one do with const & mutexes? I need to take lock in a + pure function just in case some other thread is trying to change + something. Arguably, that means the thing isn't const; but one + could just as easily argue that "const" means "this call won't + change it". If it is globally visible before the call, it may + change anyway. + + Today, I'm taking the latter interpretation. But all the + const-discarding casts get moved into LOCK/UNLOCK macros. */ +#define LOCK(as) (os_mutexLock (&((struct addrset *) (as))->lock)) +#define TRYLOCK(as) (os_mutexTryLock (&((struct addrset *) (as))->lock)) +#define UNLOCK(as) (os_mutexUnlock (&((struct addrset *) (as))->lock)) + +static int compare_locators_vwrap (const void *va, const void *vb); + +static const ut_avlCTreedef_t addrset_treedef = + UT_AVL_CTREEDEF_INITIALIZER (offsetof (struct addrset_node, avlnode), offsetof (struct addrset_node, loc), compare_locators_vwrap, 0); + +static int add_addresses_to_addrset_1 (struct addrset *as, const char *ip, int port_mode, const char *msgtag, int req_mc, int mcgen_base, int mcgen_count, int mcgen_idx) +{ + char buf[INET6_ADDRSTRLEN_EXTENDED]; + os_sockaddr_storage tmpaddr; + nn_locator_t loc; + int32_t kind; + + if (config.useIpv6) + kind = config.tcp_enable ? NN_LOCATOR_KIND_TCPv6 : NN_LOCATOR_KIND_UDPv6; + else + kind = config.tcp_enable ? NN_LOCATOR_KIND_TCPv4 : NN_LOCATOR_KIND_UDPv4; + + if (!os_sockaddrStringToAddress (ip, (os_sockaddr *) &tmpaddr, !config.useIpv6)) + { + NN_ERROR ("%s: %s: not a valid address\n", msgtag, ip); + return -1; + } + if ((config.useIpv6 && tmpaddr.ss_family != AF_INET6) || (!config.useIpv6 && tmpaddr.ss_family != AF_INET)) + { + NN_ERROR ("%s: %s: not a valid IPv%d address\n", msgtag, ip, config.useIpv6 ? 6 : 4); + return -1; + } + nn_address_to_loc (&loc, &tmpaddr, kind); + if (req_mc && !is_mcaddr (&loc)) + { + NN_ERROR ("%s: %s: not a multicast address\n", msgtag, ip); + return -1; + } + + if (mcgen_base == -1 && mcgen_count == -1 && mcgen_idx == -1) + ; + else if (kind == NN_LOCATOR_KIND_UDPv4 && is_mcaddr(&loc) && mcgen_base >= 0 && mcgen_count > 0 && mcgen_base + mcgen_count < 28 && mcgen_idx >= 0 && mcgen_idx < mcgen_count) + { + nn_udpv4mcgen_address_t x; + memset(&x, 0, sizeof(x)); + memcpy(&x.ipv4, loc.address + 12, 4); + x.base = (unsigned char) mcgen_base; + x.count = (unsigned char) mcgen_count; + x.idx = (unsigned char) mcgen_idx; + memset(loc.address, 0, sizeof(loc.address)); + memcpy(loc.address, &x, sizeof(x)); + loc.kind = NN_LOCATOR_KIND_UDPv4MCGEN; + } + else + { + NN_ERROR ("%s: %s,%d,%d,%d: IPv4 multicast address generator invalid or out of place\n", + msgtag, ip, mcgen_base, mcgen_count, mcgen_idx); + return -1; + } + + if (port_mode >= 0) + { + loc.port = (unsigned) port_mode; + nn_log (LC_CONFIG, "%s: add %s", msgtag, locator_to_string_with_port (buf, &loc)); + add_to_addrset (as, &loc); + } + else + { + nn_log (LC_CONFIG, "%s: add ", msgtag); + if (!is_mcaddr (&loc)) + { + int i; + for (i = 0; i <= config.maxAutoParticipantIndex; i++) + { + int port = config.port_base + config.port_dg * config.domainId + i * config.port_pg + config.port_d1; + loc.port = (unsigned) port; + if (i == 0) + nn_log (LC_CONFIG, "%s", locator_to_string_with_port (buf, &loc)); + else + nn_log (LC_CONFIG, ", :%d", port); + add_to_addrset (as, &loc); + } + } + else + { + int port = port_mode; + if (port == -1) + port = config.port_base + config.port_dg * config.domainId + config.port_d0; + loc.port = (unsigned) port; + nn_log (LC_CONFIG, "%s", locator_to_string_with_port (buf, &loc)); + add_to_addrset (as, &loc); + } + } + + nn_log (LC_CONFIG, "\n"); + return 0; +} + +int add_addresses_to_addrset (struct addrset *as, const char *addrs, int port_mode, const char *msgtag, int req_mc) +{ + /* port_mode: -1 => take from string, if 0 & unicast, add for a range of participant indices; + port_mode >= 0 => always set port to port_mode + */ + char *addrs_copy, *ip, *cursor, *a; + int retval = -1; + addrs_copy = os_strdup (addrs); + ip = os_malloc (strlen (addrs) + 1); + cursor = addrs_copy; + while ((a = os_strsep (&cursor, ",")) != NULL) + { + int port = 0, pos; + int mcgen_base = -1, mcgen_count = -1, mcgen_idx = -1; + if (!config.useIpv6) + { + if (port_mode == -1 && sscanf (a, "%[^:]:%d%n", ip, &port, &pos) == 2 && a[pos] == 0) + ; /* XYZ:PORT */ + else if (sscanf (a, "%[^;];%d;%d;%d%n", ip, &mcgen_base, &mcgen_count, &mcgen_idx, &pos) == 4 && a[pos] == 0) + port = port_mode; /* XYZ;BASE;COUNT;IDX for IPv4 MC address generators */ + else if (sscanf (a, "%[^:]%n", ip, &pos) == 1 && a[pos] == 0) + port = port_mode; /* XYZ */ + else { /* XY:Z -- illegal, but conversion routine should flag it */ + strcpy (ip, a); + port = 0; + } + } + else + { + if (port_mode == -1 && sscanf (a, "[%[^]]]:%d%n", ip, &port, &pos) == 2 && a[pos] == 0) + ; /* [XYZ]:PORT */ + else if (sscanf (a, "[%[^]]]%n", ip, &pos) == 1 && a[pos] == 0) + port = port_mode; /* [XYZ] */ + else { /* XYZ -- let conversion routines handle errors */ + strcpy (ip, a); + port = 0; + } + } + + if ((port > 0 && port <= 65535) || (port_mode == -1 && port == -1)) { + if (add_addresses_to_addrset_1 (as, ip, port, msgtag, req_mc, mcgen_base, mcgen_count, mcgen_idx) < 0) + goto error; + } else { + NN_ERROR ("%s: %s: port %d invalid\n", msgtag, a, port); + } + } + retval = 0; + error: + os_free (ip); + os_free (addrs_copy); + return retval; +} + +int compare_locators (const nn_locator_t *a, const nn_locator_t *b) +{ + int c; + if (a->kind != b->kind) + return (int) (a->kind - b->kind); + else if ((c = memcmp (a->address, b->address, sizeof (a->address))) != 0) + return c; + else + return (int) (a->port - b->port); +} + +static int compare_locators_vwrap (const void *va, const void *vb) +{ + return compare_locators (va, vb); +} + +struct addrset *new_addrset (void) +{ + struct addrset *as = os_malloc (sizeof (*as)); + os_atomic_st32 (&as->refc, 1); + os_mutexInit (&as->lock); + ut_avlCInit (&addrset_treedef, &as->ucaddrs); + ut_avlCInit (&addrset_treedef, &as->mcaddrs); + return as; +} + +struct addrset *ref_addrset (struct addrset *as) +{ + if (as != NULL) + { + os_atomic_inc32 (&as->refc); + } + return as; +} + +void unref_addrset (struct addrset *as) +{ + if ((as != NULL) && (os_atomic_dec32_ov (&as->refc) == 1)) + { + ut_avlCFree (&addrset_treedef, &as->ucaddrs, os_free); + ut_avlCFree (&addrset_treedef, &as->mcaddrs, os_free); + os_mutexDestroy (&as->lock); + os_free (as); + } +} + +int is_mcaddr (const nn_locator_t *loc) +{ + os_sockaddr_storage tmp; + switch (loc->kind) + { + case NN_LOCATOR_KIND_UDPv4: { + const os_sockaddr_in *x; + nn_loc_to_address (&tmp, loc); + x = (const os_sockaddr_in *) &tmp; + return IN_MULTICAST (ntohl (x->sin_addr.s_addr)); + } +#if OS_SOCKET_HAS_IPV6 + case NN_LOCATOR_KIND_UDPv6: { + const os_sockaddr_in6 *x; + nn_loc_to_address (&tmp, loc); + x = (const os_sockaddr_in6 *) &tmp; + return IN6_IS_ADDR_MULTICAST (&x->sin6_addr); + } +#endif + case NN_LOCATOR_KIND_UDPv4MCGEN: + return 1; + default: { + return 0; + } + } +} + +void set_unspec_locator (nn_locator_t *loc) +{ + loc->kind = NN_LOCATOR_KIND_INVALID; + loc->port = NN_LOCATOR_PORT_INVALID; + memset (loc->address, 0, sizeof (loc->address)); +} + +int is_unspec_locator (const nn_locator_t *loc) +{ + static const nn_locator_t zloc; + return (loc->kind == NN_LOCATOR_KIND_INVALID && + loc->port == NN_LOCATOR_PORT_INVALID && + memcmp (&zloc.address, loc->address, sizeof (zloc.address)) == 0); +} + +#ifdef DDSI_INCLUDE_SSM +int is_ssm_mcaddr (const nn_locator_t *loc) +{ + os_sockaddr_storage tmp; + switch (loc->kind) + { + case NN_LOCATOR_KIND_UDPv4: { + const os_sockaddr_in *x; + nn_loc_to_address (&tmp, loc); + x = (const os_sockaddr_in *) &tmp; + return (((uint32_t) ntohl (x->sin_addr.s_addr)) >> 24) == 232; + } +#if OS_SOCKET_HAS_IPV6 + case NN_LOCATOR_KIND_UDPv6: { + const os_sockaddr_in6 *x; + nn_loc_to_address (&tmp, loc); + x = (const os_sockaddr_in6 *) &tmp; + return x->sin6_addr.s6_addr[0] == 0xff && (x->sin6_addr.s6_addr[1] & 0xf0) == 0x30; + } +#endif + default: { + return 0; + } + } +} + +int addrset_contains_ssm (const struct addrset *as) +{ + struct addrset_node *n; + ut_avlCIter_t it; + LOCK (as); + for (n = ut_avlCIterFirst (&addrset_treedef, &as->mcaddrs, &it); n; n = ut_avlCIterNext (&it)) + { + if (is_ssm_mcaddr (&n->loc)) + { + UNLOCK (as); + return 1; + } + } + UNLOCK (as); + return 0; +} + +int addrset_any_ssm (const struct addrset *as, nn_locator_t *dst) +{ + struct addrset_node *n; + ut_avlCIter_t it; + LOCK (as); + for (n = ut_avlCIterFirst (&addrset_treedef, &as->mcaddrs, &it); n; n = ut_avlCIterNext (&it)) + { + if (is_ssm_mcaddr (&n->loc)) + { + *dst = n->loc; + UNLOCK (as); + return 1; + } + } + UNLOCK (as); + return 0; +} + +int addrset_any_non_ssm_mc (const struct addrset *as, nn_locator_t *dst) +{ + struct addrset_node *n; + ut_avlCIter_t it; + LOCK (as); + for (n = ut_avlCIterFirst (&addrset_treedef, &as->mcaddrs, &it); n; n = ut_avlCIterNext (&it)) + { + if (!is_ssm_mcaddr (&n->loc)) + { + *dst = n->loc; + UNLOCK (as); + return 1; + } + } + UNLOCK (as); + return 0; +} +#endif + +int addrset_purge (struct addrset *as) +{ + LOCK (as); + ut_avlCFree (&addrset_treedef, &as->ucaddrs, os_free); + ut_avlCFree (&addrset_treedef, &as->mcaddrs, os_free); + UNLOCK (as); + return 0; +} + +void add_to_addrset (struct addrset *as, const nn_locator_t *loc) +{ + if (!is_unspec_locator (loc)) + { + ut_avlIPath_t path; + ut_avlCTree_t *tree = is_mcaddr (loc) ? &as->mcaddrs : &as->ucaddrs; + LOCK (as); + if (ut_avlCLookupIPath (&addrset_treedef, tree, loc, &path) == NULL) + { + struct addrset_node *n = os_malloc (sizeof (*n)); + n->loc = *loc; + ut_avlCInsertIPath (&addrset_treedef, tree, n, &path); + } + UNLOCK (as); + } +} + +void remove_from_addrset (struct addrset *as, const nn_locator_t *loc) +{ + ut_avlDPath_t path; + ut_avlCTree_t *tree = is_mcaddr (loc) ? &as->mcaddrs : &as->ucaddrs; + struct addrset_node *n; + LOCK (as); + if ((n = ut_avlCLookupDPath (&addrset_treedef, tree, loc, &path)) != NULL) + { + ut_avlCDeleteDPath (&addrset_treedef, tree, n, &path); + os_free (n); + } + UNLOCK (as); +} + +void copy_addrset_into_addrset_uc (struct addrset *as, const struct addrset *asadd) +{ + struct addrset_node *n; + ut_avlCIter_t it; + LOCK (asadd); + for (n = ut_avlCIterFirst (&addrset_treedef, &asadd->ucaddrs, &it); n; n = ut_avlCIterNext (&it)) + add_to_addrset (as, &n->loc); + UNLOCK (asadd); +} + +void copy_addrset_into_addrset_mc (struct addrset *as, const struct addrset *asadd) +{ + struct addrset_node *n; + ut_avlCIter_t it; + LOCK (asadd); + for (n = ut_avlCIterFirst (&addrset_treedef, &asadd->mcaddrs, &it); n; n = ut_avlCIterNext (&it)) + add_to_addrset (as, &n->loc); + UNLOCK (asadd); +} + +void copy_addrset_into_addrset (struct addrset *as, const struct addrset *asadd) +{ + copy_addrset_into_addrset_uc (as, asadd); + copy_addrset_into_addrset_mc (as, asadd); +} + +#ifdef DDSI_INCLUDE_SSM +void copy_addrset_into_addrset_no_ssm_mc (struct addrset *as, const struct addrset *asadd) +{ + struct addrset_node *n; + ut_avlCIter_t it; + LOCK (asadd); + for (n = ut_avlCIterFirst (&addrset_treedef, &asadd->mcaddrs, &it); n; n = ut_avlCIterNext (&it)) + { + if (!is_ssm_mcaddr (&n->loc)) + add_to_addrset (as, &n->loc); + } + UNLOCK (asadd); + +} + +void copy_addrset_into_addrset_no_ssm (struct addrset *as, const struct addrset *asadd) +{ + copy_addrset_into_addrset_uc (as, asadd); + copy_addrset_into_addrset_no_ssm_mc (as, asadd); +} + +void addrset_purge_ssm (struct addrset *as) +{ + struct addrset_node *n; + LOCK (as); + n = ut_avlCFindMin (&addrset_treedef, &as->mcaddrs); + while (n) + { + struct addrset_node *n1 = n; + n = ut_avlCFindSucc (&addrset_treedef, &as->mcaddrs, n); + if (is_ssm_mcaddr (&n1->loc)) + { + ut_avlCDelete (&addrset_treedef, &as->mcaddrs, n1); + os_free (n1); + } + } + UNLOCK (as); +} +#endif + +size_t addrset_count (const struct addrset *as) +{ + if (as == NULL) + return 0; + else + { + size_t count; + LOCK (as); + count = ut_avlCCount (&as->ucaddrs) + ut_avlCCount (&as->mcaddrs); + UNLOCK (as); + return count; + } +} + +size_t addrset_count_uc (const struct addrset *as) +{ + if (as == NULL) + return 0; + else + { + size_t count; + LOCK (as); + count = ut_avlCCount (&as->ucaddrs); + UNLOCK (as); + return count; + } +} + +size_t addrset_count_mc (const struct addrset *as) +{ + if (as == NULL) + return 0; + else + { + size_t count; + LOCK (as); + count = ut_avlCCount (&as->mcaddrs); + UNLOCK (as); + return count; + } +} + +int addrset_empty_uc (const struct addrset *as) +{ + int isempty; + LOCK (as); + isempty = ut_avlCIsEmpty (&as->ucaddrs); + UNLOCK (as); + return isempty; +} + +int addrset_empty_mc (const struct addrset *as) +{ + int isempty; + LOCK (as); + isempty = ut_avlCIsEmpty (&as->mcaddrs); + UNLOCK (as); + return isempty; +} + +int addrset_empty (const struct addrset *as) +{ + int isempty; + LOCK (as); + isempty = ut_avlCIsEmpty (&as->ucaddrs) && ut_avlCIsEmpty (&as->mcaddrs); + UNLOCK (as); + return isempty; +} + +int addrset_any_uc (const struct addrset *as, nn_locator_t *dst) +{ + LOCK (as); + if (ut_avlCIsEmpty (&as->ucaddrs)) + { + UNLOCK (as); + return 0; + } + else + { + const struct addrset_node *n = ut_avlCRootNonEmpty (&addrset_treedef, &as->ucaddrs); + *dst = n->loc; + UNLOCK (as); + return 1; + } +} + +int addrset_any_mc (const struct addrset *as, nn_locator_t *dst) +{ + LOCK (as); + if (ut_avlCIsEmpty (&as->mcaddrs)) + { + UNLOCK (as); + return 0; + } + else + { + const struct addrset_node *n = ut_avlCRootNonEmpty (&addrset_treedef, &as->mcaddrs); + *dst = n->loc; + UNLOCK (as); + return 1; + } +} + +struct addrset_forall_helper_arg +{ + addrset_forall_fun_t f; + void * arg; +}; + +static void addrset_forall_helper (void *vnode, void *varg) +{ + const struct addrset_node *n = vnode; + struct addrset_forall_helper_arg *arg = varg; + arg->f (&n->loc, arg->arg); +} + +size_t addrset_forall_count (struct addrset *as, addrset_forall_fun_t f, void *arg) +{ + struct addrset_forall_helper_arg arg1; + size_t count; + arg1.f = f; + arg1.arg = arg; + LOCK (as); + ut_avlCWalk (&addrset_treedef, &as->mcaddrs, addrset_forall_helper, &arg1); + ut_avlCWalk (&addrset_treedef, &as->ucaddrs, addrset_forall_helper, &arg1); + count = ut_avlCCount (&as->ucaddrs) + ut_avlCCount (&as->mcaddrs); + UNLOCK (as); + return count; +} + +void addrset_forall (struct addrset *as, addrset_forall_fun_t f, void *arg) +{ + (void) addrset_forall_count (as, f, arg); +} + +int addrset_forone (struct addrset *as, addrset_forone_fun_t f, void *arg) +{ + unsigned i; + addrset_node_t n; + ut_avlCTree_t *trees[2]; + ut_avlCIter_t iter; + + trees[0] = &as->mcaddrs; + trees[1] = &as->ucaddrs; + + for (i = 0; i < 2u; i++) + { + n = (addrset_node_t) ut_avlCIterFirst (&addrset_treedef, trees[i], &iter); + while (n) + { + if ((f) (&n->loc, arg) > 0) + { + return 0; + } + n = (addrset_node_t) ut_avlCIterNext (&iter); + } + } + return -1; +} + +struct log_addrset_helper_arg +{ + logcat_t tf; +}; + +static void log_addrset_helper (const nn_locator_t *n, void *varg) +{ + const struct log_addrset_helper_arg *arg = varg; + char buf[INET6_ADDRSTRLEN_EXTENDED]; + if (config.enabled_logcats & arg->tf) + nn_log (arg->tf, " %s", locator_to_string_with_port (buf, n)); +} + +void nn_log_addrset (logcat_t tf, const char *prefix, const struct addrset *as) +{ + if (config.enabled_logcats & tf) + { + struct log_addrset_helper_arg arg; + arg.tf = tf; + nn_log (tf, "%s", prefix); + addrset_forall ((struct addrset *) as, log_addrset_helper, &arg); /* drop const, we know it is */ + } +} + +static int addrset_eq_onesidederr1 (const ut_avlCTree_t *at, const ut_avlCTree_t *bt) +{ + /* Just checking the root */ + if (ut_avlCIsEmpty (at) && ut_avlCIsEmpty (bt)) { + return 1; + } else if (ut_avlCIsSingleton (at) && ut_avlCIsSingleton (bt)) { + const struct addrset_node *a = ut_avlCRootNonEmpty (&addrset_treedef, at); + const struct addrset_node *b = ut_avlCRootNonEmpty (&addrset_treedef, bt); + return compare_locators (&a->loc, &b->loc) == 0; + } else { + return 0; + } +} + +int addrset_eq_onesidederr (const struct addrset *a, const struct addrset *b) +{ + int iseq; + if (a == b) + return 1; + if (a == NULL || b == NULL) + return 0; + LOCK (a); + if (TRYLOCK (b) == os_resultSuccess) + { + iseq = + addrset_eq_onesidederr1 (&a->ucaddrs, &b->ucaddrs) && + addrset_eq_onesidederr1 (&a->mcaddrs, &b->mcaddrs); + UNLOCK (b); + } + else + { + /* We could try , in a loop, &c. Or we can + just decide it isn't worth the bother. Which it isn't because + it doesn't have to be an exact check on equality. A possible + improvement would be to use an rwlock. */ + iseq = 0; + } + UNLOCK (a); + return iseq; +} diff --git a/src/core/ddsi/src/q_bitset_inlines.c b/src/core/ddsi/src/q_bitset_inlines.c new file mode 100644 index 0000000..dfcc62a --- /dev/null +++ b/src/core/ddsi/src/q_bitset_inlines.c @@ -0,0 +1,16 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#define SUPPRESS_BITSET_INLINES + +#include "ddsi/q_bitset.h" +#include "ddsi/q_bitset_template.h" + diff --git a/src/core/ddsi/src/q_bswap.c b/src/core/ddsi/src/q_bswap.c new file mode 100644 index 0000000..88af94a --- /dev/null +++ b/src/core/ddsi/src/q_bswap.c @@ -0,0 +1,80 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include "ddsi/q_bswap.h" + +nn_guid_prefix_t nn_hton_guid_prefix (nn_guid_prefix_t p) +{ + int i; + for (i = 0; i < 3; i++) + p.u[i] = toBE4u (p.u[i]); + return p; +} + +nn_guid_prefix_t nn_ntoh_guid_prefix (nn_guid_prefix_t p) +{ + int i; + for (i = 0; i < 3; i++) + p.u[i] = fromBE4u (p.u[i]); + return p; +} + +nn_entityid_t nn_hton_entityid (nn_entityid_t e) +{ + e.u = toBE4u (e.u); + return e; +} + +nn_entityid_t nn_ntoh_entityid (nn_entityid_t e) +{ + e.u = fromBE4u (e.u); + return e; +} + +nn_guid_t nn_hton_guid (nn_guid_t g) +{ + g.prefix = nn_hton_guid_prefix (g.prefix); + g.entityid = nn_hton_entityid (g.entityid); + return g; +} + +nn_guid_t nn_ntoh_guid (nn_guid_t g) +{ + g.prefix = nn_ntoh_guid_prefix (g.prefix); + g.entityid = nn_ntoh_entityid (g.entityid); + return g; +} + +void bswap_sequence_number_set_hdr (nn_sequence_number_set_t *snset) +{ + bswapSN (&snset->bitmap_base); + snset->numbits = bswap4u (snset->numbits); +} + +void bswap_sequence_number_set_bitmap (nn_sequence_number_set_t *snset) +{ + unsigned i, n = (snset->numbits + 31) / 32; + for (i = 0; i < n; i++) + snset->bits[i] = bswap4u (snset->bits[i]); +} + +void bswap_fragment_number_set_hdr (nn_fragment_number_set_t *fnset) +{ + fnset->bitmap_base = bswap4u (fnset->bitmap_base); + fnset->numbits = bswap4u (fnset->numbits); +} + +void bswap_fragment_number_set_bitmap (nn_fragment_number_set_t *fnset) +{ + unsigned i, n = (fnset->numbits + 31) / 32; + for (i = 0; i < n; i++) + fnset->bits[i] = bswap4u (fnset->bits[i]); +} diff --git a/src/core/ddsi/src/q_bswap_inlines.c b/src/core/ddsi/src/q_bswap_inlines.c new file mode 100644 index 0000000..74afe33 --- /dev/null +++ b/src/core/ddsi/src/q_bswap_inlines.c @@ -0,0 +1,15 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#define SUPPRESS_BSWAP_INLINES + +#include "ddsi/q_bswap.h" +#include "ddsi/q_bswap_template.h" diff --git a/src/core/ddsi/src/q_builtin_topic.c b/src/core/ddsi/src/q_builtin_topic.c new file mode 100644 index 0000000..03119d8 --- /dev/null +++ b/src/core/ddsi/src/q_builtin_topic.c @@ -0,0 +1,183 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include + +#include "ddsi/q_misc.h" +#include "ddsi/q_config.h" +#include "ddsi/q_entity.h" +#include "ddsi/q_builtin_topic.h" + +#include "dds_builtinTopics.h" + +static void generate_user_data (_Out_ DDS_UserDataQosPolicy *a, _In_ const nn_xqos_t *xqos) +{ + if (!(xqos->present & QP_USER_DATA) || (xqos->user_data.length == 0)) + { + a->value._maximum = 0; + a->value._length = 0; + a->value._buffer = NULL; + a->value._release = false; + } else { + a->value._maximum = xqos->user_data.length; + a->value._length = xqos->user_data.length; + a->value._buffer = xqos->user_data.value; + a->value._release = false; + } +} + +static void +generate_product_data( + _Out_ DDS_ProductDataQosPolicy *a, + _In_ const struct entity_common *participant, + _In_ const nn_plist_t *plist) +{ + /* replicate format generated in v_builtinCreateCMParticipantInfo() */ + static const char product_tag[] = "Product"; + static const char exec_name_tag[] = "ExecName"; + static const char participant_name_tag[] = "ParticipantName"; + static const char process_id_tag[] = "PID"; + static const char node_name_tag[] = "NodeName"; + static const char federation_id_tag[] = "FederationId"; + static const char vendor_id_tag[] = "VendorId"; + static const char service_type_tag[] = "ServiceType"; + const size_t cdata_overhead = 12; /* */ + const size_t tag_overhead = 5; /* <> and */ + char pidstr[11]; /* unsigned 32-bits, so max < 5e9, or 10 chars + terminator */ + char federationidstr[20]; /* max 2 * unsigned 32-bits hex + separator, terminator */ + char vendoridstr[22]; /* max 2 * unsigned 32-bits + seperator, terminator */ + char servicetypestr[11]; /* unsigned 32-bits */ + unsigned servicetype; + size_t len = 1 + 2*(sizeof(product_tag)-1) + tag_overhead; + + if (plist->present & PP_PRISMTECH_EXEC_NAME) + len += 2*(sizeof(exec_name_tag)-1) + cdata_overhead + tag_overhead + strlen(plist->exec_name); + if (plist->present & PP_ENTITY_NAME) + len += 2*(sizeof(participant_name_tag)-1) + cdata_overhead + tag_overhead + strlen(plist->entity_name); + if (plist->present & PP_PRISMTECH_PROCESS_ID) + { + int n = snprintf (pidstr, sizeof (pidstr), "%u", plist->process_id); + assert (n > 0 && (size_t) n < sizeof (pidstr)); + len += 2*(sizeof(process_id_tag)-1) + tag_overhead + (size_t) n; + } + if (plist->present & PP_PRISMTECH_NODE_NAME) + len += 2*(sizeof(node_name_tag)-1) + cdata_overhead + tag_overhead + strlen(plist->node_name); + + { + int n = snprintf (vendoridstr, sizeof (vendoridstr), "%u.%u", plist->vendorid.id[0], plist->vendorid.id[1]); + assert (n > 0 && (size_t) n < sizeof (vendoridstr)); + len += 2*(sizeof(vendor_id_tag)-1) + tag_overhead + (size_t) n; + } + + { + int n; + if (vendor_is_opensplice (plist->vendorid)) + n = snprintf (federationidstr, sizeof (federationidstr), "%x", participant->guid.prefix.u[0]); + else + n = snprintf (federationidstr, sizeof (federationidstr), "%x:%x", participant->guid.prefix.u[0], participant->guid.prefix.u[1]); + assert (n > 0 && (size_t) n < sizeof (federationidstr)); + len += 2*(sizeof(federation_id_tag)-1) + tag_overhead + (size_t) n; + } + + if (plist->present & PP_PRISMTECH_SERVICE_TYPE) + servicetype = plist->service_type; + else + servicetype = 0; + + { + int n = snprintf (servicetypestr, sizeof (servicetypestr), "%u", (unsigned) servicetype); + assert (n > 0 && (size_t) n < sizeof (servicetypestr)); + len += 2*(sizeof(service_type_tag)-1) + tag_overhead + (size_t) n; + } + + a->value = os_malloc(len); + + { + char *p = a->value; + int n; + n = snprintf (p, len, "<%s>", product_tag); assert (n >= 0 && (size_t) n < len); p += n; len -= (size_t) n; + if (plist->present & PP_PRISMTECH_EXEC_NAME) + { + n = snprintf (p, len, "<%s>", exec_name_tag, plist->exec_name, exec_name_tag); + assert (n >= 0 && (size_t) n < len); + p += n; len -= (size_t) n; + } + if (plist->present & PP_ENTITY_NAME) + { + n = snprintf (p, len, "<%s>", participant_name_tag, plist->entity_name, participant_name_tag); + assert (n >= 0 && (size_t) n < len); + p += n; len -= (size_t) n; + } + if (plist->present & PP_PRISMTECH_PROCESS_ID) + { + n = snprintf (p, len, "<%s>%s", process_id_tag, pidstr, process_id_tag); + assert (n >= 0 && (size_t) n < len); + p += n; len -= (size_t) n; + } + if (plist->present & PP_PRISMTECH_NODE_NAME) + { + n = snprintf (p, len, "<%s>", node_name_tag, plist->node_name, node_name_tag); + assert (n >= 0 && (size_t) n < len); + p += n; len -= (size_t) n; + } + n = snprintf (p, len, "<%s>%s", federation_id_tag, federationidstr, federation_id_tag); + assert (n >= 0 && (size_t) n < len); + p += n; len -= (size_t) n; + n = snprintf (p, len, "<%s>%s", vendor_id_tag, vendoridstr, vendor_id_tag); + assert (n >= 0 && (size_t) n < len); + p += n; len -= (size_t) n; + + { + n = snprintf (p, len, "<%s>%s", service_type_tag, servicetypestr, service_type_tag); + assert (n >= 0 && (size_t) n < len); + p += n; len -= (size_t) n; + } + + n = snprintf (p, len, "", product_tag); + assert (n >= 0 && (size_t) n == len-1); + (void) n; + } +} + +static void generate_key (_Out_ DDS_BuiltinTopicKey_t *a, _In_ const nn_guid_prefix_t *gid) +{ + (*a)[0] = gid->u[0]; + (*a)[1] = gid->u[1]; + (*a)[2] = gid->u[2]; +} + +void +propagate_builtin_topic_participant( + _In_ const struct entity_common *participant, + _In_ const nn_plist_t *plist, + _In_ nn_wctime_t timestamp, + _In_ int alive) +{ + DDS_ParticipantBuiltinTopicData data; + generate_key(&(data.key), &(participant->guid.prefix)); + generate_user_data(&(data.user_data), &(plist->qos)); + forward_builtin_participant(&data, timestamp, alive); +} + +void +propagate_builtin_topic_cmparticipant( + _In_ const struct entity_common *participant, + _In_ const nn_plist_t *plist, + _In_ nn_wctime_t timestamp, + _In_ int alive) +{ + DDS_CMParticipantBuiltinTopicData data; + generate_key(&(data.key), &(participant->guid.prefix)); + generate_product_data(&(data.product), participant, plist); + forward_builtin_cmparticipant(&data, timestamp, alive); + os_free(data.product.value); +} diff --git a/src/core/ddsi/src/q_config.c b/src/core/ddsi/src/q_config.c new file mode 100644 index 0000000..37ecb0a --- /dev/null +++ b/src/core/ddsi/src/q_config.c @@ -0,0 +1,2817 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include +#include +#include +#include + +#include "os/os.h" + +#include "ddsi/q_config.h" +#include "ddsi/q_log.h" +#include "util/ut_avl.h" +#include "ddsi/q_unused.h" +#include "ddsi/q_misc.h" +#include "ddsi/q_addrset.h" +#include "ddsi/q_nwif.h" +#include "ddsi/q_error.h" +#include "ddsi/sysdeps.h" + +#include "util/ut_xmlparser.h" +#include "util/ut_expand_envvars.h" + +#include "ddsc/ddsc_project.h" + +#define WARN_DEPRECATED_ALIAS 1 +#define WARN_DEPRECATED_UNIT 1 +#define MAX_PATH_DEPTH 10 /* max nesting level of configuration elements */ + +struct cfgelem; +struct cfgst; + +typedef int(*init_fun_t) (struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem); +typedef int(*update_fun_t) (struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int first, const char *value); +typedef void(*free_fun_t) (struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem); +typedef void(*print_fun_t) (struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default); + +#ifdef DDSI_INCLUDE_SECURITY +struct q_security_plugins q_security_plugin = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; +#endif + +struct unit { + const char *name; + int64_t multiplier; +}; + +struct cfgelem { + const char *name; + const struct cfgelem *children; + const struct cfgelem *attributes; + int multiplicity; + const char *defvalue; /* NULL -> no default */ + int relative_offset; + int elem_offset; + init_fun_t init; + update_fun_t update; + free_fun_t free; + print_fun_t print; + const char *description; +}; + +struct cfgst_nodekey { + const struct cfgelem *e; +}; + +struct cfgst_node { + ut_avlNode_t avlnode; + struct cfgst_nodekey key; + int count; + int failed; + int is_default; +}; + +struct cfgst { + ut_avlTree_t found; + struct config *cfg; + + + /* path_depth, isattr and path together control the formatting of + error messages by cfg_error() */ + int path_depth; + int isattr[MAX_PATH_DEPTH]; + const struct cfgelem *path[MAX_PATH_DEPTH]; + void *parent[MAX_PATH_DEPTH]; +}; + +/* "trace" is special: it enables (nearly) everything */ +static const char *logcat_names[] = { + "fatal", "error", "warning", "config", "info", "discovery", "data", "radmin", "timing", "traffic", "topic", "tcp", "plist", "whc", "throttle", "trace", NULL +}; +static const logcat_t logcat_codes[] = { + LC_FATAL, LC_ERROR, LC_WARNING, LC_CONFIG, LC_INFO, LC_DISCOVERY, LC_DATA, LC_RADMIN, LC_TIMING, LC_TRAFFIC, LC_TOPIC, LC_TCP, LC_PLIST, LC_WHC, LC_THROTTLE, LC_ALLCATS +}; + +/* We want the tracing/verbosity settings to be fixed while parsing +the configuration, so we update this variable instead. */ +static unsigned enabled_logcats; + +static int cfgst_node_cmp(const void *va, const void *vb); +static const ut_avlTreedef_t cfgst_found_treedef = +UT_AVL_TREEDEF_INITIALIZER(offsetof(struct cfgst_node, avlnode), offsetof(struct cfgst_node, key), cfgst_node_cmp, 0); + +#define DU(fname) static int uf_##fname (struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int first, const char *value) +#define PF(fname) static void pf_##fname (struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +#define DUPF(fname) DU(fname) ; PF(fname) +PF(nop); +DUPF(networkAddress); +DUPF(networkAddresses); +DU(ipv4); +DUPF(allow_multicast); +DUPF(boolean); +DUPF(negated_boolean); +DUPF(string); +DU(tracingOutputFileName); +DU(verbosity); +DUPF(logcat); +DUPF(float); +DUPF(int); +DUPF(uint); +DUPF(int32); +#if 0 +DUPF(uint32); +#endif +DU(natint); +DU(natint_255); +DUPF(participantIndex); +DU(port); +DU(dyn_port); +DUPF(memsize); +DU(duration_inf); +DU(duration_ms_1hr); +DU(duration_ms_1s); +DU(duration_us_1s); +PF(duration); +DUPF(standards_conformance); +DUPF(besmode); +DUPF(retransmit_merging); +DUPF(sched_prio_class); +DUPF(sched_class); +DUPF(maybe_memsize); +DUPF(maybe_int32); +#ifdef DDSI_INCLUDE_ENCRYPTION +DUPF(cipher); +#endif +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING +DUPF(bandwidth); +#endif +DU(domainId); +DUPF(durability_cdr); +#undef DUPF +#undef DU +#undef PF + +#define DF(fname) static void fname (struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem) +DF(ff_free); +DF(ff_networkAddresses); +#undef DF + +#define DI(fname) static int fname (struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem) +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS +DI(if_channel); +#endif /* DDSI_INCLUDE_NETWORK_CHANNELS */ +#ifdef DDSI_INCLUDE_ENCRYPTION +DI(if_security_profile); +#endif +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS +DI(if_network_partition); +DI(if_ignored_partition); +DI(if_partition_mapping); +#endif +DI(if_peer); +DI(if_thread_properties); +#undef DI + +#define CO(name) ((int) offsetof (struct config, name)) +#define ABSOFF(name) 0, CO (name) +#define RELOFF(parent,name) 1, ((int) offsetof (struct parent, name)) +#define NODATA 1, NULL, 0, 0, 0, 0, 0, 0 +#define END_MARKER { NULL, NULL, NULL, NODATA, NULL } +#define WILDCARD { "*", NULL, NULL, NODATA, NULL } +#define LEAF(name) name, NULL, NULL +#define LEAF_W_ATTRS(name, attrs) name, NULL, attrs +#define GROUP(name, children) name, children, NULL, 1, NULL, 0, 0, 0, 0, 0, 0 +#define MGROUP(name, children, attrs) name, children, attrs +#define ATTR(name) name, NULL, NULL +/* MOVED: whereto must be a path relative to DDSI2Service, may not be used in/for lists and only for elements, may not be chained */ +#define MOVED(name, whereto) ">" name, NULL, NULL, 0, whereto, 0, 0, 0, 0, 0, 0, NULL +static const struct cfgelem timestamp_cfgattrs[] = { + { ATTR("absolute"), 1, "false", ABSOFF(tracingRelativeTimestamps), 0, uf_negated_boolean, 0, pf_negated_boolean, + "

This option has no effect

" }, + END_MARKER +}; + +static const struct cfgelem general_cfgelems[] = { + { LEAF("NetworkInterfaceAddress"), 1, "auto", ABSOFF(networkAddressString), 0, uf_networkAddress, ff_free, pf_networkAddress, + "

This element specifies the preferred network interface for use by DDSI2E. The preferred network interface determines the IP address that DDSI2E advertises in the discovery protocol (but see also General/ExternalNetworkAddress), and is also the only interface over which multicasts are transmitted. The interface can be identified by its IP address, network interface name or network portion of the address. If the value \"auto\" is entered here, DDSI2E will select what it considers the most suitable interface.

" }, + { LEAF("MulticastRecvNetworkInterfaceAddresses"), 1, "preferred", ABSOFF(networkRecvAddressStrings), 0, uf_networkAddresses, ff_networkAddresses, pf_networkAddresses, + "

This element specifies on which network interfaces DDSI2E listens to multicasts. The following options are available:

\n\ +
    \n\ +
  • all: listen for multicasts on all multicast-capable interfaces; or
  • \n\ +
  • any: listen for multicasts on the operating system default interface; or
  • \n\ +
  • preferred: listen for multicasts on the preferred interface (General/NetworkInterfaceAddress); or
  • \n\ +
  • none: does not listen for multicasts on any interface; or
  • \n\ +
  • a comma-separated list of network addresses: configures DDSI2E to listen for multicasts on all of the listed addresses.
  • \n\ +
\n\ +

If DDSI2E is in IPv6 mode and the address of the preferred network interface is a link-local address, \"all\" is treated as a synonym for \"preferred\" and a comma-separated list is treated as \"preferred\" if it contains the preferred interface and as \"none\" if not.

" }, +{ LEAF("ExternalNetworkAddress"), 1, "auto", ABSOFF(externalAddressString), 0, uf_networkAddress, ff_free, pf_networkAddress, +"

This element allows explicitly overruling the network address DDSI2E advertises in the discovery protocol, which by default is the address of the preferred network interface (General/NetworkInterfaceAddress), to allow DDSI2E to communicate across a Network Address Translation (NAT) device.

" }, +{ LEAF("ExternalNetworkMask"), 1, "0.0.0.0", ABSOFF(externalMaskString), 0, uf_string, ff_free, pf_string, +"

This element specifies the network mask of the external network address. This element is relevant only when an external network address (General/ExternalNetworkAddress) is explicitly configured. In this case locators received via the discovery protocol that are within the same external subnet (as defined by this mask) will be translated to an internal address by replacing the network portion of the external address with the corresponding portion of the preferred network interface address. This option is IPv4-only.

" }, +{ LEAF("AllowMulticast"), 1, "true", ABSOFF(allowMulticast), 0, uf_allow_multicast, 0, pf_allow_multicast, +"

This element controls whether DDSI2E uses multicasts for data traffic.

\n\ +

It is a comma-separated list of some of the following keywords: \"spdp\", \"asm\", \"ssm\", or either of \"false\" or \"true\".

\n\ +
    \n\ +
  • spdp: enables the use of ASM (any-source multicast) for participant discovery
  • \n\ +
  • asm: enables the use of ASM for all traffic (including SPDP)
  • \n\ +
  • ssm: enables the use of SSM (source-specific multicast) for all non-SPDP traffic (if supported)
  • \n\ +
\n\ +

When set to \"false\" all multicasting is disabled. The default, \"true\" enables full use of multicasts. Listening for multicasts can be controlled by General/MulticastRecvNetworkInterfaceAddresses.

" }, +{ LEAF("MulticastTimeToLive"), 1, "32", ABSOFF(multicast_ttl), 0, uf_natint_255, 0, pf_int, +"

This element specifies the time-to-live setting for outgoing multicast packets.

" }, +{ LEAF("DontRoute"), 1, "false", ABSOFF(dontRoute), 0, uf_boolean, 0, pf_boolean, +"

This element allows setting the SO_DONTROUTE option for outgoing packets, to bypass the local routing tables. This is generally useful only when the routing tables cannot be trusted, which is highly unusual.

" }, +{ LEAF("UseIPv6"), 1, "false", ABSOFF(useIpv6), 0, uf_boolean, 0, pf_boolean , +"

This element can be used to DDSI2E use IPv6 instead of IPv4. This is currently an either/or switch.

" }, +{ LEAF("EnableMulticastLoopback"), 1, "true", ABSOFF(enableMulticastLoopback), 0, uf_boolean, 0, pf_boolean, +"

This element specifies whether DDSI2E allows IP multicast packets to be visible to all DDSI participants in the same node, including itself. It must be \"true\" for intra-node multicast communications, but if a node runs only a single DDSI2E service and does not host any other DDSI-capable programs, it should be set to \"false\" for improved performance.

" }, +{ LEAF("EnableLoopback"), 1, "false", ABSOFF(enableLoopback), 0, uf_boolean, 0, pf_boolean, +"

This element specifies whether DDSI packets are visible to all DDSI participants in the same process. It must be \"true\" for intra-process communications, i.e. a reader and writer communicating in the same address space. If enabled and using multicast then EnableMulticastLoopback must also be enabled.

" }, +{ LEAF("StartupModeDuration"), 1, "2 s", ABSOFF(startup_mode_duration), 0, uf_duration_ms_1hr, 0, pf_duration, +"

This element specifies how long the DDSI2E remains in its \"startup\" mode. While in \"startup\" mode all volatile reliable data published on the local node is retained as-if it were transient-local data, allowing existing readers on remote nodes to obtain the data even though discovering them takes some time. Best-effort data by definition need not arrive, and transient and persistent data are covered by the durability service.

\n\ +

Once the system is stable, DDSI2E keeps track of the existence of remote readers whether or not matching writers exist locally, avoiding this discovery delay and ensuring this is merely a node startup issue.

\n\ +

Setting General/StartupModeDuration to 0s will disable it.

" }, +{ LEAF("StartupModeCoversTransient"), 1, "true", ABSOFF(startup_mode_full), 0, uf_boolean, 0, pf_boolean, +"

This element configures whether startup-mode should also cover transient and persistent data, for configurations where the durability service does not take care of it. Configurations without defined merge policies best leave this enabled.

" }, +{ LEAF("MaxMessageSize"), 1, "4096 B", ABSOFF(max_msg_size), 0, uf_memsize, 0, pf_memsize, +"

This element specifies the maximum size of the UDP payload that DDSI2E will generate. DDSI2E will try to maintain this limit within the bounds of the DDSI specification, which means that in some cases (especially for very low values of MaxMessageSize) larger payloads may sporadically be observed (currently up to 1192 B).

\n\ +

On some networks it may be necessary to set this item to keep the packetsize below the MTU to prevent IP fragmentation. In those cases, it is generally advisable to also consider reducing Internal/FragmentSize.

" }, +{ LEAF("FragmentSize"), 1, "1280 B", ABSOFF(fragment_size), 0, uf_memsize, 0, pf_memsize, +"

This element specifies the size of DDSI sample fragments generated by DDSI2E. Samples larger than FragmentSize are fragmented into fragments of FragmentSize bytes each, except the last one, which may be smaller. The DDSI spec mandates a minimum fragment size of 1025 bytes, but DDSI2E will do whatever size is requested, accepting fragments of which the size is at least the minimum of 1025 and FragmentSize.

" }, +END_MARKER +}; + +#ifdef DDSI_INCLUDE_ENCRYPTION +static const struct cfgelem securityprofile_cfgattrs[] = { + { ATTR("Name"), 1, NULL, RELOFF(config_securityprofile_listelem, name), 0, uf_string, ff_free, pf_string, + "

This attribute specifies the name of this DDSI2E security profile. Two security profiles cannot have the same name.

" }, + { ATTR("Cipher"), 1, "null", RELOFF(config_securityprofile_listelem, cipher), 0, uf_cipher, 0, pf_cipher, + "

This attribute specifies the cipher to be used for encrypting traffic over network partitions secured by this security profile. The possible ciphers are:

\n\ +
  • aes128: AES with a 128-bit key;
  • \n\ +
  • aes192: AES with a 192-bit key;
  • \n\ +
  • aes256: AES with a 256-bit key;
  • \n\ +
  • blowfish: the Blowfish cipher with a 128 bit key;
  • \n\ +
  • null: no encryption;
\n\ +

SHA1 is used on conjunction with all ciphers except \"null\" to ensure data integrity.

" }, +{ ATTR("CipherKey"), 1, "", RELOFF(config_securityprofile_listelem, key), 0, uf_string, ff_free, pf_key, +"

The CipherKey attribute is used to define the secret key required by the cipher selected using the Cipher attribute. The value can be a URI referencing an external file containing the secret key, or the secret key can be defined in-place as a string value.

\n\ +

The key must be specified as a hexadecimal string with each character representing 4 bits of the key. E.g., 1ABC represents the 16-bit key 0001 1010 1011 1100. The key should not follow a well-known pattern and must exactly match the key length of the selected cipher.

\n\ +

A malformed key will cause the security profile to be marked as invalid, and disable all network partitions secured by the (invalid) security profile to prevent information leaks.

\n\ +

As all DDS applications require read access to the XML configuration file, for security reasons it is recommended to store the secret key in an external file in the file system, referenced by its URI. The file should be protected against read and write access from other users on the host.

" }, +END_MARKER +}; + +static const struct cfgelem security_cfgelems[] = { + { LEAF_W_ATTRS("SecurityProfile", securityprofile_cfgattrs), 0, 0, ABSOFF(securityProfiles), if_security_profile, 0, 0, 0, + "

This element defines a DDSI2E security profile.

" }, + END_MARKER +}; +#endif /* DDSI_INCLUDE_ENCRYPTION */ + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS +static const struct cfgelem networkpartition_cfgattrs[] = { + { ATTR("Name"), 1, NULL, RELOFF(config_networkpartition_listelem, name), 0, uf_string, ff_free, pf_string, + "

This attribute specifies the name of this DDSI2E network partition. Two network partitions cannot have the same name.

" }, + { ATTR("Address"), 1, NULL, RELOFF(config_networkpartition_listelem, address_string), 0, uf_string, ff_free, pf_string, + "

This attribute specifies the multicast addresses associated with the network partition as a comma-separated list. Readers matching this network partition (cf. Partitioning/PartitionMappings) will listen for multicasts on all of these addresses and advertise them in the discovery protocol. The writers will select the most suitable address from the addresses advertised by the readers.

" }, + { ATTR("Connected"), 1, "true", RELOFF(config_networkpartition_listelem, connected), 0, uf_boolean, 0, pf_boolean, + "

This attribute is a placeholder.

" }, +#ifdef DDSI_INCLUDE_ENCRYPTION + { ATTR("SecurityProfile"), 1, "null", RELOFF(config_networkpartition_listelem, profileName), 0, uf_string, ff_free, pf_string, + "

This attribute selects the DDSI2E security profile for encrypting the traffic mapped to this DDSI2E network partition. The default \"null\" means the network partition is unsecured; any other name refers to a security profile defined using the Security/SecurityProfile elements.

" }, +#endif /* DDSI_INCLUDE_ENCRYPTION */ + END_MARKER +}; + +static const struct cfgelem networkpartitions_cfgelems[] = { + { LEAF_W_ATTRS("NetworkPartition", networkpartition_cfgattrs), 0, 0, ABSOFF(networkPartitions), if_network_partition, 0, 0, 0, + "

This element defines a DDSI2E network partition.

" }, + END_MARKER +}; + +static const struct cfgelem ignoredpartitions_cfgattrs[] = { + { ATTR("DCPSPartitionTopic"), 1, NULL, RELOFF(config_ignoredpartition_listelem, DCPSPartitionTopic), 0, uf_string, ff_free, pf_string, + "

This attribute specifies a partition and a topic expression, separated by a single '.', that are used to determine if a given partition and topic will be ignored or not. The expressions may use the usual wildcards '*' and '?'. DDSI2E will consider an wildcard DCPS partition to match an expression iff there exists a string that satisfies both expressions.

" }, + END_MARKER +}; + +static const struct cfgelem ignoredpartitions_cfgelems[] = { + { LEAF_W_ATTRS("IgnoredPartition", ignoredpartitions_cfgattrs), 0, 0, ABSOFF(ignoredPartitions), if_ignored_partition, 0, 0, 0, + "

This element can be used to prevent certain combinations of DCPS partition and topic from being transmitted over the network. DDSI2E will complete ignore readers and writers for which all DCPS partitions as well as their topic is ignored, not even creating DDSI readers and writers to mirror the DCPS ones.

" }, + END_MARKER +}; + +static const struct cfgelem partitionmappings_cfgattrs[] = { + { ATTR("NetworkPartition"), 1, NULL, RELOFF(config_partitionmapping_listelem, networkPartition), 0, uf_string, ff_free, pf_string, + "

This attribute specifies which DDSI2E network partition is to be used for DCPS partition/topic combinations matching the DCPSPartitionTopic attribute within this PartitionMapping element.

" }, + { ATTR("DCPSPartitionTopic"), 1, NULL, RELOFF(config_partitionmapping_listelem, DCPSPartitionTopic), 0, uf_string, ff_free, pf_string, + "

This attribute specifies a partition and a topic expression, separated by a single '.', that are used to determine if a given partition and topic maps to the DDSI2E network partition named by the NetworkPartition attribute in this PartitionMapping element. The expressions may use the usual wildcards '*' and '?'. DDSI2E will consider a wildcard DCPS partition to match an expression if there exists a string that satisfies both expressions.

" }, + END_MARKER +}; + +static const struct cfgelem partitionmappings_cfgelems[] = { + { LEAF_W_ATTRS("PartitionMapping", partitionmappings_cfgattrs), 0, 0, ABSOFF(partitionMappings), if_partition_mapping, 0, 0, 0, + "

This element defines a mapping from a DCPS partition/topic combination to a DDSI2E network partition. This allows partitioning data flows by using special multicast addresses for part of the data and possibly also encrypting the data flow.

" }, + END_MARKER +}; + +static const struct cfgelem partitioning_cfgelems[] = { + { GROUP("NetworkPartitions", networkpartitions_cfgelems), + "

The NetworkPartitions element specifies the DDSI2E network partitions.

" }, + { GROUP("IgnoredPartitions", ignoredpartitions_cfgelems), + "

The IgnoredPartitions element specifies DCPS partition/topic combinations that are not distributed over the network.

" }, + { GROUP("PartitionMappings", partitionmappings_cfgelems), + "

The PartitionMappings element specifies the mapping from DCPS partition/topic combinations to DDSI2E network partitions.

" }, + END_MARKER +}; +#endif /* DDSI_INCLUDE_NETWORK_PARTITIONS */ + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS +static const struct cfgelem channel_cfgelems[] = { +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING + { LEAF("DataBandwidthLimit"), 1, "inf", RELOFF(config_channel_listelem, data_bandwidth_limit), 0, uf_bandwidth, 0, pf_bandwidth, + "

This element specifies the maximum transmit rate of new samples and directly related data, for this channel. Bandwidth limiting uses a leaky bucket scheme. The default value \"inf\" means DDSI2E imposes no limitation, the underlying operating system and hardware will likely limit the maimum transmit rate.

" }, + { LEAF("AuxiliaryBandwidthLimit"), 1, "inf", RELOFF(config_channel_listelem, auxiliary_bandwidth_limit), 0, uf_bandwidth, 0, pf_bandwidth, + "

This element specifies the maximum transmit rate of auxiliary traffic on this channel (e.g. retransmits, heartbeats, etc). Bandwidth limiting uses a leaky bucket scheme. The default value \"inf\" means DDSI2E imposes no limitation, the underlying operating system and hardware will likely limit the maimum transmit rate.

" }, +#endif + { LEAF("DiffServField"), 1, "0", RELOFF(config_channel_listelem, diffserv_field), 0, uf_natint, 0, pf_int, + "

This element describes the DiffServ setting the channel will apply to the networking messages. This parameter determines the value of the diffserv field of the IP version 4 packets sent on this channel which allows QoS setting to be applied to the network traffic send on this channel.
\n\ +Windows platform support for setting the diffserv field is dependent on the OS version.
\n\ +For Windows versions XP SP2 and 2003 to use the diffserv field the following parameter should be added to the register:

\n\ +HKEY_LOCAL_MACHINE\\SYSTEM\\CurrentControlSet\\Services\\TcpIp\\Parameters\\DisableUserTOSSetting

\n\ +The type of this parameter is a DWORD and its value should be set to 0 to allow setting of the diffserv field.

\n\ +For Windows version 7 or higher a new API (qWAVE) has been introduced. For these platforms the specified diffserv value is mapped to one of the support traffic types.\n\ +The mapping is as follows: 1-8 background traffic; 9-40 excellent traffic; 41-55 audio/video traffic; 56 voice traffic; 57-63 control traffic.\n\ +When an application is run without Administrative priveleges then only the diffserv value of 0, 8, 40 or 56 is allowed.

" }, +END_MARKER +}; + +static const struct cfgelem channel_cfgattrs[] = { + { ATTR("Name"), 1, NULL, RELOFF(config_channel_listelem, name), 0, uf_string, ff_free, pf_string, + "

This attribute specifies name of this channel. The name should uniquely identify the channel.

" }, + { ATTR("TransportPriority"), 1, "0", RELOFF(config_channel_listelem, priority), 0, uf_natint, 0, pf_int, + "

This attribute sets the transport priority threshold for the channel. Each DCPS data writer has a \"transport_priority\" QoS and this QoS is used to select a channel for use by this writer. The selected channel is the one with the largest threshold not greater than the writer's transport priority, and if no such channel exists, the channel with the lowest threshold.

" }, + END_MARKER +}; + +static const struct cfgelem channels_cfgelems[] = { + { MGROUP("Channel", channel_cfgelems, channel_cfgattrs), 42, 0, ABSOFF(channels), if_channel, 0, 0, 0, + "

This element defines a channel.

" }, + END_MARKER +}; +#endif /* DDSI_INCLUDE_NETWORK_CHANNELS */ + +static const struct cfgelem thread_properties_sched_cfgelems[] = { + { LEAF("Class"), 1, "default", RELOFF(config_thread_properties_listelem, sched_class), 0, uf_sched_class, 0, pf_sched_class, + "

This element specifies the thread scheduling class (realtime, timeshare or default). The user may need special privileges from the underlying operating system to be able to assign some of the privileged scheduling classes.

" }, + { LEAF("Priority"), 1, "default", RELOFF(config_thread_properties_listelem, sched_priority), 0, uf_maybe_int32, 0, pf_maybe_int32, + "

This element specifies the thread priority (decimal integer or default). Only priorities that are supported by the underlying operating system can be assigned to this element. The user may need special privileges from the underlying operating system to be able to assign some of the privileged priorities.

" }, + END_MARKER +}; + +static const struct cfgelem thread_properties_cfgattrs[] = { + { ATTR("Name"), 1, NULL, RELOFF(config_thread_properties_listelem, name), 0, uf_string, ff_free, pf_string, + "

The Name of the thread for which properties are being set. The following threads exist:

\n\ +
  • gc: garbage collector thread involved in deleting entities;
  • \n\ +
  • recv: receive thread, taking data from the network and running the protocol state machine;
  • \n\ +
  • dq.builtins: delivery thread for DDSI-builtin data, primarily for discovery;
  • \n\ +
  • lease: DDSI liveliness monitoring;
  • \n\ +
  • tev: general timed-event handling, retransmits and discovery;
  • \n\ +
  • xmit.CHAN: transmit thread for channel CHAN;
  • \n\ +
  • dq.CHAN: delivery thread for channel CHAN;
  • \n\ +
  • tev.CHAN: timed-even thread for channel CHAN.
" }, +END_MARKER +}; + +static const struct cfgelem thread_properties_cfgelems[] = { + { GROUP("Scheduling", thread_properties_sched_cfgelems), + "

This element configures the scheduling properties of the thread.

" }, + { LEAF("StackSize"), 1, "default", RELOFF(config_thread_properties_listelem, stack_size), 0, uf_maybe_memsize, 0, pf_maybe_memsize, + "

This element configures the stack size for this thread. The default value default leaves the stack size at the operating system default.

" }, + END_MARKER +}; + +static const struct cfgelem threads_cfgelems[] = { + { MGROUP("Thread", thread_properties_cfgelems, thread_properties_cfgattrs), 1000, 0, ABSOFF(thread_properties), if_thread_properties, 0, 0, 0, + "

This element is used to set thread properties.

" }, + END_MARKER +}; + +static const struct cfgelem compatibility_cfgelems[] = { + { LEAF("StandardsConformance"), 1, "lax", ABSOFF(standards_conformance), 0, uf_standards_conformance, 0, pf_standards_conformance, + "

This element sets the level of standards conformance of this instance of the DDSI2E Service. Stricter conformance typically means less interoperability with other implementations. Currently three modes are defined:

\n\ +
  • pedantic: very strictly conform to the specification, ultimately for compliancy testing, but currently of little value because it adheres even to what will most likely turn out to be editing errors in the DDSI standard. Arguably, as long as no errata have been published it is the current text that is in effect, and that is what pedantic currently does.
  • \n\ +
  • strict: a slightly less strict view of the standard than does pedantic: it follows the established behaviour where the standard is obviously in error.
  • \n\ +
  • lax: attempt to provide the smoothest possible interoperability, anticipating future revisions of elements in the standard in areas that other implementations do not adhere to, even though there is no good reason not to.
\n\ +

The default setting is \"lax\".

" }, +{ LEAF("ExplicitlyPublishQosSetToDefault"), 1, "false", ABSOFF(explicitly_publish_qos_set_to_default), 0, uf_boolean, 0, pf_boolean, +"

This element specifies whether QoS settings set to default values are explicitly published in the discovery protocol. Implementations are to use the default value for QoS settings not published, which allows a significant reduction of the amount of data that needs to be exchanged for the discovery protocol, but this requires all implementations to adhere to the default values specified by the specifications.

\n\ +

When interoperability is required with an implementation that does not follow the specifications in this regard, setting this option to true will help.

" }, +{ LEAF("ManySocketsMode"), 1, "false", ABSOFF(many_sockets_mode), 0, uf_boolean, 0, pf_boolean, +"

This option specifies whether a network socket will be created for each domain participant on a host. The specification seems to assume that each participant has a unique address, and setting this option will ensure this to be the case. This is not the defeault.

\n\ +

Disabling it slightly improves performance and reduces network traffic somewhat. It also causes the set of port numbers needed by DDSI2E to become predictable, which may be useful for firewall and NAT configuration.

" }, +{ LEAF("ArrivalOfDataAssertsPpAndEpLiveliness"), 1, "true", ABSOFF(arrival_of_data_asserts_pp_and_ep_liveliness), 0, uf_boolean, 0, pf_boolean, +"

When set to true, arrival of a message from a peer asserts liveliness of that peer. When set to false, only SPDP and explicit lease renewals have this effect.

" }, +{ LEAF("AckNackNumbitsEmptySet"), 1, "0", ABSOFF(acknack_numbits_emptyset), 0, uf_natint, 0, pf_int, +"

This element governs the representation of an acknowledgement message that does not also negatively-acknowledge some samples. If set to 0, the generated acknowledgements have an invalid form and will be reject by the strict and pedantic conformance modes, but several other implementation require this setting for smooth interoperation.

\n\ +

If set to 1, all acknowledgements sent by DDSI2E adhere the form of acknowledgement messages allowed by the standard, but this causes problems when interoperating with these other implementations. The strict and pedantic standards conformance modes always overrule an AckNackNumbitsEmptySet=0 to prevent the transmitting of invalid messages.

" }, +{ LEAF("RespondToRtiInitZeroAckWithInvalidHeartbeat"), 1, "false", ABSOFF(respond_to_rti_init_zero_ack_with_invalid_heartbeat), 0, uf_boolean, 0, pf_boolean, +"

This element allows a closer mimicking of the behaviour of some other DDSI implementations, albeit at the cost of generating even more invalid messages. Setting it to true ensures a Heartbeat can be sent at any time when a remote node requests one, setting it to false delays it until a valid one can be sent.

\n\ +

The latter is fully compliant with the specification, and no adverse effects have been observed. It is the default.

" }, +{ LEAF("AssumeRtiHasPmdEndpoints"), 1, "false", ABSOFF(assume_rti_has_pmd_endpoints), 0, uf_boolean, 0, pf_boolean, +"

This option assumes ParticipantMessageData endpoints required by the liveliness protocol are present in RTI participants even when not properly advertised by the participant discovery protocol.

" }, +END_MARKER +}; + +static const struct cfgelem unsupp_test_cfgelems[] = { + { LEAF("XmitLossiness"), 1, "0", ABSOFF(xmit_lossiness), 0, uf_int, 0, pf_int, + "

This element controls the fraction of outgoing packets to drop, specified as samples per thousand.

" }, + END_MARKER +}; + +static const struct cfgelem unsupp_watermarks_cfgelems[] = { + { LEAF("WhcLow"), 1, "1 kB", ABSOFF(whc_lowwater_mark), 0, uf_memsize, 0, pf_memsize, + "

This element sets the low-water mark for the DDSI2E WHCs, expressed in bytes. A suspended writer resumes transmitting when its DDSI2E WHC shrinks to this size.

" }, + { LEAF("WhcHigh"), 1, "100 kB", ABSOFF(whc_highwater_mark), 0, uf_memsize, 0, pf_memsize, + "

This element sets the maximum allowed high-water mark for the DDSI2E WHCs, expressed in bytes. A writer is suspended when the WHC reaches this size.

" }, + { LEAF("WhcHighInit"), 1, "30 kB", ABSOFF(whc_init_highwater_mark), 0, uf_maybe_memsize, 0, pf_maybe_memsize, + "

This element sets the initial level of the high-water mark for the DDSI2E WHCs, expressed in bytes.

" }, + { LEAF("WhcAdaptive|WhcAdaptative"), 1, "true", ABSOFF(whc_adaptive), 0, uf_boolean, 0, pf_boolean, + "

This element controls whether DDSI2E will adapt the high-water mark to current traffic conditions, based on retransmit requests and transmit pressure.

" }, + END_MARKER +}; + +static const struct cfgelem control_topic_cfgattrs[] = { + { ATTR("Enable"), 1, "false", ABSOFF(enable_control_topic), 0, uf_boolean, 0, pf_boolean }, + { ATTR("InitialReset"), 1, "inf", ABSOFF(initial_deaf_mute_reset), 0, uf_duration_inf, 0, pf_duration }, + END_MARKER +}; + +static const struct cfgelem control_topic_cfgelems[] = { + { LEAF("Deaf"), 1, "false", ABSOFF(initial_deaf), 0, uf_boolean, 0, pf_boolean }, + { LEAF("Mute"), 1, "false", ABSOFF(initial_mute), 0, uf_boolean, 0, pf_boolean }, + END_MARKER +}; + +static const struct cfgelem rediscovery_blacklist_duration_attrs[] = { + { ATTR("enforce"), 1, "false", ABSOFF(prune_deleted_ppant.enforce_delay), 0, uf_boolean, 0, pf_boolean, + "

This attribute controls whether the configured time during which recently deleted participants will not be rediscovered (i.e., \"black listed\") is enforced and following complete removal of the participant in DDSI2E, or whether it can be rediscovered earlier provided all traces of that participant have been removed already.

" }, + END_MARKER +}; + +static const struct cfgelem heartbeat_interval_attrs[] = { + { ATTR("min"), 1, "5 ms", ABSOFF(const_hb_intv_min), 0, uf_duration_inf, 0, pf_duration }, + { ATTR("minsched"), 1, "20 ms", ABSOFF(const_hb_intv_sched_min), 0, uf_duration_inf, 0, pf_duration }, + { ATTR("max"), 1, "8 s", ABSOFF(const_hb_intv_sched_max), 0, uf_duration_inf, 0, pf_duration }, + END_MARKER +}; + +static const struct cfgelem unsupp_cfgelems[] = { + { MOVED("MaxMessageSize", "General/MaxMessageSize") }, + { MOVED("FragmentSize", "General/FragmentSize") }, + { LEAF("DeliveryQueueMaxSamples"), 1, "256", ABSOFF(delivery_queue_maxsamples), 0, uf_uint, 0, pf_uint, + "

This element controls the Maximum size of a delivery queue, expressed in samples. Once a delivery queue is full, incoming samples destined for that queue are dropped until space becomes available again.

" }, + { LEAF("PrimaryReorderMaxSamples"), 1, "64", ABSOFF(primary_reorder_maxsamples), 0, uf_uint, 0, pf_uint, + "

This element sets the maximum size in samples of a primary re-order administration. Each proxy writer has one primary re-order administration to buffer the packet flow in case some packets arrive out of order. Old samples are forwarded to secondary re-order administrations associated with readers in need of historical data.

" }, + { LEAF("SecondaryReorderMaxSamples"), 1, "16", ABSOFF(secondary_reorder_maxsamples), 0, uf_uint, 0, pf_uint, + "

This element sets the maximum size in samples of a secondary re-order administration. The secondary re-order administration is per reader in need of historical data.

" }, + { LEAF("DefragUnreliableMaxSamples"), 1, "4", ABSOFF(defrag_unreliable_maxsamples), 0, uf_uint, 0, pf_uint, + "

This element sets the maximum number of samples that can be defragmented simultaneously for a best-effort writers.

" }, + { LEAF("DefragReliableMaxSamples"), 1, "16", ABSOFF(defrag_reliable_maxsamples), 0, uf_uint, 0, pf_uint, + "

This element sets the maximum number of samples that can be defragmented simultaneously for a reliable writer. This has to be large enough to handle retransmissions of historical data in addition to new samples.

" }, + { LEAF("BuiltinEndpointSet"), 1, "writers", ABSOFF(besmode), 0, uf_besmode, 0, pf_besmode, + "

This element controls which participants will have which built-in endpoints for the discovery and liveliness protocols. Valid values are:

\n\ +
  • full: all participants have all endpoints;
  • \n\ +
  • writers: all participants have the writers, but just one has the readers;
  • \n\ +
  • minimal: only one participant has built-in endpoints.
\n\ +

The default is writers, as this is thought to be compliant and reasonably efficient. Minimal may or may not be compliant but is most efficient, and full is inefficient but certain to be compliant. See also Internal/ConservativeBuiltinReaderStartup.

" }, +{ LEAF("AggressiveKeepLastWhc|AggressiveKeepLast1Whc"), 1, "true", ABSOFF(aggressive_keep_last_whc), 0, uf_boolean, 0, pf_boolean, +"

This element controls whether to drop a reliable sample from a DDSI2E WHC before all readers have acknowledged it as soon as a later sample becomes available. It only affects DCPS data writers with a history QoS setting of KEEP_LAST with depth 1. The default setting, false, mimics the behaviour of the OpenSplice RT networking and is necessary to make the behaviour of wait_for_acknowledgements() consistent across the networking services.

" }, +{ LEAF("ConservativeBuiltinReaderStartup"), 1, "false", ABSOFF(conservative_builtin_reader_startup), 0, uf_boolean, 0, pf_boolean, +"

This element forces all DDSI2E built-in discovery-related readers to request all historical data, instead of just one for each \"topic\". There is no indication that any of the current DDSI implementations requires changing of this setting, but it is conceivable that an implementation might track which participants have been informed of the existence of endpoints and which have not been, refusing communication with those that have \"can't\" know.

\n\ +

Should it be necessary to hide DDSI2E's shared discovery behaviour, set this to true and Internal/BuiltinEndpointSet to full.

" }, +{ LEAF("MeasureHbToAckLatency"), 1, "false", ABSOFF(meas_hb_to_ack_latency), 0, uf_boolean, 0, pf_boolean, +"

This element enables heartbeat-to-ack latency among DDSI2E services by prepending timestamps to Heartbeat and AckNack messages and calculating round trip times. This is non-standard behaviour. The measured latencies are quite noisy and are currently not used anywhere.

" }, +{ LEAF("SuppressSPDPMulticast"), 1, "false", ABSOFF(suppress_spdp_multicast), 0, uf_boolean, 0, pf_boolean, +"

The element controls whether the mandatory multicasting of the participant discovery packets occurs. Completely disabling multicasting requires this element be set to true, and generally requires explicitly listing peers to ping for unicast discovery.

\n\ +

See also General/AllowMulticast.

" }, +{ LEAF("UnicastResponseToSPDPMessages"), 1, "true", ABSOFF(unicast_response_to_spdp_messages), 0, uf_boolean, 0, pf_boolean, +"

This element controls whether the response to a newly discovered participant is sent as a unicasted SPDP packet, instead of rescheduling the periodic multicasted one. There is no known benefit to setting this to false.

" }, +{ LEAF("SynchronousDeliveryPriorityThreshold"), 1, "0", ABSOFF(synchronous_delivery_priority_threshold), 0, uf_int, 0, pf_int, +"

This element controls whether samples sent by a writer with QoS settings latency_budget <= SynchronousDeliveryLatencyBound and transport_priority greater than or equal to this element's value will be delivered synchronously from the \"recv\" thread, all others will be delivered asynchronously through delivery queues. This reduces latency at the expense of aggregate bandwidth.

" }, +{ LEAF("SynchronousDeliveryLatencyBound"), 1, "inf", ABSOFF(synchronous_delivery_latency_bound), 0, uf_duration_inf, 0, pf_duration, +"

This element controls whether samples sent by a writer with QoS settings transport_priority >= SynchronousDeliveryPriorityThreshold and a latency_budget at most this element's value will be delivered synchronously from the \"recv\" thread, all others will be delivered asynchronously through delivery queues. This reduces latency at the expense of aggregate bandwidth.

" }, +{ LEAF("MaxParticipants"), 1, "0", ABSOFF(max_participants), 0, uf_natint, 0, pf_int, +"

This elements configures the maximum number of DCPS domain participants this DDSI2E instance is willing to service. 0 is unlimited.

" }, +{ LEAF("AccelerateRexmitBlockSize"), 1, "0", ABSOFF(accelerate_rexmit_block_size), 0, uf_uint, 0, pf_uint, +"

Proxy readers that are assumed to sill be retrieving historical data get this many samples retransmitted when they NACK something, even if some of these samples have sequence numbers outside the set covered by the NACK.

" }, +{ LEAF("RetransmitMerging"), 1, "adaptive", ABSOFF(retransmit_merging), 0, uf_retransmit_merging, 0, pf_retransmit_merging, +"

This elements controls the addressing and timing of retransmits. Possible values are:

\n\ +
  • never: retransmit only to the NACK-ing reader;
  • \n \ +
  • adaptive: attempt to combine retransmits needed for reliability, but send historical (transient-local) data to the requesting reader only;
  • \n\ +
  • always: do not distinguish between different causes, always try to merge.
\n\ +

The default is adaptive. See also Internal/RetransmitMergingPeriod.

" }, +{ LEAF("RetransmitMergingPeriod"), 1, "5 ms", ABSOFF(retransmit_merging_period), 0, uf_duration_us_1s, 0, pf_duration, +"

This setting determines the size of the time window in which a NACK of some sample is ignored because a retransmit of that sample has been multicasted too recently. This setting has no effect on unicasted retransmits.

\n\ +

See also Internal/RetransmitMerging.

" }, +{ LEAF_W_ATTRS("HeartbeatInterval", heartbeat_interval_attrs), 1, "100 ms", ABSOFF(const_hb_intv_sched), 0, uf_duration_inf, 0, pf_duration }, +{ LEAF("MaxQueuedRexmitBytes"), 1, "50 kB", ABSOFF(max_queued_rexmit_bytes), 0, uf_memsize, 0, pf_memsize, +"

This setting limits the maximum number of bytes queued for retransmission. The default value of 0 is unlimited unless an AuxiliaryBandwidthLimit has been set, in which case it becomes NackDelay * AuxiliaryBandwidthLimit. It must be large enough to contain the largest sample that may need to be retransmitted.

" }, +{ LEAF("MaxQueuedRexmitMessages"), 1, "200", ABSOFF(max_queued_rexmit_msgs), 0, uf_uint, 0, pf_uint, +"

This settings limits the maximum number of samples queued for retransmission.

" }, +{ LEAF("LeaseDuration"), 1, "10 s", ABSOFF(lease_duration), 0, uf_duration_ms_1hr, 0, pf_duration, +"

This setting controls the default participant lease duration.

" }, +{ LEAF("WriterLingerDuration"), 1, "1 s", ABSOFF(writer_linger_duration), 0, uf_duration_ms_1hr, 0, pf_duration, +"

This setting controls the maximum duration for which actual deletion of a reliable writer with unacknowledged data in its history will be postponed to provide proper reliable transmission.

" }, +{ LEAF("MinimumSocketReceiveBufferSize"), 1, "default", ABSOFF(socket_min_rcvbuf_size), 0, uf_maybe_memsize, 0, pf_maybe_memsize, +"

This setting controls the minimum size of socket receive buffers. The operating system provides some size receive buffer upon creation of the socket, this option can be used to increase the size of the buffer beyond that initially provided by the operating system. If the buffer size cannot be increased to the specified size, an error is reported.

\n\ +

The default setting is the word \"default\", which means DDSI2E will attempt to increase the buffer size to 1MB, but will silently accept a smaller buffer should that attempt fail.

" }, +{ LEAF("MinimumSocketSendBufferSize"), 1, "64 KiB", ABSOFF(socket_min_sndbuf_size), 0, uf_memsize, 0, pf_memsize, +"

This setting controls the minimum size of socket send buffers. This setting can only increase the size of the send buffer, if the operating system by default creates a larger buffer, it is left unchanged.

" }, +{ LEAF("NackDelay"), 1, "10 ms", ABSOFF(nack_delay), 0, uf_duration_ms_1hr, 0, pf_duration, +"

This setting controls the delay between receipt of a HEARTBEAT indicating missing samples and a NACK (ignored when the HEARTBEAT requires an answer). However, no NACK is sent if a NACK had been scheduled already for a response earlier than the delay requests: then that NACK will incorporate the latest information.

" }, +{ LEAF("AutoReschedNackDelay"), 1, "1 s", ABSOFF(auto_resched_nack_delay), 0, uf_duration_inf, 0, pf_duration, +"

This setting controls the interval with which a reader will continue NACK'ing missing samples in the absence of a response from the writer, as a protection mechanism against writers incorrectly stopping the sending of HEARTBEAT messages.

" }, +{ LEAF("PreEmptiveAckDelay"), 1, "10 ms", ABSOFF(preemptive_ack_delay), 0, uf_duration_ms_1hr, 0, pf_duration, +"

This setting controls the delay between the discovering a remote writer and sending a pre-emptive AckNack to discover the range of data available.

" }, +{ LEAF("ScheduleTimeRounding"), 1, "0 ms", ABSOFF(schedule_time_rounding), 0, uf_duration_ms_1hr, 0, pf_duration, +"

This setting allows the timing of scheduled events to be rounded up so that more events can be handled in a single cycle of the event queue. The default is 0 and causes no rounding at all, i.e. are scheduled exactly, whereas a value of 10ms would mean that events are rounded up to the nearest 10 milliseconds.

" }, +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING +{ LEAF("AuxiliaryBandwidthLimit"), 1, "inf", ABSOFF(auxiliary_bandwidth_limit), 0, uf_bandwidth, 0, pf_bandwidth, +"

This element specifies the maximum transmit rate of auxiliary traffic not bound to a specific channel, such as discovery traffic, as well as auxiliary traffic related to a certain channel if that channel has elected to share this global AuxiliaryBandwidthLimit. Bandwidth limiting uses a leaky bucket scheme. The default value \"inf\" means DDSI2E imposes no limitation, the underlying operating system and hardware will likely limit the maimum transmit rate.

" }, +#endif +{ LEAF("DDSI2DirectMaxThreads"), 1, "1", ABSOFF(ddsi2direct_max_threads), 0, uf_uint, 0, pf_uint, +"

This element sets the maximum number of extra threads for an experimental, undocumented and unsupported direct mode.

" }, +{ LEAF("SquashParticipants"), 1, "false", ABSOFF(squash_participants), 0, uf_boolean, 0, pf_boolean, +"

This element controls whether DDSI2E advertises all the domain participants it serves in DDSI (when set to false), or rather only one domain participant (the one corresponding to the DDSI2E process; when set to true). In the latter case DDSI2E becomes the virtual owner of all readers and writers of all domain participants, dramatically reducing discovery traffic (a similar effect can be obtained by setting Internal/BuiltinEndpointSet to \"minimal\" but with less loss of information).

" }, +{ LEAF("LegacyFragmentation"), 1, "false", ABSOFF(buggy_datafrag_flags_mode), 0, uf_boolean, 0, pf_boolean, +"

This option enables a backwards-compatible, non-compliant setting and interpretation of the control flags in fragmented data messages. To be enabled only when requiring interoperability between compliant and non-compliant versions of DDSI2E for large messages.

" }, +{ LEAF("SPDPResponseMaxDelay"), 1, "0 ms", ABSOFF(spdp_response_delay_max), 0, uf_duration_ms_1s, 0, pf_duration, +"

Maximum pseudo-random delay in milliseconds between discovering a remote participant and responding to it.

" }, +{ LEAF("LateAckMode"), 1, "false", ABSOFF(late_ack_mode), 0, uf_boolean, 0, pf_boolean, +"

Ack a sample only when it has been delivered, instead of when committed to delivering it.

" }, +{ LEAF("ForwardAllMessages"), 1, "false", ABSOFF(forward_all_messages), 0, uf_boolean, 0, pf_boolean, +"

Forward all messages from a writer, rather than trying to forward each sample only once. The default of trying to forward each sample only once filters out duplicates for writers in multiple partitions under nearly all circumstances, but may still publish the odd duplicate. Note: the current implementation also can lose in contrived test cases, that publish more than 2**32 samples using a single data writer in conjunction with carefully controlled management of the writer history via cooperating local readers.

" }, +{ LEAF("RetryOnRejectBestEffort"), 1, "false", ABSOFF(retry_on_reject_besteffort), 0, uf_boolean, 0, pf_boolean, +"

Whether or not to locally retry pushing a received best-effort sample into the reader caches when resource limits are reached.

" }, +{ LEAF("GenerateKeyhash"), 1, "true", ABSOFF(generate_keyhash), 0, uf_boolean, 0, pf_boolean, +"

When true, include keyhashes in outgoing data for topics with keys.

" }, +{ LEAF("MaxSampleSize"), 1, "2147483647 B", ABSOFF(max_sample_size), 0, uf_memsize, 0, pf_memsize, +"

This setting controls the maximum (CDR) serialised size of samples that DDSI2E will forward in either direction. Samples larger than this are discarded with a warning.

" }, +{ LEAF("WriteBatch"), 1, "false", ABSOFF(whc_batch), 0, uf_boolean, 0, pf_boolean, +"

This element enables the batching of write operations. By default each write operation writes through the write cache and out onto the transport. Enabling write batching causes multiple small write operations to be aggregated within the write cache into a single larger write. This gives greater throughput at the expense of latency. Currently there is no mechanism for the write cache to automatically flush itself, so that if write batching is enabled, the application may havee to use the dds_write_flush function to ensure thta all samples are written.

" }, +{ LEAF("LogStackTraces"), 1, "true", ABSOFF(noprogress_log_stacktraces), 0, uf_boolean, 0, pf_boolean, +"

This element controls whether or not to write stack traces to the DDSI2 trace when a thread fails to make progress (on select platforms only).

" }, +{ LEAF("MonitorPort"), 1, "-1", ABSOFF(monitor_port), 0, uf_int, 0, pf_int, +"

This element allows configuring a service that dumps a text description of part the internal state to TCP clients. By default (-1), this is disabled; specifying 0 means a kernel-allocated port is used; a positive number is used as the TCP port number.

" }, +{ LEAF("AssumeMulticastCapable"), 1, "", ABSOFF(assumeMulticastCapable), 0, uf_string, ff_free, pf_string, +"

This element controls which network interfaces are assumed to be capable of multicasting even when the interface flags returned by the operating system state it is not (this provides a workaround for some platforms). It is a comma-separated lists of patterns (with ? and * wildcards) against which the interface names are matched.

" }, +{ LEAF("PrioritizeRetransmit"), 1, "true", ABSOFF(prioritize_retransmit), 0, uf_boolean, 0, pf_boolean, +"

This element controls whether retransmits are prioritized over new data, speeding up recovery.

" }, +{ LEAF("UseMulticastIfMreqn"), 1, "0", ABSOFF(use_multicast_if_mreqn), 0, uf_int, 0, pf_int, +"

Do not use.

" }, +{ LEAF("SendAsync"), 1, "false", ABSOFF(xpack_send_async), 0, uf_boolean, 0, pf_boolean, +"

This element controls whether the actual sending of packets occurs on the same thread that prepares them, or is done asynchronously by another thread.

" }, +{ LEAF_W_ATTRS("RediscoveryBlacklistDuration", rediscovery_blacklist_duration_attrs), 1, "10s", ABSOFF(prune_deleted_ppant.delay), 0, uf_duration_inf, 0, pf_duration, +"

This element controls for how long a remote participant that was previously deleted will remain on a blacklist to prevent rediscovery, giving the software on a node time to perform any cleanup actions it needs to do. To some extent this delay is required internally by DDSI2E, but in the default configuration with the 'enforce' attribute set to false, DDSI2E will reallow rediscovery as soon as it has cleared its internal administration. Setting it to too small a value may result in the entry being pruned from the blacklist before DDSI2E is ready, it is therefore recommended to set it to at least several seconds.

" }, +{ MGROUP("ControlTopic", control_topic_cfgelems, control_topic_cfgattrs), 1, 0, 0, 0, 0, 0, 0, 0, +"

The ControlTopic element allows configured whether DDSI2E provides a special control interface via a predefined topic or not.

" }, +{ GROUP("Test", unsupp_test_cfgelems), +"

Testing options.

" }, +{ GROUP("Watermarks", unsupp_watermarks_cfgelems), +"

Watermarks for flow-control.

" }, +END_MARKER +}; + +static const struct cfgelem sizing_cfgelems[] = +{ + { LEAF("ReceiveBufferSize"), 1, "1 MiB", ABSOFF(rbuf_size), 0, uf_memsize, 0, pf_memsize, + "

This element sets the size of a single receive buffer. Many receive buffers may be needed. Their size must be greater than ReceiveBufferChunkSize by a modest amount.

" }, + { LEAF("ReceiveBufferChunkSize"), 1, "128 KiB", ABSOFF(rmsg_chunk_size), 0, uf_memsize, 0, pf_memsize, + "

This element specifies the size of one allocation unit in the receive buffer. Must be greater than the maximum packet size by a modest amount (too large packets are dropped). Each allocation is shrunk immediately after processing a message, or freed straightaway.

" }, + END_MARKER +}; + +static const struct cfgelem discovery_ports_cfgelems[] = { + { LEAF("Base"), 1, "7400", ABSOFF(port_base), 0, uf_port, 0, pf_int, + "

This element specifies the base port number (refer to the DDSI 2.1 specification, section 9.6.1, constant PB).

" }, + { LEAF("DomainGain"), 1, "250", ABSOFF(port_dg), 0, uf_int, 0, pf_int, + "

This element specifies the domain gain, relating domain ids to sets of port numbers (refer to the DDSI 2.1 specification, section 9.6.1, constant DG).

" }, + { LEAF("ParticipantGain"), 1, "2", ABSOFF(port_pg), 0, uf_int, 0, pf_int, + "

This element specifies the participant gain, relating p0, articipant index to sets of port numbers (refer to the DDSI 2.1 specification, section 9.6.1, constant PG).

" }, + { LEAF("MulticastMetaOffset"), 1, "0", ABSOFF(port_d0), 0, uf_int, 0, pf_int, + "

This element specifies the port number for multicast meta traffic (refer to the DDSI 2.1 specification, section 9.6.1, constant d0).

" }, + { LEAF("UnicastMetaOffset"), 1, "10", ABSOFF(port_d1), 0, uf_int, 0, pf_int, + "

This element specifies the port number for unicast meta traffic (refer to the DDSI 2.1 specification, section 9.6.1, constant d1).

" }, + { LEAF("MulticastDataOffset"), 1, "1", ABSOFF(port_d2), 0, uf_int, 0, pf_int, + "

This element specifies the port number for multicast meta traffic (refer to the DDSI 2.1 specification, section 9.6.1, constant d2).

" }, + { LEAF("UnicastDataOffset"), 1, "11", ABSOFF(port_d3), 0, uf_int, 0, pf_int, + "

This element specifies the port number for unicast meta traffic (refer to the DDSI 2.1 specification, section 9.6.1, constant d3).

" }, + END_MARKER +}; + +static const struct cfgelem tcp_cfgelems[] = { + { LEAF("Enable"), 1, "false", ABSOFF(tcp_enable), 0, uf_boolean, 0, pf_boolean, + "

This element enables the optional TCP transport.

" }, + { LEAF("NoDelay"), 1, "true", ABSOFF(tcp_nodelay), 0, uf_boolean, 0, pf_boolean, + "

This element enables the TCP_NODELAY socket option, preventing multiple DDSI messages being sent in the same TCP request. Setting this option typically optimises latency over throughput.

" }, + { LEAF("Port"), 1, "-1", ABSOFF(tcp_port), 0, uf_dyn_port, 0, pf_int, + "

This element specifies the TCP port number on which DDSI2E accepts connections. If the port is set it is used in entity locators, published with DDSI discovery. Dynamically allocated if zero. Disabled if -1 or not configured. If disabled other DDSI services will not be able to establish connections with the service, the service can only communicate by establishing connections to other services.

" }, + { LEAF("ReadTimeout"), 1, "2 s", ABSOFF(tcp_read_timeout), 0, uf_duration_ms_1hr, 0, pf_duration, + "

This element specifies the timeout for blocking TCP read operations. If this timeout expires then the connection is closed.

" }, + { LEAF("WriteTimeout"), 1, "2 s", ABSOFF(tcp_write_timeout), 0, uf_duration_ms_1hr, 0, pf_duration, + "

This element specifies the timeout for blocking TCP write operations. If this timeout expires then the connection is closed.

" }, + END_MARKER +}; + +#ifdef DDSI_INCLUDE_SSL +static const struct cfgelem ssl_cfgelems[] = { + { LEAF("Enable"), 1, "false", ABSOFF(ssl_enable), 0, uf_boolean, 0, pf_boolean, + "

This enables SSL/TLS for TCP.

" }, + { LEAF("CertificateVerification"), 1, "true", ABSOFF(ssl_verify), 0, uf_boolean, 0, pf_boolean, + "

If disabled this allows SSL connections to occur even if an X509 certificate fails verification.

" }, + { LEAF("VerifyClient"), 1, "false", ABSOFF(ssl_verify_client), 0, uf_boolean, 0, pf_boolean, + "

This enables an SSL server checking the X509 certificate of a connecting client.

" }, + { LEAF("SelfSignedCertificates"), 1, "false", ABSOFF(ssl_self_signed), 0, uf_boolean, 0, pf_boolean, + "

This enables the use of self signed X509 certificates.

" }, + { LEAF("KeystoreFile"), 1, "keystore", ABSOFF(ssl_keystore), 0, uf_string, ff_free, pf_string, + "

The SSL/TLS key and certificate store file name. The keystore must be in PEM format.

" }, + { LEAF("KeyPassphrase"), 1, "secret", ABSOFF(ssl_key_pass), 0, uf_string, ff_free, pf_string, + "

The SSL/TLS key pass phrase for encrypted keys.

" }, + { LEAF("Ciphers"), 1, "ALL:!ADH:!LOW:!EXP:!MD5:@STRENGTH", ABSOFF(ssl_ciphers), 0, uf_string, ff_free, pf_string, + "

The set of ciphers used by SSL/TLS

" }, + { LEAF("EntropyFile"), 1, "", ABSOFF(ssl_rand_file), 0, uf_string, ff_free, pf_string, + "

The SSL/TLS random entropy file name.

" }, + END_MARKER +}; +#endif + +static const struct cfgelem tp_cfgelems[] = { + { LEAF("Enable"), 1, "false", ABSOFF(tp_enable), 0, uf_boolean, 0, pf_boolean, + "

This element enables the optional thread pool.

" }, + { LEAF("Threads"), 1, "4", ABSOFF(tp_threads), 0, uf_natint, 0, pf_int, + "

This elements configures the initial number of threads in the thread pool.

" }, + { LEAF("ThreadMax"), 1, "8", ABSOFF(tp_max_threads), 0, uf_natint, 0, pf_int, + "

This elements configures the maximum number of threads in the thread pool.

" }, + END_MARKER +}; + +static const struct cfgelem discovery_peer_cfgattrs[] = { + { ATTR("Address"), 1, NULL, RELOFF(config_peer_listelem, peer), 0, uf_ipv4, ff_free, pf_string, + "

This element specifies an IP address to which discovery packets must be sent, in addition to the default multicast address (see also General/AllowMulticast). Both a hostnames and a numerical IP address is accepted; the hostname or IP address may be suffixed with :PORT to explicitly set the port to which it must be sent. Multiple Peers may be specified.

" }, + END_MARKER +}; + +static const struct cfgelem discovery_peers_group_cfgelems[] = { + { MGROUP("Peer", NULL, discovery_peer_cfgattrs), 0, NULL, ABSOFF(peers_group), if_peer, 0, 0, 0, + "

This element statically configures an addresses for discovery.

" }, + END_MARKER +}; + +static const struct cfgelem discovery_peers_cfgelems[] = { + { MGROUP("Peer", NULL, discovery_peer_cfgattrs), 0, NULL, ABSOFF(peers), if_peer, 0, 0, 0, + "

This element statically configures an addresses for discovery.

" }, + { GROUP("Group", discovery_peers_group_cfgelems), + "

This element statically configures a fault tolerant group of addresses for discovery. Each member of the group is tried in sequence until one succeeds.

" }, + END_MARKER +}; + +static const struct cfgelem discovery_cfgelems[] = { + { LEAF("DomainId"), 1, "default", ABSOFF(discoveryDomainId), 0, uf_maybe_int32, 0, pf_maybe_int32, + "

This element allows overriding of the DDS Domain Id that is used for DDSI2E.

" }, + { LEAF("AdvertiseBuiltinTopicWriters"), 1, "true", ABSOFF(advertise_builtin_topic_writers), 0, uf_boolean, 0, pf_boolean, + "

This element controls whether or not DDSI2E advertises writers for the built-in topics from its discovery for backwards compatibility with older OpenSplice versions.

" }, + { LEAF("DSGracePeriod"), 1, "30 s", ABSOFF(ds_grace_period), 0, uf_duration_inf, 0, pf_duration, + "

This setting controls for how long endpoints discovered via a Cloud discovery service will survive after the discovery service disappeared, allowing reconnect without loss of data when the discovery service restarts (or another instance takes over).

" }, + { GROUP("Peers", discovery_peers_cfgelems), + "

This element statically configures addresses for discovery.

" }, + { LEAF("ParticipantIndex"), 1, "none", ABSOFF(participantIndex), 0, uf_participantIndex, 0, pf_participantIndex, + "

This element specifies the DDSI participant index used by this instance of the DDSI2E service for discovery purposes. Only one such participant id is used, independent of the number of actual DomainParticipants on the node. It is either:

\n\ +
  • auto: which will attempt to automatically determine an available participant index (see also Discovery/MaxAutoParticipantIndex), or
  • \n \ +
  • a non-negative integer less than 120, or
  • \n\ +
  • none:, which causes it to use arbitrary port numbers for unicast sockets which entirely removes the constraints on the participant index but makes unicast discovery impossible.
\n\ +

The default is auto. The participant index is part of the port number calculation and if predictable port numbers are needed and fixing the participant index has no adverse effects, it is recommended that the second be option be used.

" }, +{ LEAF("MaxAutoParticipantIndex"), 1, "9", ABSOFF(maxAutoParticipantIndex), 0, uf_natint, 0, pf_int, +"

This element specifies the maximum DDSI participant index selected by this instance of the DDSI2E service if the Discovery/ParticipantIndex is \"auto\".

" }, +{ LEAF("SPDPMulticastAddress"), 1, "239.255.0.1", ABSOFF(spdpMulticastAddressString), 0, uf_ipv4, ff_free, pf_string, +"

This element specifies the multicast address that is used as the destination for the participant discovery packets. In IPv4 mode the default is the (standardised) 239.255.0.1, in IPv6 mode it becomes ff02::ffff:239.255.0.1, which is a non-standardised link-local multicast address.

" }, +{ LEAF("SPDPInterval"), 1, "30 s", ABSOFF(spdp_interval), 0, uf_duration_ms_1hr, 0, pf_duration, +"

This element specifies the interval between spontaneous transmissions of participant discovery packets.

" }, +{ LEAF("DefaultMulticastAddress"), 1, "auto", ABSOFF(defaultMulticastAddressString), 0, uf_networkAddress, 0, pf_networkAddress, +"

This element specifies the default multicast address for all traffic other than participant discovery packets. It defaults to Discovery/SPDPMulticastAddress.

" }, +{ LEAF("EnableTopicDiscovery"), 1, "true", ABSOFF(do_topic_discovery), 0, uf_boolean, 0, pf_boolean, +"

Do not use.

" }, +{ GROUP("Ports", discovery_ports_cfgelems), +"

The Ports element allows specifying various parameters related to the port numbers used for discovery. These all have default values specified by the DDSI 2.1 specification and rarely need to be changed.

" }, +END_MARKER +}; + +static const struct cfgelem tracing_cfgelems[] = { + { LEAF("EnableCategory"), 1, "", 0, 0, 0, uf_logcat, 0, pf_logcat, + "

This element enables individual logging categories. These are enabled in addition to those enabled by Tracing/Verbosity. Recognised categories are:

\n\ +
  • fatal: all fatal errors, errors causing immediate termination
  • \n\ +
  • error: failures probably impacting correctness but not necessarily causing immediate termination
  • \n\ +
  • warning: abnormal situations that will likely not impact correctness
  • \n\ +
  • config: full dump of the configuration
  • \n\ +
  • info: general informational notices
  • \n\ +
  • discovery: all discovery activity
  • \n\ +
  • data: include data content of samples in traces
  • \n\ +
  • radmin: receive buffer administration
  • \n\ +
  • timing: periodic reporting of CPU loads per thread
  • \n\ +
  • traffic: periodic reporting of total outgoing data
  • \n\ +
  • whc: tracing of writer history cache changes
  • \n\ +
  • tcp: tracing of TCP-specific activity
  • \n\ +
  • topic: tracing of topic definitions
  • \n\ +
  • >i>plist: tracing of discovery parameter list interpretation
\n\ +

In addition, there is the keyword trace that enables all but radmin, topic, plist and whc

.\n\ +

The categorisation of tracing output is incomplete and hence most of the verbosity levels and categories are not of much use in the current release. This is an ongoing process and here we describe the target situation rather than the current situation. Currently, the most useful is trace.

" }, +{ LEAF("Verbosity"), 1, "none", 0, 0, 0, uf_verbosity, 0, pf_nop, +"

This element enables standard groups of categories, based on a desired verbosity level. This is in addition to the categories enabled by the Tracing/EnableCategory setting. Recognised verbosity levels and the categories they map to are:

\n\ +
  • none: no DDSI2E log
  • \n\ +
  • severe: error and fatal
  • \n\ +
  • warning: severe + warning
  • \n\ +
  • info: warning + info
  • \n\ +
  • config: info + config
  • \n\ +
  • fine: config + discovery
  • \n\ +
  • finer: fine + traffic and timing
  • \n\ +
  • finest: finer + trace
\n\ +

While none prevents any message from being written to a DDSI2 log file.

\n\ +

The categorisation of tracing output is incomplete and hence most of the verbosity levels and categories are not of much use in the current release. This is an ongoing process and here we describe the target situation rather than the current situation. Currently, the most useful verbosity levels are config, fine and finest.

" }, +{ LEAF("OutputFile"), 1, DDSC_PROJECT_NAME_NOSPACE_SMALL"-trace.log", ABSOFF(tracingOutputFileName), 0, uf_tracingOutputFileName, ff_free, pf_string, +"

This option specifies where the logging is printed to. Note that stdout and stderr are treated as special values, representing \"standard out\" and \"standard error\" respectively. No file is created unless logging categories are enabled using the Tracing/Verbosity or Tracing/EnabledCategory settings.

" }, +{ LEAF_W_ATTRS("Timestamps", timestamp_cfgattrs), 1, "true", ABSOFF(tracingTimestamps), 0, uf_boolean, 0, pf_boolean, +"

This option has no effect.

" }, +{ LEAF("AppendToFile"), 1, "false", ABSOFF(tracingAppendToFile), 0, uf_boolean, 0, pf_boolean, +"

This option specifies whether the output is to be appended to an existing log file. The default is to create a new log file each time, which is generally the best option if a detailed log is generated.

" }, +{ LEAF("PacketCaptureFile"), 1, "", ABSOFF(pcap_file), 0, uf_string, ff_free, pf_string, +"

This option specifies the file to which received and sent packets will be logged in the \"pcap\" format suitable for analysis using common networking tools, such as WireShark. IP and UDP headers are ficitious, in particular the destination address of received packets. The TTL may be used to distinguish between sent and received packets: it is 255 for sent packets and 128 for received ones. Currently IPv4 only.

" }, +END_MARKER +}; + +static const struct cfgelem sched_prio_cfgattrs[] = { + { ATTR("priority_kind"), 1, "relative", ABSOFF(watchdog_sched_priority_class), 0, uf_sched_prio_class, 0, pf_sched_prio_class, + "

This attribute specifies whether the specified Priority is a relative or absolute priority.

" }, + END_MARKER +}; + +static const struct cfgelem sched_cfgelems[] = { + { LEAF("Class"), 1, "default", ABSOFF(watchdog_sched_class), 0, uf_sched_class, 0, pf_sched_class, + "

This element specifies the thread scheduling class that will be used by the watchdog thread. The user may need the appropriate privileges from the underlying operating system to be able to assign some of the privileged scheduling classes.

" }, + { LEAF_W_ATTRS("Priority", sched_prio_cfgattrs), 1, "0", ABSOFF(watchdog_sched_priority), 0, uf_int32, 0, pf_int32, + "

This element specifies the thread priority. Only priorities that are supported by the underlying operating system can be assigned to this element. The user may need special privileges from the underlying operating system to be able to assign some of the privileged priorities.

" }, + END_MARKER +}; + +static const struct cfgelem watchdog_cfgelems[] = { + { GROUP("Scheduling", sched_cfgelems), + "

This element specifies the type of OS scheduling class will be used by the thread that announces its liveliness periodically.

" }, + END_MARKER +}; + +static const struct cfgelem ddsi2_cfgelems[] = { + { GROUP("General", general_cfgelems), + "

The General element specifies overall DDSI2E service settings.

" }, +#ifdef DDSI_INCLUDE_ENCRYPTION + { GROUP("Security", security_cfgelems), + "

The Security element specifies DDSI2E security profiles that can be used to encrypt traffic mapped to DDSI2E network partitions.

" }, +#endif +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + { GROUP("Partitioning", partitioning_cfgelems), + "

The Partitioning element specifies DDSI2E network partitions and how DCPS partition/topic combinations are mapped onto the network partitions.

" }, +#endif +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + { GROUP("Channels", channels_cfgelems), + "

This element is used to group a set of channels. The channels are independent data paths through DDSI2E and by using separate threads and setting their priorities appropriately, chanenls can be used to map transport priorities to operating system scheduler priorities, ensuring system-wide end-to-end priority preservation.

" }, +#endif + { GROUP("Threads", threads_cfgelems), + "

This element is used to set thread properties.

" }, + { GROUP("Sizing", sizing_cfgelems), + "

The Sizing element specifies a variety of configuration settings dealing with expected system sizes, buffer sizes, &c.

" }, + { GROUP("Compatibility", compatibility_cfgelems), + "

The Compatibility elements allows specifying various settings related to compatability with standards and with other DDSI implementations.

" }, + { GROUP("Discovery", discovery_cfgelems), + "

The Discovery element allows specifying various parameters related to the discovery of peers.

" }, + { GROUP("Tracing", tracing_cfgelems), + "

The Tracing element controls the amount and type of information that is written into the tracing log by the DDSI service. This is useful to track the DDSI service during application development.

" }, + { GROUP("Internal|Unsupported", unsupp_cfgelems), + "

The Internal elements deal with a variety of settings that evolving and that are not necessarily fully supported. For the vast majority of the Internal settings, the functionality per-se is supported, but the right to change the way the options control the functionality is reserved. This includes renaming or moving options.

" }, + { GROUP("Watchdog", watchdog_cfgelems), + "

This element specifies the type of OS scheduling class will be used by the thread that announces its liveliness periodically.

" }, + { GROUP("TCP", tcp_cfgelems), + "

The TCP element allows specifying various parameters related to running DDSI over TCP.

" }, + { GROUP("ThreadPool", tp_cfgelems), + "

The ThreadPool element allows specifying various parameters related to using a thread pool to send DDSI messages to multiple unicast addresses (TCP or UDP).

" }, +#ifdef DDSI_INCLUDE_SSL + { GROUP("SSL", ssl_cfgelems), + "

The SSL element allows specifying various parameters related to using SSL/TLS for DDSI over TCP.

" }, +#endif + END_MARKER +}; + +/* Note: using 2e-1 instead of 0.2 to avoid use of the decimal +separator, which is locale dependent. */ +static const struct cfgelem lease_expiry_time_cfgattrs[] = { + { ATTR("update_factor"), 1, "2e-1", ABSOFF(servicelease_update_factor), 0, uf_float, 0, pf_float, NULL }, + END_MARKER +}; + +static const struct cfgelem lease_cfgelems[] = { + { LEAF_W_ATTRS("ExpiryTime", lease_expiry_time_cfgattrs), 1, "10", ABSOFF(servicelease_expiry_time), 0, uf_float, 0, pf_float, NULL }, + END_MARKER +}; + + +static const struct cfgelem domain_cfgelems[] = { + { GROUP("Lease", lease_cfgelems), NULL }, + { LEAF("Id"), 1, "0", ABSOFF(domainId), 0, uf_domainId, 0, pf_int, NULL }, + WILDCARD, + END_MARKER +}; + +static const struct cfgelem durability_cfgelems[] = { + { LEAF("Encoding"), 1, "CDR_CLIENT", ABSOFF(durability_cdr), 0, uf_durability_cdr, 0, pf_durability_cdr, NULL }, + END_MARKER +}; + +static const struct cfgelem root_cfgelems[] = { + { "Domain", domain_cfgelems, NULL, NODATA, NULL }, + { "DDSI2E|DDSI2", ddsi2_cfgelems, NULL, NODATA, + "

DDSI2 settings ...

" }, + { "Durability", durability_cfgelems, NULL, NODATA, NULL }, + { "Lease", lease_cfgelems, NULL, NODATA, NULL }, + END_MARKER +}; + +static const struct cfgelem cyclonedds_root_cfgelems[] = +{ + { DDSC_PROJECT_NAME_NOSPACE, root_cfgelems, NULL, NODATA, NULL }, + END_MARKER +}; + +static const struct cfgelem root_cfgelem = +{ "root", cyclonedds_root_cfgelems, NULL, NODATA, NULL }; + + +#undef ATTR +#undef GROUP +#undef LEAF_W_ATTRS +#undef LEAF +#undef WILDCARD +#undef END_MARKER +#undef NODATA +#undef RELOFF +#undef ABSOFF +#undef CO + +struct config config; +struct ddsi_plugin ddsi_plugin; + +static const struct unit unittab_duration[] = { + { "ns", 1 }, + { "us", 1000 }, + { "ms", T_MILLISECOND }, + { "s", T_SECOND }, + { "min", 60 * T_SECOND }, + { "hr", 3600 * T_SECOND }, + { "day", 24 * 3600 * T_SECOND }, + { NULL, 0 } +}; + +/* note: order affects whether printed as KiB or kB, for consistency +with bandwidths and pedanticness, favour KiB. */ +static const struct unit unittab_memsize[] = { + { "B", 1 }, + { "KiB", 1024 }, + { "kB", 1024 }, + { "MiB", 1048576 }, + { "MB", 1048576 }, + { "GiB", 1073741824 }, + { "GB", 1073741824 }, + { NULL, 0 } +}; + +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING +static const struct unit unittab_bandwidth_bps[] = { + { "b/s", 1 },{ "bps", 1 }, + { "Kib/s", 1024 },{ "Kibps", 1024 }, + { "kb/s", 1000 },{ "kbps", 1000 }, + { "Mib/s", 1048576 },{ "Mibps", 1000 }, + { "Mb/s", 1000000 },{ "Mbps", 1000000 }, + { "Gib/s", 1073741824 },{ "Gibps", 1073741824 }, + { "Gb/s", 1000000000 },{ "Gbps", 1000000000 }, + { "B/s", 8 },{ "Bps", 8 }, + { "KiB/s", 8 * 1024 },{ "KiBps", 8 * 1024 }, + { "kB/s", 8 * 1000 },{ "kBps", 8 * 1000 }, + { "MiB/s", 8 * 1048576 },{ "MiBps", 8 * 1048576 }, + { "MB/s", 8 * 1000000 },{ "MBps", 8 * 1000000 }, + { "GiB/s", 8 * (int64_t) 1073741824 },{ "GiBps", 8 * (int64_t) 1073741824 }, + { "GB/s", 8 * (int64_t) 1000000000 },{ "GBps", 8 * (int64_t) 1000000000 }, + { NULL, 0 } +}; + +static const struct unit unittab_bandwidth_Bps[] = { + { "B/s", 1 },{ "Bps", 1 }, + { "KiB/s", 1024 },{ "KiBps", 1024 }, + { "kB/s", 1000 },{ "kBps", 1000 }, + { "MiB/s", 1048576 },{ "MiBps", 1048576 }, + { "MB/s", 1000000 },{ "MBps", 1000000 }, + { "GiB/s", 1073741824 },{ "GiBps", 1073741824 }, + { "GB/s", 1000000000 },{ "GBps", 1000000000 }, + { NULL, 0 } +}; +#endif + +static void cfgst_push(struct cfgst *cfgst, int isattr, const struct cfgelem *elem, void *parent) +{ + assert(cfgst->path_depth < MAX_PATH_DEPTH); + assert(isattr == 0 || isattr == 1); + cfgst->isattr[cfgst->path_depth] = isattr; + cfgst->path[cfgst->path_depth] = elem; + cfgst->parent[cfgst->path_depth] = parent; + cfgst->path_depth++; +} + +static void cfgst_pop(struct cfgst *cfgst) +{ + assert(cfgst->path_depth > 0); + cfgst->path_depth--; +} + +static const struct cfgelem *cfgst_tos(const struct cfgst *cfgst) +{ + assert(cfgst->path_depth > 0); + return cfgst->path[cfgst->path_depth - 1]; +} + +static void *cfgst_parent(const struct cfgst *cfgst) +{ + assert(cfgst->path_depth > 0); + return cfgst->parent[cfgst->path_depth - 1]; +} + +struct cfg_note_buf { + size_t bufpos; + size_t bufsize; + char *buf; +}; + +static size_t cfg_note_vsnprintf(struct cfg_note_buf *bb, const char *fmt, va_list ap) +{ + int x; + x = os_vsnprintf(bb->buf + bb->bufpos, bb->bufsize - bb->bufpos, fmt, ap); + if ( x >= 0 && (size_t) x >= bb->bufsize - bb->bufpos ) { + size_t nbufsize = ((bb->bufsize + (size_t) x + 1) + 1023) & (size_t) (-1024); + char *nbuf = os_realloc(bb->buf, nbufsize); + bb->buf = nbuf; + bb->bufsize = nbufsize; + return nbufsize; + } + if ( x < 0 ) + NN_FATAL("cfg_note_vsnprintf: os_vsnprintf failed\n"); + else + bb->bufpos += (size_t) x; + return 0; +} + +static void cfg_note_snprintf(struct cfg_note_buf *bb, const char *fmt, ...) +{ + /* The reason the 2nd call to os_vsnprintf is here and not inside + cfg_note_vsnprintf is because I somehow doubt that all platforms + implement va_copy() */ + va_list ap; + size_t r; + va_start(ap, fmt); + r = cfg_note_vsnprintf(bb, fmt, ap); + va_end(ap); + if ( r > 0 ) { + int s; + va_start(ap, fmt); + s = os_vsnprintf(bb->buf + bb->bufpos, bb->bufsize - bb->bufpos, fmt, ap); + if ( s < 0 || (size_t) s >= bb->bufsize - bb->bufpos ) + NN_FATAL("cfg_note_snprintf: os_vsnprintf failed\n"); + va_end(ap); + bb->bufpos += (size_t) s; + } +} + +static size_t cfg_note(struct cfgst *cfgst, logcat_t cat, size_t bsz, const char *fmt, va_list ap) +{ + /* Have to snprintf our way to a single string so we can OS_REPORT + as well as nn_log. Otherwise configuration errors will be lost + completely on platforms where stderr doesn't actually work for + outputting error messages (this includes Windows because of the + way "ospl start" does its thing). */ + struct cfg_note_buf bb; + int i, sidx; + size_t r; + + bb.bufpos = 0; + bb.bufsize = (bsz == 0) ? 1024 : bsz; + if ( (bb.buf = os_malloc(bb.bufsize)) == NULL ) + NN_FATAL("cfg_note: out of memory\n"); + + cfg_note_snprintf(&bb, "config: "); + + /* Path to element/attribute causing the error. Have to stop once an + attribute is reached: a NULL marker may have been pushed onto the + stack afterward in the default handling. */ + sidx = 0; + while ( sidx < cfgst->path_depth && cfgst->path[sidx]->name == NULL ) + sidx++; + for ( i = sidx; i < cfgst->path_depth && (i == sidx || !cfgst->isattr[i - 1]); i++ ) { + if ( cfgst->path[i] == NULL ) { + assert(i > sidx); + cfg_note_snprintf(&bb, "/#text"); + } else if ( cfgst->isattr[i] ) { + cfg_note_snprintf(&bb, "[@%s]", cfgst->path[i]->name); + } else { + const char *p = strchr(cfgst->path[i]->name, '|'); + int n = p ? (int) (p - cfgst->path[i]->name) : (int) strlen(cfgst->path[i]->name); + cfg_note_snprintf(&bb, "%s%*.*s", (i == sidx) ? "" : "/", n, n, cfgst->path[i]->name); + } + } + + cfg_note_snprintf(&bb, ": "); + if ( (r = cfg_note_vsnprintf(&bb, fmt, ap)) > 0 ) { + /* Can't reset ap ... and va_copy isn't widely available - so + instead abort and hope the caller tries again with a larger + initial buffer */ + os_free(bb.buf); + return r; + } + + switch ( cat ) { + case LC_CONFIG: + nn_log(cat, "%s\n", bb.buf); + break; + case LC_WARNING: + NN_WARNING("%s\n", bb.buf); + break; + case LC_ERROR: + NN_ERROR("%s\n", bb.buf); + break; + default: + NN_FATAL("cfg_note unhandled category %u for message %s\n", (unsigned) cat, bb.buf); + break; + } + + os_free(bb.buf); + return 0; +} + +#if WARN_DEPRECATED_ALIAS || WARN_DEPRECATED_UNIT +static void cfg_warning(struct cfgst *cfgst, const char *fmt, ...) +{ + va_list ap; + size_t bsz = 0; + do { + va_start(ap, fmt); + bsz = cfg_note(cfgst, LC_WARNING, bsz, fmt, ap); + va_end(ap); + } while ( bsz > 0 ); +} +#endif + +static int cfg_error(struct cfgst *cfgst, const char *fmt, ...) +{ + va_list ap; + size_t bsz = 0; + do { + va_start(ap, fmt); + bsz = cfg_note(cfgst, LC_ERROR, bsz, fmt, ap); + va_end(ap); + } while ( bsz > 0 ); + return 0; +} + +static int cfg_log(struct cfgst *cfgst, const char *fmt, ...) +{ + va_list ap; + size_t bsz = 0; + do { + va_start(ap, fmt); + bsz = cfg_note(cfgst, LC_CONFIG, bsz, fmt, ap); + va_end(ap); + } while ( bsz > 0 ); + return 0; +} + +static int list_index(const char *list[], const char *elem) +{ + int i; + for ( i = 0; list[i] != NULL; i++ ) { + if ( os_strcasecmp(list[i], elem) == 0 ) + return i; + } + return -1; +} + +static int64_t lookup_multiplier(struct cfgst *cfgst, const struct unit *unittab, const char *value, int unit_pos, int value_is_zero, int64_t def_mult, int err_on_unrecognised) +{ + assert(0 <= unit_pos && (size_t) unit_pos <= strlen(value)); + while ( value[unit_pos] == ' ' ) + unit_pos++; + if ( value[unit_pos] == 0 ) { + if ( value_is_zero ) { + /* No matter what unit, 0 remains just that. For convenience, + always allow 0 to be specified without a unit */ + return 1; + } else if ( def_mult == 0 && err_on_unrecognised ) { + cfg_error(cfgst, "%s: unit is required", value); + return 0; + } else { +#if WARN_DEPRECATED_UNIT + cfg_warning(cfgst, "%s: use of default unit is deprecated", value); +#endif + return def_mult; + } + } else { + int i; + for ( i = 0; unittab[i].name != NULL; i++ ) { + if ( strcmp(unittab[i].name, value + unit_pos) == 0 ) + return unittab[i].multiplier; + } + if ( err_on_unrecognised ) + cfg_error(cfgst, "%s: unrecognised unit", value + unit_pos); + return 0; + } +} + +static void *cfg_address(UNUSED_ARG(struct cfgst *cfgst), void *parent, struct cfgelem const * const cfgelem) +{ + assert(cfgelem->multiplicity == 1); + return (char *) parent + cfgelem->elem_offset; +} + +static void *cfg_deref_address(UNUSED_ARG(struct cfgst *cfgst), void *parent, struct cfgelem const * const cfgelem) +{ + assert(cfgelem->multiplicity != 1); + return *((void **) ((char *) parent + cfgelem->elem_offset)); +} + +static void *if_common(UNUSED_ARG(struct cfgst *cfgst), void *parent, struct cfgelem const * const cfgelem, unsigned size) +{ + struct config_listelem **current = (struct config_listelem **) ((char *) parent + cfgelem->elem_offset); + struct config_listelem *new = os_malloc(size); + new->next = *current; + *current = new; + return new; +} + +static int if_thread_properties(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem) +{ + struct config_thread_properties_listelem *new = if_common(cfgst, parent, cfgelem, sizeof(*new)); + if ( new == NULL ) + return -1; + new->name = NULL; + return 0; +} + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS +static int if_channel(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem) +{ + struct config_channel_listelem *new = if_common(cfgst, parent, cfgelem, sizeof(*new)); + if ( new == NULL ) + return -1; + new->name = NULL; + new->channel_reader_ts = NULL; + new->dqueue = NULL; + new->queueId = 0; + new->evq = NULL; + new->transmit_conn = NULL; + return 0; +} +#endif /* DDSI_INCLUDE_NETWORK_CHANNELS */ + +#ifdef DDSI_INCLUDE_ENCRYPTION +static int if_security_profile(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem) +{ + if ( if_common(cfgst, parent, cfgelem, sizeof(struct config_securityprofile_listelem)) == NULL ) + return -1; + return 0; +} +#endif /* DDSI_INCLUDE_ENCRYPTION */ + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS +static int if_network_partition(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem) +{ + struct config_networkpartition_listelem *new = if_common(cfgst, parent, cfgelem, sizeof(*new)); + if ( new == NULL ) + return -1; + new->address_string = NULL; +#ifdef DDSI_INCLUDE_ENCRYPTION + new->profileName = NULL; + new->securityProfile = NULL; +#endif /* DDSI_INCLUDE_ENCRYPTION */ + return 0; +} + +static int if_ignored_partition(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem) +{ + if ( if_common(cfgst, parent, cfgelem, sizeof(struct config_ignoredpartition_listelem)) == NULL ) + return -1; + return 0; +} + +static int if_partition_mapping(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem) +{ + if ( if_common(cfgst, parent, cfgelem, sizeof(struct config_partitionmapping_listelem)) == NULL ) + return -1; + return 0; +} +#endif /* DDSI_INCLUDE_NETWORK_PARTITIONS */ + +static int if_peer(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem) +{ + struct config_peer_listelem *new = if_common(cfgst, parent, cfgelem, sizeof(struct config_peer_listelem)); + if ( new == NULL ) + return -1; + new->peer = NULL; + return 0; +} + +static void ff_free(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem) +{ + void **elem = cfg_address(cfgst, parent, cfgelem); + os_free(*elem); +} + +static int uf_boolean(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + static const char *vs[] = { "false", "true", NULL }; + int *elem = cfg_address(cfgst, parent, cfgelem); + int idx = list_index(vs, value); + if ( idx < 0 ) + return cfg_error(cfgst, "'%s': undefined value", value); + else { + *elem = idx; + return 1; + } +} + +static int uf_negated_boolean(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int first, const char *value) +{ + if ( !uf_boolean(cfgst, parent, cfgelem, first, value) ) + return 0; + else { + int *elem = cfg_address(cfgst, parent, cfgelem); + *elem = !*elem; + return 1; + } +} + + +static int uf_logcat(struct cfgst *cfgst, UNUSED_ARG(void *parent), UNUSED_ARG(struct cfgelem const * const cfgelem), UNUSED_ARG(int first), const char *value) +{ + static const char **vs = logcat_names; + static const logcat_t *lc = logcat_codes; + char *copy = os_strdup(value), *cursor = copy, *tok; + while ( (tok = os_strsep(&cursor, ",")) != NULL ) { + int idx = list_index(vs, tok); + if ( idx < 0 ) { + int ret = cfg_error(cfgst, "'%s' in '%s' undefined", tok, value); + os_free(copy); + return ret; + } + enabled_logcats |= lc[idx]; + } + os_free(copy); + return 1; +} + +static int uf_verbosity(struct cfgst *cfgst, UNUSED_ARG(void *parent), UNUSED_ARG(struct cfgelem const * const cfgelem), UNUSED_ARG(int first), const char *value) +{ + static const char *vs[] = { + "finest", "finer", "fine", "config", "info", "warning", "severe", "none", NULL + }; + static const logcat_t lc[] = { + LC_ALLCATS, LC_TRAFFIC | LC_TIMING, LC_DISCOVERY | LC_THROTTLE, LC_CONFIG, LC_INFO, LC_WARNING, LC_ERROR | LC_FATAL, 0, 0 + }; + int idx = list_index(vs, value); + assert(sizeof(vs) / sizeof(*vs) == sizeof(lc) / sizeof(*lc)); + if ( idx < 0 ) + return cfg_error(cfgst, "'%s': undefined value", value); + else { + int i; + for ( i = (int) (sizeof(vs) / sizeof(*vs)) - 1; i >= idx; i-- ) + enabled_logcats |= lc[i]; + return 1; + } +} + +static int uf_besmode(struct cfgst *cfgst, UNUSED_ARG(void *parent), UNUSED_ARG(struct cfgelem const * const cfgelem), UNUSED_ARG(int first), const char *value) +{ + static const char *vs[] = { + "full", "writers", "minimal", NULL + }; + static const enum besmode ms[] = { + BESMODE_FULL, BESMODE_WRITERS, BESMODE_MINIMAL, 0, + }; + int idx = list_index(vs, value); + enum besmode *elem = cfg_address(cfgst, parent, cfgelem); + assert(sizeof(vs) / sizeof(*vs) == sizeof(ms) / sizeof(*ms)); + if ( idx < 0 ) + return cfg_error(cfgst, "'%s': undefined value", value); + *elem = ms[idx]; + return 1; +} + +static void pf_besmode(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + enum besmode *p = cfg_address(cfgst, parent, cfgelem); + const char *str = "INVALID"; + switch ( *p ) { + case BESMODE_FULL: str = "full"; break; + case BESMODE_WRITERS: str = "writers"; break; + case BESMODE_MINIMAL: str = "minimal"; break; + } + cfg_log(cfgst, "%s%s", str, is_default ? " [def]" : ""); +} + + +static int uf_durability_cdr(struct cfgst *cfgst, UNUSED_ARG(void *parent), UNUSED_ARG(struct cfgelem const * const cfgelem), UNUSED_ARG(int first), const char *value) +{ + static const char *vs[] = { "cdr_le", "cdr_be", "cdr_server", "cdr_client", NULL }; + static const enum durability_cdr ms[] = { DUR_CDR_LE, DUR_CDR_BE, DUR_CDR_SERVER, DUR_CDR_CLIENT, 0 }; + int idx = list_index(vs, value); + enum durability_cdr * elem = cfg_address(cfgst, parent, cfgelem); + if ( idx < 0 ) { + return cfg_error(cfgst, "'%s': undefined value", value); + } + *elem = ms[idx]; + return 1; +} + +static void pf_durability_cdr(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + enum durability_cdr *p = cfg_address(cfgst, parent, cfgelem); + const char *str = "INVALID"; + switch ( *p ) { + case DUR_CDR_LE: str = "cdr_le"; break; + case DUR_CDR_BE: str = "cdr_be"; break; + case DUR_CDR_SERVER: str = "cdr_server"; break; + case DUR_CDR_CLIENT: str = "cdr_client"; break; + } + cfg_log(cfgst, "%s%s", str, is_default ? " [def]" : ""); +} + + +static int uf_retransmit_merging(struct cfgst *cfgst, UNUSED_ARG(void *parent), UNUSED_ARG(struct cfgelem const * const cfgelem), UNUSED_ARG(int first), const char *value) +{ + static const char *vs[] = { + "never", "adaptive", "always", NULL + }; + static const enum retransmit_merging ms[] = { + REXMIT_MERGE_NEVER, REXMIT_MERGE_ADAPTIVE, REXMIT_MERGE_ALWAYS, 0, + }; + int idx = list_index(vs, value); + enum retransmit_merging *elem = cfg_address(cfgst, parent, cfgelem); + assert(sizeof(vs) / sizeof(*vs) == sizeof(ms) / sizeof(*ms)); + if ( idx < 0 ) + return cfg_error(cfgst, "'%s': undefined value", value); + *elem = ms[idx]; + return 1; +} + +static void pf_retransmit_merging(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + enum retransmit_merging *p = cfg_address(cfgst, parent, cfgelem); + const char *str = "INVALID"; + switch ( *p ) { + case REXMIT_MERGE_NEVER: str = "never"; break; + case REXMIT_MERGE_ADAPTIVE: str = "adaptive"; break; + case REXMIT_MERGE_ALWAYS: str = "always"; break; + } + cfg_log(cfgst, "%s%s", str, is_default ? " [def]" : ""); +} + +static int uf_string(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + char **elem = cfg_address(cfgst, parent, cfgelem); + *elem = os_strdup(value); + return 1; +} + +static int uf_natint64_unit(struct cfgst *cfgst, int64_t *elem, const char *value, const struct unit *unittab, int64_t def_mult, int64_t max) +{ + int pos; + double v_dbl; + int64_t v_int; + int64_t mult; + /* try convert as integer + optional unit; if that fails, try + floating point + optional unit (round, not truncate, to integer) */ + if ( *value == 0 ) { + *elem = 0; /* some static analyzers don't "get it" */ + return cfg_error(cfgst, "%s: empty string is not a valid value", value); + } else if ( sscanf(value, "%lld%n", (long long int *) &v_int, &pos) == 1 && (mult = lookup_multiplier(cfgst, unittab, value, pos, v_int == 0, def_mult, 0)) != 0 ) { + assert(mult > 0); + if ( v_int < 0 || v_int > max / mult ) + return cfg_error(cfgst, "%s: value out of range", value); + *elem = mult * v_int; + return 1; + } else if ( sscanf(value, "%lf%n", &v_dbl, &pos) == 1 && (mult = lookup_multiplier(cfgst, unittab, value, pos, v_dbl == 0, def_mult, 1)) != 0 ) { + double dmult = (double) mult; + assert(dmult > 0); + if ( v_dbl < 0 || (int64_t) (v_dbl * dmult + 0.5) > max ) + return cfg_error(cfgst, "%s: value out of range", value); + *elem = (int64_t) (v_dbl * dmult + 0.5); + return 1; + } else { + *elem = 0; /* some static analyzers don't "get it" */ + return cfg_error(cfgst, "%s: invalid value", value); + } +} + +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING +static int uf_bandwidth(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + int64_t bandwidth_bps = 0; + if ( strncmp(value, "inf", 3) == 0 ) { + /* special case: inf needs no unit */ + int *elem = cfg_address(cfgst, parent, cfgelem); + if ( strspn(value + 3, " ") != strlen(value + 3) && + lookup_multiplier(cfgst, unittab_bandwidth_bps, value, 3, 1, 8, 1) == 0 ) + return 0; + *elem = 0; + return 1; + } else if ( !uf_natint64_unit(cfgst, &bandwidth_bps, value, unittab_bandwidth_bps, 8, INT64_MAX) ) { + return 0; + } else if ( bandwidth_bps / 8 > INT_MAX ) { + return cfg_error(cfgst, "%s: value out of range", value); + } else { + uint32_t *elem = cfg_address(cfgst, parent, cfgelem); + *elem = (uint32_t) (bandwidth_bps / 8); + return 1; + } +} +#endif + +static int uf_memsize(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + int64_t size = 0; + if ( !uf_natint64_unit(cfgst, &size, value, unittab_memsize, 1, INT32_MAX) ) + return 0; + else { + uint32_t *elem = cfg_address(cfgst, parent, cfgelem); + *elem = (uint32_t) size; + return 1; + } +} + +#ifdef DDSI_INCLUDE_ENCRYPTION +static int uf_cipher(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + if ( q_security_plugin.cipher_type_from_string ) { + q_cipherType *elem = cfg_address(cfgst, parent, cfgelem); + if ( (q_security_plugin.cipher_type_from_string) (value, elem) ) { + return 1; + } else { + return cfg_error(cfgst, "%s: undefined value", value); + } + } + return 1; +} +#endif /* DDSI_INCLUDE_ENCRYPTION */ + + +static int uf_tracingOutputFileName(struct cfgst *cfgst, UNUSED_ARG(void *parent), UNUSED_ARG(struct cfgelem const * const cfgelem), UNUSED_ARG(int first), const char *value) +{ + struct config *cfg = cfgst->cfg; + { + cfg->tracingOutputFileName = os_strdup(value); + } + return 1; +} + +static int uf_ipv4(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int first, const char *value) +{ + /* Not actually doing any checking yet */ + return uf_string(cfgst, parent, cfgelem, first, value); +} + +static int uf_networkAddress(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int first, const char *value) +{ + if ( os_strcasecmp(value, "auto") != 0 ) + return uf_ipv4(cfgst, parent, cfgelem, first, value); + else { + char **elem = cfg_address(cfgst, parent, cfgelem); + *elem = NULL; + return 1; + } +} + +static void ff_networkAddresses(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem) +{ + char ***elem = cfg_address(cfgst, parent, cfgelem); + int i; + for ( i = 0; (*elem)[i]; i++ ) + os_free((*elem)[i]); + os_free(*elem); +} + +static int uf_networkAddresses_simple(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + char ***elem = cfg_address(cfgst, parent, cfgelem); + if ( (*elem = os_malloc(2 * sizeof(char *))) == NULL ) + return cfg_error(cfgst, "out of memory"); + if ( ((*elem)[0] = os_strdup(value)) == NULL ) { + os_free(*elem); + *elem = NULL; + return cfg_error(cfgst, "out of memory"); + } + (*elem)[1] = NULL; + return 1; +} + +static int uf_networkAddresses(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int first, const char *value) +{ + /* Check for keywords first */ + { + static const char *keywords[] = { "all", "any", "none" }; + int i; + for ( i = 0; i < (int) (sizeof(keywords) / sizeof(*keywords)); i++ ) { + if ( os_strcasecmp(value, keywords[i]) == 0 ) + return uf_networkAddresses_simple(cfgst, parent, cfgelem, first, keywords[i]); + } + } + + /* If not keyword, then comma-separated list of addresses */ + { + char ***elem = cfg_address(cfgst, parent, cfgelem); + char *copy; + unsigned count; + + /* First count how many addresses we have - but do it stupidly by + counting commas and adding one (so two commas in a row are both + counted) */ + { + const char *scan = value; + count = 1; + while ( *scan ) + count += (*scan++ == ','); + } + + copy = os_strdup(value); + + /* Allocate an array of address strings (which may be oversized a + bit because of the counting of the commas) */ + *elem = os_malloc((count + 1) * sizeof(char *)); + + { + char *cursor = copy, *tok; + unsigned idx = 0; + while ( (tok = os_strsep(&cursor, ",")) != NULL ) { + assert(idx < count); + (*elem)[idx] = os_strdup(tok); + idx++; + } + (*elem)[idx] = NULL; + } + os_free(copy); + } + return 1; +} + +static int uf_allow_multicast(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ +#ifdef DDSI_INCLUDE_SSM + static const char *vs[] = { "false", "spdp", "asm", "ssm", "true", NULL }; + static const unsigned bs[] = { AMC_FALSE, AMC_SPDP, AMC_ASM, AMC_SSM, AMC_TRUE }; +#else + static const char *vs[] = { "false", "spdp", "asm", "true", NULL }; + static const unsigned bs[] = { AMC_FALSE, AMC_SPDP, AMC_ASM, AMC_TRUE }; +#endif + char *copy = os_strdup(value), *cursor = copy, *tok; + unsigned *elem = cfg_address(cfgst, parent, cfgelem); + if ( copy == NULL ) + return cfg_error(cfgst, "out of memory"); + *elem = 0; + while ( (tok = os_strsep(&cursor, ",")) != NULL ) { + int idx = list_index(vs, tok); + if ( idx < 0 ) { + int ret = cfg_error(cfgst, "'%s' in '%s' undefined", tok, value); + os_free(copy); + return ret; + } + *elem |= bs[idx]; + } + os_free(copy); + return 1; +} + +static void pf_allow_multicast(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + unsigned *p = cfg_address(cfgst, parent, cfgelem); + const char *str = "INVALID"; + switch ( *p ) { + case AMC_FALSE: str = "false"; break; + case AMC_SPDP: str = "spdp"; break; + case AMC_ASM: str = "asm"; break; +#ifdef DDSI_INCLUDE_SSM + case AMC_SSM: str = "ssm"; break; + case (AMC_SPDP | AMC_ASM): str = "spdp,asm"; break; + case (AMC_SPDP | AMC_SSM): str = "spdp,ssm"; break; +#endif + case AMC_TRUE: str = "true"; break; + } + cfg_log(cfgst, "%s%s", str, is_default ? " [def]" : ""); +} + +static int uf_sched_prio_class(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + int ret; + q__schedPrioClass *prio; + + assert(value != NULL); + + prio = cfg_address(cfgst, parent, cfgelem); + + if ( os_strcasecmp(value, "relative") == 0 ) { + *prio = Q__SCHED_PRIO_RELATIVE; + ret = 1; + } else if ( os_strcasecmp(value, "absolute") == 0 ) { + *prio = Q__SCHED_PRIO_ABSOLUTE; + ret = 1; + } else { + ret = cfg_error(cfgst, "'%s': undefined value", value); + } + + return ret; +} + +static void pf_sched_prio_class(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + char *str; + q__schedPrioClass *prio = cfg_address(cfgst, parent, cfgelem); + + if ( *prio == Q__SCHED_PRIO_RELATIVE ) { + str = "relative"; + } else if ( *prio == Q__SCHED_PRIO_ABSOLUTE ) { + str = "absolute"; + } else { + str = "INVALID"; + } + + cfg_log(cfgst, "%s%s", str, is_default ? " [def]" : ""); +} + +static int uf_sched_class(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + static const char *vs[] = { "realtime", "timeshare", "default" }; + static const os_schedClass ms[] = { OS_SCHED_REALTIME, OS_SCHED_TIMESHARE, OS_SCHED_DEFAULT }; + int idx = list_index(vs, value); + os_schedClass *elem = cfg_address(cfgst, parent, cfgelem); + assert(sizeof(vs) / sizeof(*vs) == sizeof(ms) / sizeof(*ms)); + if ( idx < 0 ) + return cfg_error(cfgst, "'%s': undefined value", value); + *elem = ms[idx]; + return 1; +} + +static void pf_sched_class(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + os_schedClass *p = cfg_address(cfgst, parent, cfgelem); + const char *str = "INVALID"; + switch ( *p ) { + case OS_SCHED_DEFAULT: str = "default"; break; + case OS_SCHED_TIMESHARE: str = "timeshare"; break; + case OS_SCHED_REALTIME: str = "realtime"; break; + } + cfg_log(cfgst, "%s%s", str, is_default ? " [def]" : ""); +} + +static int uf_maybe_int32(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + struct config_maybe_int32 *elem = cfg_address(cfgst, parent, cfgelem); + int pos; + if ( os_strcasecmp(value, "default") == 0 ) { + elem->isdefault = 1; + elem->value = 0; + return 1; + } else if ( sscanf(value, "%d%n", &elem->value, &pos) == 1 && value[pos] == 0 ) { + elem->isdefault = 0; + return 1; + } else { + return cfg_error(cfgst, "'%s': neither 'default' nor a decimal integer\n", value); + } +} + +static int uf_maybe_memsize(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + struct config_maybe_uint32 *elem = cfg_address(cfgst, parent, cfgelem); + int64_t size = 0; + if ( os_strcasecmp(value, "default") == 0 ) { + elem->isdefault = 1; + elem->value = 0; + return 1; + } else if ( !uf_natint64_unit(cfgst, &size, value, unittab_memsize, 1, INT32_MAX) ) { + return 0; + } else { + elem->isdefault = 0; + elem->value = (uint32_t) size; + return 1; + } +} + + +static int uf_float(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + float *elem = cfg_address(cfgst, parent, cfgelem); + char *endptr; + float f = os_strtof(value, &endptr); + if ( *value == 0 || *endptr != 0 ) + return cfg_error(cfgst, "%s: not a floating point number", value); + *elem = f; + return 1; +} + +static int uf_int(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + int *elem = cfg_address(cfgst, parent, cfgelem); + char *endptr; + long v = strtol(value, &endptr, 10); + if ( *value == 0 || *endptr != 0 ) + return cfg_error(cfgst, "%s: not a decimal integer", value); + if ( v != (int) v ) + return cfg_error(cfgst, "%s: value out of range", value); + *elem = (int) v; + return 1; +} + +static int uf_duration_gen(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, const char *value, int64_t def_mult, int64_t max_ns) +{ + return uf_natint64_unit(cfgst, cfg_address(cfgst, parent, cfgelem), value, unittab_duration, def_mult, max_ns); +} + +static int uf_duration_inf(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + if ( os_strcasecmp(value, "inf") == 0 ) { + int64_t *elem = cfg_address(cfgst, parent, cfgelem); + *elem = T_NEVER; + return 1; + } else { + return uf_duration_gen(cfgst, parent, cfgelem, value, 0, T_NEVER - 1); + } +} + +static int uf_duration_ms_1hr(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + return uf_duration_gen(cfgst, parent, cfgelem, value, T_MILLISECOND, 3600 * T_SECOND); +} + +static int uf_duration_ms_1s(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + return uf_duration_gen(cfgst, parent, cfgelem, value, T_MILLISECOND, T_SECOND); +} + +static int uf_duration_us_1s(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + return uf_duration_gen(cfgst, parent, cfgelem, value, 1000, T_SECOND); +} + +static int uf_int32(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + int32_t *elem = cfg_address(cfgst, parent, cfgelem); + char *endptr; + long v = strtol(value, &endptr, 10); + if ( *value == 0 || *endptr != 0 ) + return cfg_error(cfgst, "%s: not a decimal integer", value); + if ( v != (int32_t) v ) + return cfg_error(cfgst, "%s: value out of range", value); + *elem = (int32_t) v; + return 1; +} + +#if 0 +static int uf_uint32(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + uint32_t *elem = cfg_address(cfgst, parent, cfgelem); + char *endptr; + unsigned long v = strtoul(value, &endptr, 10); + if ( *value == 0 || *endptr != 0 ) + return cfg_error(cfgst, "%s: not a decimal integer", value); + if ( v != (uint32_t) v ) + return cfg_error(cfgst, "%s: value out of range", value); + *elem = (uint32_t) v; + return 1; +} +#endif + +static int uf_uint(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + unsigned *elem = cfg_address(cfgst, parent, cfgelem); + char *endptr; + unsigned long v = strtoul(value, &endptr, 10); + if ( *value == 0 || *endptr != 0 ) + return cfg_error(cfgst, "%s: not a decimal integer", value); + if ( v != (unsigned) v ) + return cfg_error(cfgst, "%s: value out of range", value); + *elem = (unsigned) v; + return 1; +} + +static int uf_int_min_max(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int first, const char *value, int min, int max) +{ + int *elem = cfg_address(cfgst, parent, cfgelem); + if ( !uf_int(cfgst, parent, cfgelem, first, value) ) + return 0; + else if ( *elem < min || *elem > max ) + return cfg_error(cfgst, "%s: out of range", value); + else + return 1; +} + +static int uf_domainId(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int first, const char *value) +{ + return uf_int_min_max(cfgst, parent, cfgelem, first, value, 0, 230); +} + +static int uf_participantIndex(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int first, const char *value) +{ + int *elem = cfg_address(cfgst, parent, cfgelem); + if ( os_strcasecmp(value, "auto") == 0 ) { + *elem = PARTICIPANT_INDEX_AUTO; + return 1; + } else if ( os_strcasecmp(value, "none") == 0 ) { + *elem = PARTICIPANT_INDEX_NONE; + return 1; + } else { + return uf_int_min_max(cfgst, parent, cfgelem, first, value, 0, 120); + } +} + +static int uf_port(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int first, const char *value) +{ + return uf_int_min_max(cfgst, parent, cfgelem, first, value, 1, 65535); +} + +static int uf_dyn_port(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int first, const char *value) +{ + return uf_int_min_max(cfgst, parent, cfgelem, first, value, -1, 65535); +} + +static int uf_natint(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int first, const char *value) +{ + return uf_int_min_max(cfgst, parent, cfgelem, first, value, 0, INT32_MAX); +} + +static int uf_natint_255(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int first, const char *value) +{ + return uf_int_min_max(cfgst, parent, cfgelem, first, value, 0, 255); +} + +static int do_update(struct cfgst *cfgst, update_fun_t upd, void *parent, struct cfgelem const * const cfgelem, const char *value, int is_default) +{ + struct cfgst_node *n; + struct cfgst_nodekey key; + ut_avlIPath_t np; + int ok; + key.e = cfgelem; + if ( (n = ut_avlLookupIPath(&cfgst_found_treedef, &cfgst->found, &key, &np)) == NULL ) { + if ( (n = os_malloc(sizeof(*n))) == NULL ) + return cfg_error(cfgst, "out of memory"); + + n->key = key; + n->count = 0; + n->failed = 0; + n->is_default = is_default; + ut_avlInsertIPath(&cfgst_found_treedef, &cfgst->found, n, &np); + } + if ( cfgelem->multiplicity == 0 || n->count < cfgelem->multiplicity ) + ok = upd(cfgst, parent, cfgelem, (n->count == n->failed), value); + else + ok = cfg_error(cfgst, "only %d instance(s) allowed", cfgelem->multiplicity); + n->count++; + if ( !ok ) { + n->failed++; + } + return ok; +} + +static int set_default(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem) +{ + if ( cfgelem->defvalue == NULL ) + return cfg_error(cfgst, "element missing in configuration"); + return do_update(cfgst, cfgelem->update, parent, cfgelem, cfgelem->defvalue, 1); +} + +static int set_defaults(struct cfgst *cfgst, void *parent, int isattr, struct cfgelem const * const cfgelem, int clear_found) +{ + const struct cfgelem *ce; + int ok = 1; + for ( ce = cfgelem; ce && ce->name; ce++ ) { + struct cfgst_node *n; + struct cfgst_nodekey key; + key.e = ce; + cfgst_push(cfgst, isattr, ce, parent); + if ( ce->multiplicity == 1 ) { + if ( ut_avlLookup(&cfgst_found_treedef, &cfgst->found, &key) == NULL ) { + if ( ce->update ) { + int ok1; + cfgst_push(cfgst, 0, NULL, NULL); + ok1 = set_default(cfgst, parent, ce); + cfgst_pop(cfgst); + ok = ok && ok1; + } + } + if ( (n = ut_avlLookup(&cfgst_found_treedef, &cfgst->found, &key)) != NULL ) { + if ( clear_found ) { + ut_avlDelete(&cfgst_found_treedef, &cfgst->found, n); + os_free(n); + } + } + if ( ce->children ) { + int ok1 = set_defaults(cfgst, parent, 0, ce->children, clear_found); + ok = ok && ok1; + } + if ( ce->attributes ) { + int ok1 = set_defaults(cfgst, parent, 1, ce->attributes, clear_found); + ok = ok && ok1; + } + } + cfgst_pop(cfgst); + } + return ok; +} + +static void pf_nop(UNUSED_ARG(struct cfgst *cfgst), UNUSED_ARG(void *parent), UNUSED_ARG(struct cfgelem const * const cfgelem), UNUSED_ARG(int is_default)) +{ +} + +static void pf_string(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + char **p = cfg_address(cfgst, parent, cfgelem); + cfg_log(cfgst, "%s%s", *p ? *p : "(null)", is_default ? " [def]" : ""); +} + +static void pf_int64_unit(struct cfgst *cfgst, int64_t value, int is_default, const struct unit *unittab, const char *zero_unit) +{ + if ( value == 0 ) { + /* 0s is a bit of a special case: we don't want to print 0hr (or + whatever unit will have the greatest multiplier), so hard-code + as 0s */ + cfg_log(cfgst, "0 %s%s", zero_unit, is_default ? " [def]" : ""); + } else { + int64_t m = 0; + const char *unit = NULL; + int i; + for ( i = 0; unittab[i].name != NULL; i++ ) { + if ( unittab[i].multiplier > m && (value % unittab[i].multiplier) == 0 ) { + m = unittab[i].multiplier; + unit = unittab[i].name; + } + } + assert(m > 0); + assert(unit != NULL); + cfg_log(cfgst, "%lld %s%s", value / m, unit, is_default ? " [def]" : ""); + } +} + +#ifdef DDSI_INCLUDE_ENCRYPTION +static void pf_key(struct cfgst *cfgst, UNUSED_ARG(void *parent), UNUSED_ARG(struct cfgelem const * const cfgelem), UNUSED_ARG(int is_default)) +{ + cfg_log(cfgst, ""); +} + +static void pf_cipher(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + q_cipherType *p = cfg_address(cfgst, parent, cfgelem); + if ( q_security_plugin.cipher_type ) { + cfg_log(cfgst, "%s%s", (q_security_plugin.cipher_type) (*p), is_default ? " [def]" : ""); + } +} +#endif /* DDSI_INCLUDE_ENCRYPTION */ + +static void pf_networkAddress(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + char **p = cfg_address(cfgst, parent, cfgelem); + cfg_log(cfgst, "%s%s", *p ? *p : "auto", is_default ? " [def]" : ""); +} + +static void pf_participantIndex(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + int *p = cfg_address(cfgst, parent, cfgelem); + switch ( *p ) { + case PARTICIPANT_INDEX_NONE: + cfg_log(cfgst, "none%s", is_default ? " [def]" : ""); + break; + case PARTICIPANT_INDEX_AUTO: + cfg_log(cfgst, "auto%s", is_default ? " [def]" : ""); + break; + default: + cfg_log(cfgst, "%d%s", *p, is_default ? " [def]" : ""); + break; + } +} + +static void pf_networkAddresses(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + int i; + char ***p = cfg_address(cfgst, parent, cfgelem); + for ( i = 0; (*p)[i] != NULL; i++ ) + cfg_log(cfgst, "%s%s", (*p)[i], is_default ? " [def]" : ""); +} + +static void pf_int(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + int *p = cfg_address(cfgst, parent, cfgelem); + cfg_log(cfgst, "%d%s", *p, is_default ? " [def]" : ""); +} + +static void pf_uint(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + unsigned *p = cfg_address(cfgst, parent, cfgelem); + cfg_log(cfgst, "%u%s", *p, is_default ? " [def]" : ""); +} + +static void pf_duration(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + const int64_t *elem = cfg_address(cfgst, parent, cfgelem); + if ( *elem == T_NEVER ) + cfg_log(cfgst, "inf%s", is_default ? " [def]" : ""); + else + pf_int64_unit(cfgst, *elem, is_default, unittab_duration, "s"); +} + +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING +static void pf_bandwidth(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + const uint32_t *elem = cfg_address(cfgst, parent, cfgelem); + if ( *elem == 0 ) + cfg_log(cfgst, "inf%s", is_default ? " [def]" : ""); + else + pf_int64_unit(cfgst, *elem, is_default, unittab_bandwidth_Bps, "B/s"); +} +#endif + +static void pf_memsize(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + const int *elem = cfg_address(cfgst, parent, cfgelem); + pf_int64_unit(cfgst, *elem, is_default, unittab_memsize, "B"); +} + +static void pf_int32(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + int32_t *p = cfg_address(cfgst, parent, cfgelem); + cfg_log(cfgst, "%d%s", *p, is_default ? " [def]" : ""); +} + +#if 0 +static void pf_uint32(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + uint32_t *p = cfg_address(cfgst, parent, cfgelem); + cfg_log(cfgst, "%u%s", *p, is_default ? " [def]" : ""); +} +#endif + +static void pf_maybe_int32(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + struct config_maybe_int32 *p = cfg_address(cfgst, parent, cfgelem); + if ( p->isdefault ) + cfg_log(cfgst, "default%s", is_default ? " [def]" : ""); + else + cfg_log(cfgst, "%d%s", p->value, is_default ? " [def]" : ""); +} + +static void pf_maybe_memsize(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + struct config_maybe_uint32 *p = cfg_address(cfgst, parent, cfgelem); + if ( p->isdefault ) + cfg_log(cfgst, "default%s", is_default ? " [def]" : ""); + else + pf_int64_unit(cfgst, p->value, is_default, unittab_memsize, "B"); +} + + +static void pf_float(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + float *p = cfg_address(cfgst, parent, cfgelem); + cfg_log(cfgst, "%f%s", *p, is_default ? " [def]" : ""); +} + +static void pf_boolean(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + int *p = cfg_address(cfgst, parent, cfgelem); + cfg_log(cfgst, "%s%s", *p ? "true" : "false", is_default ? " [def]" : ""); +} + +static void pf_negated_boolean(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + int *p = cfg_address(cfgst, parent, cfgelem); + cfg_log(cfgst, "%s%s", *p ? "false" : "true", is_default ? " [def]" : ""); +} + +static int uf_standards_conformance(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, UNUSED_ARG(int first), const char *value) +{ + static const char *vs[] = { + "pedantic", "strict", "lax", NULL + }; + static const logcat_t lc[] = { + NN_SC_PEDANTIC, NN_SC_STRICT, NN_SC_LAX, 0 + }; + enum nn_standards_conformance *elem = cfg_address(cfgst, parent, cfgelem); + int idx = list_index(vs, value); + assert(sizeof(vs) / sizeof(*vs) == sizeof(lc) / sizeof(*lc)); + if ( idx < 0 ) + return cfg_error(cfgst, "'%s': undefined value", value); + else { + *elem = lc[idx]; + return 1; + } +} + +static void pf_standards_conformance(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem, int is_default) +{ + enum nn_standards_conformance *p = cfg_address(cfgst, parent, cfgelem); + const char *str = "INVALID"; + switch ( *p ) { + case NN_SC_PEDANTIC: str = "pedantic"; break; + case NN_SC_STRICT: str = "strict"; break; + case NN_SC_LAX: str = "lax"; break; + } + cfg_log(cfgst, "%s%s", str, is_default ? " [def]" : ""); +} + +static void pf_logcat(struct cfgst *cfgst, UNUSED_ARG(void *parent), UNUSED_ARG(struct cfgelem const * const cfgelem), UNUSED_ARG(int is_default)) +{ + logcat_t remaining = config.enabled_logcats; + char res[256] = "", *resp = res; + const char *prefix = ""; + size_t i; +#ifndef NDEBUG + { + size_t max; + for ( i = 0, max = 0; i < sizeof(logcat_codes) / sizeof(*logcat_codes); i++ ) + max += 1 + strlen(logcat_names[i]); + max += 11; /* ,0x%x */ + max += 1; /* \0 */ + assert(max <= sizeof(res)); + } +#endif + /* TRACE enables ALLCATS, all the others just one */ + if ( (remaining & LC_ALLCATS) == LC_ALLCATS ) { + resp += snprintf(resp, 256, "%strace", prefix); + remaining &= ~LC_ALLCATS; + prefix = ","; + } + for ( i = 0; i < sizeof(logcat_codes) / sizeof(*logcat_codes); i++ ) { + if ( remaining & logcat_codes[i] ) { + resp += snprintf(resp, 256, "%s%s", prefix, logcat_names[i]); + remaining &= ~logcat_codes[i]; + prefix = ","; + } + } + if ( remaining ) { + resp += snprintf(resp, 256, "%s0x%x", prefix, (unsigned) remaining); + } + assert(resp <= res + sizeof(res)); + /* can't do default indicator: user may have specified Verbosity, in + which case EnableCategory is at default, but for these two + settings, I don't mind. */ + cfg_log(cfgst, "%s", res); +} + + +static void print_configitems(struct cfgst *cfgst, void *parent, int isattr, struct cfgelem const * const cfgelem, int unchecked) +{ + const struct cfgelem *ce; + for ( ce = cfgelem; ce && ce->name; ce++ ) { + struct cfgst_nodekey key; + struct cfgst_node *n; + if ( ce->name[0] == '>' ) /* moved, so don't care */ + continue; + key.e = ce; + cfgst_push(cfgst, isattr, ce, parent); + if ( ce->multiplicity == 1 ) { + if ( (n = ut_avlLookup(&cfgst_found_treedef, &cfgst->found, &key)) != NULL ) { + cfgst_push(cfgst, 0, NULL, NULL); + ce->print(cfgst, parent, ce, n->is_default); + cfgst_pop(cfgst); + } else { + if ( unchecked && ce->print ) { + cfgst_push(cfgst, 0, NULL, NULL); + ce->print(cfgst, parent, ce, 0); + cfgst_pop(cfgst); + } + } + + if ( ce->children ) + print_configitems(cfgst, parent, 0, ce->children, unchecked); + if ( ce->attributes ) + print_configitems(cfgst, parent, 1, ce->attributes, unchecked); + } else { + struct config_listelem *p = cfg_deref_address(cfgst, parent, ce); + while ( p ) { + cfgst_push(cfgst, 0, NULL, NULL); + if ( ce->print ) { + ce->print(cfgst, p, ce, 0); + } + cfgst_pop(cfgst); + if ( ce->attributes ) + print_configitems(cfgst, p, 1, ce->attributes, 1); + if ( ce->children ) + print_configitems(cfgst, p, 0, ce->children, 1); + p = p->next; + } + } + cfgst_pop(cfgst); + } +} + + +static void free_all_elements(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem) +{ + const struct cfgelem *ce; + + for ( ce = cfgelem; ce && ce->name; ce++ ) { + if ( ce->name[0] == '>' ) /* moved, so don't care */ + continue; + + if ( ce->free ) + ce->free(cfgst, parent, ce); + + if ( ce->multiplicity == 1 ) { + if ( ce->children ) + free_all_elements(cfgst, parent, ce->children); + if ( ce->attributes ) + free_all_elements(cfgst, parent, ce->attributes); + } else { + struct config_listelem *p = cfg_deref_address(cfgst, parent, ce); + struct config_listelem *r; + while ( p ) { + if ( ce->attributes ) + free_all_elements(cfgst, p, ce->attributes); + if ( ce->children ) + free_all_elements(cfgst, p, ce->children); + r = p; + p = p->next; + os_free(r); + } + } + } +} + +static void free_configured_elements(struct cfgst *cfgst, void *parent, struct cfgelem const * const cfgelem) +{ + const struct cfgelem *ce; + for ( ce = cfgelem; ce && ce->name; ce++ ) { + struct cfgst_nodekey key; + struct cfgst_node *n; + if ( ce->name[0] == '>' ) /* moved, so don't care */ + continue; + key.e = ce; + if ( (n = ut_avlLookup(&cfgst_found_treedef, &cfgst->found, &key)) != NULL ) { + if ( ce->free && n->count > n->failed ) + ce->free(cfgst, parent, ce); + } + + if ( ce->multiplicity == 1 ) { + if ( ce->children ) + free_configured_elements(cfgst, parent, ce->children); + if ( ce->attributes ) + free_configured_elements(cfgst, parent, ce->attributes); + } else { + struct config_listelem *p = cfg_deref_address(cfgst, parent, ce); + struct config_listelem *r; + while ( p ) { + if ( ce->attributes ) + free_all_elements(cfgst, p, ce->attributes); + if ( ce->children ) + free_all_elements(cfgst, p, ce->children); + r = p; + p = p->next; + os_free(r); + } + } + } +} + +static int matching_name_index(const char *name_w_aliases, const char *name) +{ + const char *ns = name_w_aliases, *p = strchr(ns, '|'); + int idx = 0; + while ( p ) { + if ( os_strncasecmp(ns, name, (size_t) (p - ns)) == 0 && name[p - ns] == 0 ) { + /* ns upto the pipe symbol is a prefix of name, and name is terminated at that point */ + return idx; + } + /* If primary name followed by '||' instead of '|', aliases are non-warning */ + ns = p + 1 + (idx == 0 && p[1] == '|'); + p = strchr(ns, '|'); + idx++; + } + return (os_strcasecmp(ns, name) == 0) ? idx : -1; +} + +static const struct cfgelem *lookup_redirect(const char *target) +{ + const struct cfgelem *cfgelem = ddsi2_cfgelems; + char *target_copy = os_strdup(target), *p1; + const char *p = target_copy; + while ( p ) { + p1 = strchr(p, '/'); + if ( p1 ) *p1++ = 0; + for ( ; cfgelem->name; cfgelem++ ) { + /* not supporting multiple redirects */ + assert(cfgelem->name[0] != '>'); + if ( matching_name_index(cfgelem->name, p) >= 0 ) + break; + } + if ( p1 ) { + cfgelem = cfgelem->children; + } + p = p1; + } + os_free(target_copy); + return cfgelem; +} + +static int proc_elem_open(void *varg, UNUSED_ARG(uintptr_t parentinfo), UNUSED_ARG(uintptr_t *eleminfo), const char *name) +{ + struct cfgst * const cfgst = varg; + const struct cfgelem *cfgelem = cfgst_tos(cfgst); + const struct cfgelem *cfg_subelem; + int moved = 0; + if ( cfgelem == NULL ) { + /* Ignoring, but do track the structure so we can know when to stop ignoring */ + cfgst_push(cfgst, 0, NULL, NULL); + return 1; + } + for ( cfg_subelem = cfgelem->children; cfg_subelem && cfg_subelem->name && strcmp(cfg_subelem->name, "*") != 0; cfg_subelem++ ) { + const char *csename = cfg_subelem->name; + int idx; + moved = (csename[0] == '>'); + if ( moved ) + csename++; + idx = matching_name_index(csename, name); +#if WARN_DEPRECATED_ALIAS + if ( idx > 0 ) { + int n = (int) (strchr(csename, '|') - csename); + if ( csename[n + 1] != '|' ) + cfg_warning(cfgst, "'%s': deprecated alias for '%*.*s'", name, n, n, csename); + } +#endif + if ( idx >= 0 ) { + break; + } + } + if ( cfg_subelem == NULL || cfg_subelem->name == NULL ) + return cfg_error(cfgst, "%s: unknown element", name); + else if ( strcmp(cfg_subelem->name, "*") == 0 ) { + /* Push a marker that we are to ignore this part of the DOM tree */ + cfgst_push(cfgst, 0, NULL, NULL); + return 1; + } else { + void *parent, *dynparent; + + if ( moved ) { +#if WARN_DEPRECATED_ALIAS + cfg_warning(cfgst, "'%s': deprecated alias for '%s'", name, cfg_subelem->defvalue); +#endif + cfg_subelem = lookup_redirect(cfg_subelem->defvalue); + } + + parent = cfgst_parent(cfgst); + assert(cfgelem->init || cfgelem->multiplicity == 1); /*multi-items must have an init-func */ + if ( cfg_subelem->init ) { + if ( cfg_subelem->init(cfgst, parent, cfg_subelem) < 0 ) + return 0; + } + + if ( cfg_subelem->multiplicity != 1 ) + dynparent = cfg_deref_address(cfgst, parent, cfg_subelem); + else + dynparent = parent; + + cfgst_push(cfgst, 0, cfg_subelem, dynparent); + return 1; + } +} + +static int proc_attr(void *varg, UNUSED_ARG(uintptr_t eleminfo), const char *name, const char *value) +{ + /* All attributes are processed immediately after opening the element */ + struct cfgst * const cfgst = varg; + const struct cfgelem *cfgelem = cfgst_tos(cfgst); + const struct cfgelem *cfg_attr; + if ( cfgelem == NULL ) + return 1; + for ( cfg_attr = cfgelem->attributes; cfg_attr && cfg_attr->name; cfg_attr++ ) { + if ( os_strcasecmp(cfg_attr->name, name) == 0 ) + break; + } + if ( cfg_attr == NULL || cfg_attr->name == NULL ) + return cfg_error(cfgst, "%s: unknown attribute", name); + else { + void *parent = cfgst_parent(cfgst); + char *xvalue = ut_expand_envvars(value); + int ok; + cfgst_push(cfgst, 1, cfg_attr, parent); + ok = do_update(cfgst, cfg_attr->update, parent, cfg_attr, xvalue, 0); + cfgst_pop(cfgst); + os_free(xvalue); + return ok; + } +} + +static int proc_elem_data(void *varg, UNUSED_ARG(uintptr_t eleminfo), const char *value) +{ + struct cfgst * const cfgst = varg; + const struct cfgelem *cfgelem = cfgst_tos(cfgst); + if ( cfgelem == NULL ) + return 1; + if ( cfgelem->update == 0 ) + return cfg_error(cfgst, "%s: no data expected", value); + else { + void *parent = cfgst_parent(cfgst); + char *xvalue = ut_expand_envvars(value); + int ok; + cfgst_push(cfgst, 0, NULL, parent); + ok = do_update(cfgst, cfgelem->update, parent, cfgelem, xvalue, 0); + cfgst_pop(cfgst); + os_free(xvalue); + return ok; + } +} + +static int proc_elem_close(void *varg, UNUSED_ARG(uintptr_t eleminfo)) +{ + struct cfgst * const cfgst = varg; + const struct cfgelem * cfgelem = cfgst_tos(cfgst); + int ok = 1; + if ( cfgelem && cfgelem->multiplicity != 1 ) { + void *parent = cfgst_parent(cfgst); + int ok1; + ok1 = set_defaults(cfgst, parent, 1, cfgelem->attributes, 1); + ok = ok && ok1; + ok1 = set_defaults(cfgst, parent, 0, cfgelem->children, 1); + ok = ok && ok1; + } + cfgst_pop(cfgst); + return ok; +} + +static void proc_error(void *varg, const char *msg, int line) +{ + struct cfgst * const cfgst = varg; + cfg_error(cfgst, "parser error %s at line %d", msg, line); +} + + +static int cfgst_node_cmp(const void *va, const void *vb) +{ + return memcmp(va, vb, sizeof(struct cfgst_nodekey)); +} + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS +static int set_default_channel(struct config *cfg) +{ + if ( cfg->channels == NULL ) { + /* create one default channel if none configured */ + struct config_channel_listelem *c; + if ( (c = os_malloc(sizeof(*c))) == NULL ) + return ERR_OUT_OF_MEMORY; + c->next = NULL; + c->name = os_strdup("user"); + c->priority = 0; + c->resolution = 1 * T_MILLISECOND; +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING + c->data_bandwidth_limit = 0; + c->auxiliary_bandwidth_limit = 0; +#endif + c->diffserv_field = 0; + c->channel_reader_ts = NULL; + c->queueId = 0; + c->dqueue = NULL; + c->evq = NULL; + c->transmit_conn = NULL; + cfg->channels = c; + } + return 0; +} + +static int sort_channels_cmp(const void *va, const void *vb) +{ + const struct config_channel_listelem * const *a = va; + const struct config_channel_listelem * const *b = vb; + return ((*a)->priority == (*b)->priority) ? 0 : ((*a)->priority < (*b)->priority) ? -1 : 1; +} + +static int sort_channels_check_nodups(struct config *cfg) +{ + /* Selecting a channel is much easier & more elegant if the channels + are sorted on descending priority. While we do retain the list + structure, sorting is much easier in an array, and hence we + convert back and forth. */ + struct config_channel_listelem **ary, *c; + unsigned i, n; + int result; + + n = 0; + for ( c = cfg->channels; c; c = c->next ) + n++; + assert(n > 0); + + ary = os_malloc(n * sizeof(*ary)); + + i = 0; + for ( c = cfg->channels; c; c = c->next ) + ary[i++] = c; + qsort(ary, n, sizeof(*ary), sort_channels_cmp); + + result = 0; + for ( i = 0; i < n - 1; i++ ) { + if ( ary[i]->priority == ary[i + 1]->priority ) { + NN_ERROR("config: duplicate channel definition for priority %u: channels %s and %s\n", + ary[i]->priority, ary[i]->name, ary[i + 1]->name); + result = ERR_ENTITY_EXISTS; + } + } + + if ( result == 0 ) { + cfg->channels = ary[0]; + for ( i = 0; i < n - 1; i++ ) + ary[i]->next = ary[i + 1]; + ary[i]->next = NULL; + cfg->max_channel = ary[i]; + } + + os_free(ary); + return result; +} +#endif /* DDSI_INCLUDE_NETWORK_CHANNELS */ + +struct cfgst * config_init +( + _In_opt_ const char *configfile +) +{ + int ok = 1; + struct cfgst *cfgst; + + memset(&config, 0, sizeof(config)); + + config.tracingOutputFile = stderr; + config.enabled_logcats = LC_ERROR | LC_WARNING; + + cfgst = os_malloc(sizeof(*cfgst)); + memset(cfgst, 0, sizeof(*cfgst)); + + ut_avlInit(&cfgst_found_treedef, &cfgst->found); + cfgst->cfg = &config; + + /* configfile == NULL will get you the default configuration */ + if ( configfile ) { + char *copy = os_strdup(configfile), *cursor = copy, *tok; + while ( (tok = os_strsep(&cursor, ",")) != NULL ) { + struct ut_xmlpCallbacks cb; + struct ut_xmlpState *qx; + FILE *fp; + + if ( (fp = fopen(tok, "r")) == NULL ) { + if ( strncmp(tok, "file://", 7) != 0 || (fp = fopen(tok + 7, "r")) == NULL ) { + NN_ERROR("can't open configuration file %s\n", tok); + os_free(copy); + os_free(cfgst); + return NULL; + } + } + + cb.attr = proc_attr; + cb.elem_close = proc_elem_close; + cb.elem_data = proc_elem_data; + cb.elem_open = proc_elem_open; + cb.error = proc_error; + + if ( (qx = ut_xmlpNewFile(fp, cfgst, &cb)) == NULL ) { + fclose(fp); + os_free(copy); + os_free(cfgst); + return NULL; + } + cfgst_push(cfgst, 0, &root_cfgelem, &config); + + ok = (ut_xmlpParse(qx) >= 0); + /* Pop until stack empty: error handling is rather brutal */ + assert(!ok || cfgst->path_depth == 1); + while ( cfgst->path_depth > 0 ) + cfgst_pop(cfgst); + ut_xmlpFree(qx); + fclose(fp); + } + os_free(copy); + } + + /* Set defaults for everything not set that we have a default value + for, signal errors for things unset but without a default. */ + { + int ok1 = set_defaults(cfgst, cfgst->cfg, 0, root_cfgelems, 0); + ok = ok && ok1; + } + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + /* Default channel gets set outside set_defaults -- a bit too + complicated for the poor framework */ + + if ( set_default_channel(cfgst->cfg) < 0 ) { + ok = 0; + } + if ( cfgst->cfg->channels && sort_channels_check_nodups(cfgst->cfg) < 0 ) { + ok = 0; + } +#endif + +#ifdef DDSI_INCLUDE_ENCRYPTION + /* Check security profiles */ + { + struct config_securityprofile_listelem *s = config.securityProfiles; + while ( s ) { + switch ( s->cipher ) { + case Q_CIPHER_UNDEFINED: + case Q_CIPHER_NULL: + /* nop */ + if ( s->key && strlen(s->key) > 0 ) { + NN_ERROR("config: DDSI2Service/Security/SecurityProfile[@cipherkey]: %s: cipher key not required\n", s->key); + } + break; + + default: + /* read the cipherkey if present */ + if ( !s->key || strlen(s->key) == 0 ) { + NN_ERROR("config: DDSI2Service/Security/SecurityProfile[@cipherkey]: cipher key missing\n"); + ok = 0; + } else if ( q_security_plugin.valid_uri && !(q_security_plugin.valid_uri) (s->cipher, s->key) ) { + NN_ERROR("config: DDSI2Service/Security/SecurityProfile[@cipherkey]: %s : incorrect key\n", s->key); + ok = 0; + } + } + s = s->next; + } + } +#endif /* DDSI_INCLUDE_ENCRYPTION */ + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + /* Assign network partition ids */ +#ifdef DDSI_INCLUDE_ENCRYPTION + /* also create links from the network partitions to the + securityProfiles and signal errors if profiles do not exist */ +#endif /* DDSI_INCLUDE_ENCRYPTION */ + { + struct config_networkpartition_listelem *p = config.networkPartitions; + config.nof_networkPartitions = 0; + while ( p ) { +#ifdef DDSI_INCLUDE_ENCRYPTION + if ( os_strcasecmp(p->profileName, "null") == 0 ) + p->securityProfile = NULL; + else { + struct config_securityprofile_listelem *s = config.securityProfiles; + while ( s && os_strcasecmp(p->profileName, s->name) != 0 ) + s = s->next; + if ( s ) + p->securityProfile = s; + else { + NN_ERROR("config: DDSI2Service/Partitioning/NetworkPartitions/NetworkPartition[@securityprofile]: %s: unknown securityprofile\n", p->profileName); + ok = 0; + } + } +#endif /* DDSI_INCLUDE_ENCRYPTION */ + config.nof_networkPartitions++; + /* also use crc32 just like native nw and ordinary ddsi2e, only + for interoperability because it is asking for trouble & + forces us to include a crc32 routine when we have md5 + available anyway */ + p->partitionId = config.nof_networkPartitions; /* starting at 1 */ + p->partitionHash = crc32_calc(p->name, (int) strlen(p->name)); + p = p->next; + } + } + + /* Create links from the partitionmappings to the network partitions + and signal errors if partitions do not exist */ + { + struct config_partitionmapping_listelem * m = config.partitionMappings; + while ( m ) { + struct config_networkpartition_listelem * p = config.networkPartitions; + while ( p && os_strcasecmp(m->networkPartition, p->name) != 0 ) { + p = p->next; + } + if ( p ) { + m->partition = p; + } else { + NN_ERROR("config: DDSI2Service/Partitioning/PartitionMappings/PartitionMapping[@networkpartition]: %s: unknown partition\n", m->networkPartition); + ok = 0; + } + m = m->next; + } + } +#endif /* DDSI_INCLUDE_NETWORK_PARTITIONS */ + + /* Now switch to configured tracing settings */ + config.enabled_logcats = enabled_logcats; + + if ( !ok ) { + free_configured_elements(cfgst, cfgst->cfg, root_cfgelems); + } + + if ( ok ) { + config.valid = 1; + return cfgst; + } else { + ut_avlFree(&cfgst_found_treedef, &cfgst->found, os_free); + os_free(cfgst); + return NULL; + } +} + +void config_print_cfgst(_In_ struct cfgst *cfgst) +{ + if ( cfgst == NULL ) + return; + print_configitems(cfgst, cfgst->cfg, 0, root_cfgelems, 0); +} + +void config_fini(_In_ struct cfgst *cfgst) +{ + assert(cfgst); + assert(cfgst->cfg == &config); + assert(config.valid); + + free_all_elements(cfgst, cfgst->cfg, root_cfgelems); + if ( config.tracingOutputFile ) { + fclose(config.tracingOutputFile); + } + memset(&config, 0, sizeof(config)); + config.valid = 0; + + ut_avlFree(&cfgst_found_treedef, &cfgst->found, os_free); + os_free(cfgst); +} + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS +static char *get_partition_search_pattern(const char *partition, const char *topic) +{ + char *pt = os_malloc(strlen(partition) + strlen(topic) + 2); + os_sprintf(pt, "%s.%s", partition, topic); + return pt; +} + +struct config_partitionmapping_listelem *find_partitionmapping(const char *partition, const char *topic) +{ + char *pt = get_partition_search_pattern(partition, topic); + struct config_partitionmapping_listelem *pm; + for ( pm = config.partitionMappings; pm; pm = pm->next ) + if ( WildcardOverlap(pt, pm->DCPSPartitionTopic) ) + break; + os_free(pt); + return pm; +} + +struct config_networkpartition_listelem *find_networkpartition_by_id(uint32_t id) +{ + struct config_networkpartition_listelem *np; + for ( np = config.networkPartitions; np; np = np->next ) + if ( np->partitionId == id ) + return np; + return 0; +} + +int is_ignored_partition(const char *partition, const char *topic) +{ + char *pt = get_partition_search_pattern(partition, topic); + struct config_ignoredpartition_listelem *ip; + for ( ip = config.ignoredPartitions; ip; ip = ip->next ) + if ( WildcardOverlap(pt, ip->DCPSPartitionTopic) ) + break; + os_free(pt); + return ip != NULL; +} +#endif /* DDSI_INCLUDE_NETWORK_PARTITIONS */ + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS +struct config_channel_listelem *find_channel(nn_transport_priority_qospolicy_t transport_priority) +{ + struct config_channel_listelem *c; + /* Channel selection is to use the channel with the lowest priority + not less than transport_priority, or else the one with the + highest priority. */ + assert(config.channels != NULL); + assert(config.max_channel != NULL); + for ( c = config.channels; c; c = c->next ) { + assert(c->next == NULL || c->next->priority > c->priority); + if ( transport_priority.value <= c->priority ) + return c; + } + return config.max_channel; +} +#endif /* DDSI_INCLUDE_NETWORK_CHANNELS */ diff --git a/src/core/ddsi/src/q_ddsi_discovery.c b/src/core/ddsi/src/q_ddsi_discovery.c new file mode 100644 index 0000000..f435f4c --- /dev/null +++ b/src/core/ddsi/src/q_ddsi_discovery.c @@ -0,0 +1,1996 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include +#include + +#include "os/os.h" + +#include "util/ut_avl.h" +#include "ddsi/ddsi_ser.h" +#include "ddsi/q_protocol.h" +#include "ddsi/q_rtps.h" +#include "ddsi/q_misc.h" +#include "ddsi/q_config.h" +#include "ddsi/q_log.h" +#include "ddsi/q_plist.h" +#include "ddsi/q_unused.h" +#include "ddsi/q_xevent.h" +#include "ddsi/q_addrset.h" +#include "ddsi/q_ddsi_discovery.h" +#include "ddsi/q_radmin.h" +#include "ddsi/q_ephash.h" +#include "ddsi/q_entity.h" +#include "ddsi/q_globals.h" +#include "ddsi/q_xmsg.h" +#include "ddsi/q_bswap.h" +#include "ddsi/q_transmit.h" +#include "ddsi/q_lease.h" +#include "ddsi/q_error.h" +#include "ddsi/q_builtin_topic.h" +#include "q__osplser.h" +#include "ddsi/q_md5.h" +#include "ddsi/q_feature_check.h" + +#include "ddsi/sysdeps.h" + +static const nn_vendorid_t ownvendorid = MY_VENDOR_ID; + +static int get_locator (nn_locator_t *loc, const nn_locators_t *locs, int uc_same_subnet) +{ + struct nn_locators_one *l; + nn_locator_t first, samenet; + int first_set = 0, samenet_set = 0; + memset (&first, 0, sizeof (first)); + memset (&samenet, 0, sizeof (samenet)); + + /* Special case UDPv4 MC address generators - there is a bit of an type mismatch between an address generator (i.e., a set of addresses) and an address ... Whoever uses them is supposed to know that that is what he wants, so we simply given them priority. */ + if (!config.tcp_enable && !config.useIpv6) + { + for (l = locs->first; l != NULL; l = l->next) + { + if (l->loc.kind == NN_LOCATOR_KIND_UDPv4MCGEN) + { + *loc = l->loc; + return 1; + } + } + } + + /* Preferably an (the first) address that matches a network we are + on; if none does, pick the first. No multicast locator ever will + match, so the first one will be used. */ + for (l = locs->first; l != NULL; l = l->next) + { + os_sockaddr_storage tmp; + int i; + + /* Skip locators of the wrong kind */ + + if (! ddsi_factory_supports (gv.m_factory, l->loc.kind)) + { + continue; + } + + nn_loc_to_address (&tmp, &l->loc); + + if (l->loc.kind == NN_LOCATOR_KIND_UDPv4 && gv.extmask.s_addr != 0) + { + /* If the examined locator is in the same subnet as our own + external IP address, this locator will be translated into one + in the same subnet as our own local ip and selected. */ + os_sockaddr_in *tmp4 = (os_sockaddr_in *) &tmp; + const os_sockaddr_in *ownip = (os_sockaddr_in *) &gv.ownip; + const os_sockaddr_in *extip = (os_sockaddr_in *) &gv.extip; + + if ((tmp4->sin_addr.s_addr & gv.extmask.s_addr) == (extip->sin_addr.s_addr & gv.extmask.s_addr)) + { + /* translate network part of the IP address from the external + one to the internal one */ + tmp4->sin_addr.s_addr = + (tmp4->sin_addr.s_addr & ~gv.extmask.s_addr) | (ownip->sin_addr.s_addr & gv.extmask.s_addr); + nn_address_to_loc (loc, &tmp, l->loc.kind); + return 1; + } + } + +#if OS_SOCKET_HAS_IPV6 + if ((l->loc.kind == NN_LOCATOR_KIND_UDPv6) || (l->loc.kind == NN_LOCATOR_KIND_TCPv6)) + { + /* We (cowardly) refuse to accept advertised link-local + addresses unles we're in "link-local" mode ourselves. Then + we just hope for the best. */ + if (!gv.ipv6_link_local && IN6_IS_ADDR_LINKLOCAL (&((os_sockaddr_in6 *) &tmp)->sin6_addr)) + continue; + } +#endif + + if (!first_set) + { + first = l->loc; + first_set = 1; + } + for (i = 0; i < gv.n_interfaces; i++) + { + if (os_sockaddrSameSubnet ((os_sockaddr *) &tmp, (os_sockaddr *) &gv.interfaces[i].addr, (os_sockaddr *) &gv.interfaces[i].netmask)) + { + if (os_sockaddrIPAddressEqual ((os_sockaddr*) &gv.interfaces[i].addr, (os_sockaddr*) &gv.ownip)) + { + /* matches the preferred interface -> the very best situation */ + *loc = l->loc; + return 1; + } + else if (!samenet_set) + { + /* on a network we're connected to */ + samenet = l->loc; + samenet_set = 1; + } + } + } + } + if (!uc_same_subnet) + { + if (samenet_set) + { + /* prefer a directly connected network */ + *loc = samenet; + return 1; + } + else if (first_set) + { + /* else any address we found will have to do */ + *loc = first; + return 1; + } + } + return 0; +} + +/****************************************************************************** + *** + *** SPDP + *** + *****************************************************************************/ + +static void maybe_add_pp_as_meta_to_as_disc (const struct addrset *as_meta) +{ + if (addrset_empty_mc (as_meta)) + { + nn_locator_t loc; + if (addrset_any_uc (as_meta, &loc)) + { + add_to_addrset (gv.as_disc, &loc); + } + } +} + +int spdp_write (struct participant *pp) +{ + static const nn_vendorid_t myvendorid = MY_VENDOR_ID; + serdata_t serdata; + serstate_t serstate; + struct nn_xmsg *mpayload; + size_t payload_sz; + char *payload_blob; + struct nn_locators_one def_uni_loc_one, def_multi_loc_one, meta_uni_loc_one, meta_multi_loc_one; + nn_plist_t ps; + nn_guid_t kh; + struct writer *wr; + size_t size; + char node[64]; + uint64_t qosdiff; + + if (pp->e.onlylocal) { + /* This topic is only locally available. */ + return 0; + } + + propagate_builtin_topic_participant(&(pp->e), pp->plist, now(), true); + + TRACE (("spdp_write(%x:%x:%x:%x)\n", PGUID (pp->e.guid))); + + if ((wr = get_builtin_writer (pp, NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER)) == NULL) + { + TRACE (("spdp_write(%x:%x:%x:%x) - builtin participant writer not found\n", PGUID (pp->e.guid))); + return 0; + } + + /* First create a fake message for the payload: we can add plists to + xmsgs easily, but not to serdata. But it is rather easy to copy + the payload of an xmsg over to a serdata ... Expected size isn't + terribly important, the msg will grow as needed, address space is + essentially meaningless because we only use the message to + construct the payload. */ + mpayload = nn_xmsg_new (gv.xmsgpool, &pp->e.guid.prefix, 0, NN_XMSG_KIND_DATA); + + nn_plist_init_empty (&ps); + ps.present |= PP_PARTICIPANT_GUID | PP_BUILTIN_ENDPOINT_SET | + PP_PROTOCOL_VERSION | PP_VENDORID | PP_PARTICIPANT_LEASE_DURATION; + ps.participant_guid = pp->e.guid; + ps.builtin_endpoint_set = pp->bes; + ps.protocol_version.major = RTPS_MAJOR; + ps.protocol_version.minor = RTPS_MINOR; + ps.vendorid = myvendorid; + if (pp->prismtech_bes) + { + ps.present |= PP_PRISMTECH_BUILTIN_ENDPOINT_SET; + ps.prismtech_builtin_endpoint_set = pp->prismtech_bes; + } + ps.default_unicast_locators.n = 1; + ps.default_unicast_locators.first = + ps.default_unicast_locators.last = &def_uni_loc_one; + ps.metatraffic_unicast_locators.n = 1; + ps.metatraffic_unicast_locators.first = + ps.metatraffic_unicast_locators.last = &meta_uni_loc_one; + def_uni_loc_one.next = NULL; + meta_uni_loc_one.next = NULL; + + if (config.many_sockets_mode) + { + def_uni_loc_one.loc = pp->m_locator; + meta_uni_loc_one.loc = pp->m_locator; + } + else + { + def_uni_loc_one.loc = gv.loc_default_uc; + meta_uni_loc_one.loc = gv.loc_meta_uc; + } + + if (config.publish_uc_locators) + { + ps.present |= PP_DEFAULT_UNICAST_LOCATOR | PP_METATRAFFIC_UNICAST_LOCATOR; + ps.aliased |= PP_DEFAULT_UNICAST_LOCATOR | PP_METATRAFFIC_UNICAST_LOCATOR; + } + + if (config.allowMulticast) + { + int include = 0; +#ifdef DDSI_INCLUDE_SSM + /* Note that if the default multicast address is an SSM address, + we will simply advertise it. The recipients better understand + it means the writers will publish to address and the readers + favour SSM. */ + if (is_ssm_mcaddr (&gv.loc_default_mc)) + include = (config.allowMulticast & AMC_SSM) != 0; + else + include = (config.allowMulticast & AMC_ASM) != 0; +#else + if (config.allowMulticast & AMC_ASM) + include = 1; +#endif + if (include) + { + ps.present |= PP_DEFAULT_MULTICAST_LOCATOR | PP_METATRAFFIC_MULTICAST_LOCATOR; + ps.aliased |= PP_DEFAULT_MULTICAST_LOCATOR | PP_METATRAFFIC_MULTICAST_LOCATOR; + ps.default_multicast_locators.n = 1; + ps.default_multicast_locators.first = + ps.default_multicast_locators.last = &def_multi_loc_one; + ps.metatraffic_multicast_locators.n = 1; + ps.metatraffic_multicast_locators.first = + ps.metatraffic_multicast_locators.last = &meta_multi_loc_one; + def_multi_loc_one.next = NULL; + def_multi_loc_one.loc = gv.loc_default_mc; + meta_multi_loc_one.next = NULL; + meta_multi_loc_one.loc = gv.loc_meta_mc; + } + } + ps.participant_lease_duration = nn_to_ddsi_duration (pp->lease_duration); + + /* Add PrismTech specific version information */ + { + ps.present |= PP_PRISMTECH_PARTICIPANT_VERSION_INFO; + ps.prismtech_participant_version_info.version = 0; + ps.prismtech_participant_version_info.flags = + NN_PRISMTECH_FL_DDSI2_PARTICIPANT_FLAG | + NN_PRISMTECH_FL_PTBES_FIXED_0 | + NN_PRISMTECH_FL_SUPPORTS_STATUSINFOX; + if (config.besmode == BESMODE_MINIMAL) + ps.prismtech_participant_version_info.flags |= NN_PRISMTECH_FL_MINIMAL_BES_MODE; + os_mutexLock (&gv.privileged_pp_lock); + if (pp->is_ddsi2_pp) + ps.prismtech_participant_version_info.flags |= NN_PRISMTECH_FL_PARTICIPANT_IS_DDSI2; + os_mutexUnlock (&gv.privileged_pp_lock); + + os_gethostname(node, sizeof(node)-1); + node[sizeof(node)-1] = '\0'; + size = strlen(node) + strlen(OSPL_VERSION_STR) + strlen(OSPL_HOST_STR) + strlen(OSPL_TARGET_STR) + 4; /* + ///'\0' */ + ps.prismtech_participant_version_info.internals = os_malloc(size); + (void) snprintf(ps.prismtech_participant_version_info.internals, size, "%s/%s/%s/%s", node, OSPL_VERSION_STR, OSPL_HOST_STR, OSPL_TARGET_STR); + TRACE (("spdp_write(%x:%x:%x:%x) - internals: %s\n", PGUID (pp->e.guid), ps.prismtech_participant_version_info.internals)); + } + + /* Participant QoS's insofar as they are set, different from the default, and mapped to the SPDP data, rather than to the PrismTech-specific CMParticipant endpoint. Currently, that means just USER_DATA. */ + qosdiff = nn_xqos_delta (&pp->plist->qos, &gv.default_plist_pp.qos, QP_USER_DATA); + if (config.explicitly_publish_qos_set_to_default) + qosdiff |= ~QP_UNRECOGNIZED_INCOMPATIBLE_MASK; + + assert (ps.qos.present == 0); + nn_plist_addtomsg (mpayload, &ps, ~(uint64_t)0, 0); + nn_plist_addtomsg (mpayload, pp->plist, 0, qosdiff); + nn_xmsg_addpar_sentinel (mpayload); + + /* A NULL topic implies a parameter list, now that we do PMD through + the serializer */ + serstate = ddsi_serstate_new (gv.serpool, NULL); + payload_blob = nn_xmsg_payload (&payload_sz, mpayload); + ddsi_serstate_append_blob (serstate, 4, payload_sz, payload_blob); + kh = nn_hton_guid (pp->e.guid); + serstate_set_key (serstate, 0, &kh); + ddsi_serstate_set_msginfo (serstate, 0, now (), NULL); + serdata = ddsi_serstate_fix (serstate); + nn_plist_fini(&ps); + nn_xmsg_free (mpayload); + + return write_sample_nogc_notk (NULL, wr, serdata); +} + +int spdp_dispose_unregister (struct participant *pp) +{ + struct nn_xmsg *mpayload; + size_t payload_sz; + char *payload_blob; + nn_plist_t ps; + serdata_t serdata; + serstate_t serstate; + nn_guid_t kh; + struct writer *wr; + + if ((wr = get_builtin_writer (pp, NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER)) == NULL) + { + TRACE (("spdp_dispose_unregister(%x:%x:%x:%x) - builtin participant writer not found\n", PGUID (pp->e.guid))); + return 0; + } + + mpayload = nn_xmsg_new (gv.xmsgpool, &pp->e.guid.prefix, 0, NN_XMSG_KIND_DATA); + nn_plist_init_empty (&ps); + ps.present |= PP_PARTICIPANT_GUID; + ps.participant_guid = pp->e.guid; + nn_plist_addtomsg (mpayload, &ps, ~(uint64_t)0, ~(uint64_t)0); + nn_xmsg_addpar_sentinel (mpayload); + + serstate = ddsi_serstate_new (gv.serpool, NULL); + payload_blob = nn_xmsg_payload (&payload_sz, mpayload); + ddsi_serstate_append_blob (serstate, 4, payload_sz, payload_blob); + kh = nn_hton_guid (pp->e.guid); + serstate_set_key (serstate, 1, &kh); + ddsi_serstate_set_msginfo (serstate, NN_STATUSINFO_DISPOSE | NN_STATUSINFO_UNREGISTER, now (), NULL); + serdata = ddsi_serstate_fix (serstate); + nn_xmsg_free (mpayload); + + return write_sample_nogc_notk (NULL, wr, serdata); +} + +static unsigned pseudo_random_delay (const nn_guid_t *x, const nn_guid_t *y, nn_mtime_t tnow) +{ + /* You know, an ordinary random generator would be even better, but + the C library doesn't have a reentrant one and I don't feel like + integrating, say, the Mersenne Twister right now. */ + static const uint64_t cs[] = { + UINT64_C (15385148050874689571), + UINT64_C (17503036526311582379), + UINT64_C (11075621958654396447), + UINT64_C ( 9748227842331024047), + UINT64_C (14689485562394710107), + UINT64_C (17256284993973210745), + UINT64_C ( 9288286355086959209), + UINT64_C (17718429552426935775), + UINT64_C (10054290541876311021), + UINT64_C (13417933704571658407) + }; + uint32_t a = x->prefix.u[0], b = x->prefix.u[1], c = x->prefix.u[2], d = x->entityid.u; + uint32_t e = y->prefix.u[0], f = y->prefix.u[1], g = y->prefix.u[2], h = y->entityid.u; + uint32_t i = (uint32_t) ((uint64_t) tnow.v >> 32), j = (uint32_t) tnow.v; + uint64_t m = 0; + m += (a + cs[0]) * (b + cs[1]); + m += (c + cs[2]) * (d + cs[3]); + m += (e + cs[4]) * (f + cs[5]); + m += (g + cs[6]) * (h + cs[7]); + m += (i + cs[8]) * (j + cs[9]); + return (unsigned) (m >> 32); +} + +static void respond_to_spdp (const nn_guid_t *dest_proxypp_guid) +{ + struct ephash_enum_participant est; + struct participant *pp; + nn_mtime_t tnow = now_mt (); + ephash_enum_participant_init (&est); + while ((pp = ephash_enum_participant_next (&est)) != NULL) + { + /* delay_base has 32 bits, so delay_norm is approximately 1s max; + delay_max <= 1s by config checks */ + unsigned delay_base = pseudo_random_delay (&pp->e.guid, dest_proxypp_guid, tnow); + unsigned delay_norm = delay_base >> 2; + int64_t delay_max_ms = config.spdp_response_delay_max / 1000000; + int64_t delay = (int64_t) delay_norm * delay_max_ms / 1000; + nn_mtime_t tsched = add_duration_to_mtime (tnow, delay); + TRACE ((" %"PRId64, delay)); + if (!config.unicast_response_to_spdp_messages) + /* pp can't reach gc_delete_participant => can safely reschedule */ + resched_xevent_if_earlier (pp->spdp_xevent, tsched); + else + qxev_spdp (tsched, &pp->e.guid, dest_proxypp_guid); + } + ephash_enum_participant_fini (&est); +} + +static int handle_SPDP_dead (const struct receiver_state *rst, nn_wctime_t timestamp, const nn_plist_t *datap, unsigned statusinfo) +{ + nn_guid_t guid; + + if (!(config.enabled_logcats & LC_TRACE)) + nn_log (LC_DISCOVERY, "SPDP ST%x", statusinfo); + + if (datap->present & PP_PARTICIPANT_GUID) + { + guid = datap->participant_guid; + nn_log (LC_DISCOVERY, " %x:%x:%x:%x", PGUID (guid)); + assert (guid.entityid.u == NN_ENTITYID_PARTICIPANT); + if (delete_proxy_participant_by_guid (&guid, timestamp, 0) < 0) + { + nn_log (LC_DISCOVERY, " unknown"); + } + else + { + nn_log (LC_DISCOVERY, " delete"); + } + } + else + { + NN_WARNING ("data (SPDP, vendor %u.%u): no/invalid payload\n", rst->vendor.id[0], rst->vendor.id[1]); + } + return 1; +} + +static void allowmulticast_aware_add_to_addrset (struct addrset *as, const nn_locator_t *loc) +{ +#if DDSI_INCLUDE_SSM + if (is_ssm_mcaddr (loc)) + { + if (!(config.allowMulticast & AMC_SSM)) + return; + } + else if (is_mcaddr (loc)) + { + if (!(config.allowMulticast & AMC_ASM)) + return; + } +#else + if (is_mcaddr (loc) && !(config.allowMulticast & AMC_ASM)) + return; +#endif + add_to_addrset (as, loc); +} + +static struct proxy_participant *find_ddsi2_proxy_participant (const nn_guid_t *ppguid) +{ + struct ephash_enum_proxy_participant it; + struct proxy_participant *pp; + ephash_enum_proxy_participant_init (&it); + while ((pp = ephash_enum_proxy_participant_next (&it)) != NULL) + { + if (vendor_is_opensplice (pp->vendor) && pp->e.guid.prefix.u[0] == ppguid->prefix.u[0] && pp->is_ddsi2_pp) + break; + } + ephash_enum_proxy_participant_fini (&it); + return pp; +} + +static void make_participants_dependent_on_ddsi2 (const nn_guid_t *ddsi2guid, nn_wctime_t timestamp) +{ + struct ephash_enum_proxy_participant it; + struct proxy_participant *pp, *d2pp; + struct lease *d2pp_lease; + if ((d2pp = ephash_lookup_proxy_participant_guid (ddsi2guid)) == NULL) + return; + d2pp_lease = os_atomic_ldvoidp (&d2pp->lease); + ephash_enum_proxy_participant_init (&it); + while ((pp = ephash_enum_proxy_participant_next (&it)) != NULL) + { + if (vendor_is_opensplice (pp->vendor) && pp->e.guid.prefix.u[0] == ddsi2guid->prefix.u[0] && !pp->is_ddsi2_pp) + { + TRACE (("proxy participant %x:%x:%x:%x depends on ddsi2 %x:%x:%x:%x", PGUID (pp->e.guid), PGUID (*ddsi2guid))); + os_mutexLock (&pp->e.lock); + pp->privileged_pp_guid = *ddsi2guid; + os_mutexUnlock (&pp->e.lock); + proxy_participant_reassign_lease (pp, d2pp_lease); + TRACE (("\n")); + + if (ephash_lookup_proxy_participant_guid (ddsi2guid) == NULL) + { + /* If DDSI2 has been deleted here (i.e., very soon after + having been created), we don't know whether pp will be + deleted */ + break; + } + } + } + ephash_enum_proxy_participant_fini (&it); + + if (pp != NULL) + { + TRACE (("make_participants_dependent_on_ddsi2: ddsi2 %x:%x:%x:%x is no more, delete %x:%x:%x:%x\n", PGUID (*ddsi2guid), PGUID (pp->e.guid))); + delete_proxy_participant_by_guid (&pp->e.guid, timestamp, 1); + } +} + +static int handle_SPDP_alive (const struct receiver_state *rst, nn_wctime_t timestamp, const nn_plist_t *datap) +{ + const unsigned bes_sedp_announcer_mask = + NN_DISC_BUILTIN_ENDPOINT_SUBSCRIPTION_ANNOUNCER | + NN_DISC_BUILTIN_ENDPOINT_PUBLICATION_ANNOUNCER; + struct addrset *as_meta, *as_default; + struct proxy_participant *proxypp; + unsigned builtin_endpoint_set; + unsigned prismtech_builtin_endpoint_set; + nn_guid_t privileged_pp_guid; + nn_duration_t lease_duration; + unsigned custom_flags = 0; + + if (!(config.enabled_logcats & LC_TRACE)) + nn_log (LC_DISCOVERY, "SPDP ST0"); + + if (!(datap->present & PP_PARTICIPANT_GUID) || !(datap->present & PP_BUILTIN_ENDPOINT_SET)) + { + NN_WARNING ("data (SPDP, vendor %u.%u): no/invalid payload\n", rst->vendor.id[0], rst->vendor.id[1]); + return 1; + } + + /* At some point the RTI implementation didn't mention + BUILTIN_ENDPOINT_PARTICIPANT_MESSAGE_DATA_READER & ...WRITER, or + so it seemed; and yet they are necessary for correct operation, + so add them. */ + builtin_endpoint_set = datap->builtin_endpoint_set; + prismtech_builtin_endpoint_set = (datap->present & PP_PRISMTECH_BUILTIN_ENDPOINT_SET) ? datap->prismtech_builtin_endpoint_set : 0; + if (vendor_is_rti (rst->vendor) && + ((builtin_endpoint_set & + (NN_BUILTIN_ENDPOINT_PARTICIPANT_MESSAGE_DATA_READER | + NN_BUILTIN_ENDPOINT_PARTICIPANT_MESSAGE_DATA_WRITER)) + != (NN_BUILTIN_ENDPOINT_PARTICIPANT_MESSAGE_DATA_READER | + NN_BUILTIN_ENDPOINT_PARTICIPANT_MESSAGE_DATA_WRITER)) && + config.assume_rti_has_pmd_endpoints) + { + NN_WARNING ("data (SPDP, vendor %u.%u): assuming unadvertised PMD endpoints do exist\n", + rst->vendor.id[0], rst->vendor.id[1]); + builtin_endpoint_set |= + NN_BUILTIN_ENDPOINT_PARTICIPANT_MESSAGE_DATA_READER | + NN_BUILTIN_ENDPOINT_PARTICIPANT_MESSAGE_DATA_WRITER; + } + if ((datap->present & PP_PRISMTECH_PARTICIPANT_VERSION_INFO) && + (datap->present & PP_PRISMTECH_BUILTIN_ENDPOINT_SET) && + !(datap->prismtech_participant_version_info.flags & NN_PRISMTECH_FL_PTBES_FIXED_0)) + { + /* FIXED_0 (bug 0) indicates that this is an updated version that advertises + CM readers/writers correctly (without it, we could make a reasonable guess, + but it would cause problems with cases where we would be happy with only + (say) CM participant. Have to do a backwards-compatible fix because it has + already been released with the flags all aliased to bits 0 and 1 ... */ + nn_log (LC_DISCOVERY, " (ptbes_fixed_0 %x)", prismtech_builtin_endpoint_set); + if (prismtech_builtin_endpoint_set & NN_DISC_BUILTIN_ENDPOINT_CM_PARTICIPANT_READER) + prismtech_builtin_endpoint_set |= NN_DISC_BUILTIN_ENDPOINT_CM_PUBLISHER_READER | NN_DISC_BUILTIN_ENDPOINT_CM_SUBSCRIBER_READER; + if (prismtech_builtin_endpoint_set & NN_DISC_BUILTIN_ENDPOINT_CM_PARTICIPANT_WRITER) + prismtech_builtin_endpoint_set |= NN_DISC_BUILTIN_ENDPOINT_CM_PUBLISHER_WRITER | NN_DISC_BUILTIN_ENDPOINT_CM_SUBSCRIBER_WRITER; + } + + nn_log (LC_DISCOVERY, " %x:%x:%x:%x", PGUID (datap->participant_guid)); + + /* Local SPDP packets may be looped back, and that may include ones + currently being deleted. The first thing that happens when + deleting a participant is removing it from the hash table, and + consequently the looped back packet may appear to be from an + unknown participant. So we handle that, too. */ + + if (is_deleted_participant_guid (&datap->participant_guid, DPG_REMOTE)) + { + nn_log (LC_DISCOVERY, " (recently deleted)"); + return 1; + } + + if (!config.enableLoopback) + { + int islocal = 0; + if (ephash_lookup_participant_guid (&datap->participant_guid)) + islocal = 1; + if (islocal) + { + nn_log (LC_DISCOVERY, " (local %d)", islocal); + return 0; + } + } + + if ((proxypp = ephash_lookup_proxy_participant_guid (&datap->participant_guid)) != NULL) + { + /* SPDP processing is so different from normal processing that we + are even skipping the automatic lease renewal. Therefore do it + regardless of + config.arrival_of_data_asserts_pp_and_ep_liveliness. */ + nn_log (LC_DISCOVERY, " (known)"); + lease_renew (os_atomic_ldvoidp (&proxypp->lease), now_et ()); + os_mutexLock (&proxypp->e.lock); + if (proxypp->implicitly_created) + { + nn_log (LC_DISCOVERY, " (NEW was-implicitly-created)"); + proxypp->implicitly_created = 0; + update_proxy_participant_plist_locked (proxypp, datap, UPD_PROXYPP_SPDP, timestamp); + } + os_mutexUnlock (&proxypp->e.lock); + return 0; + } + + nn_log (LC_DISCOVERY, " bes %x ptbes %x NEW", builtin_endpoint_set, prismtech_builtin_endpoint_set); + + if (datap->present & PP_PARTICIPANT_LEASE_DURATION) + { + lease_duration = datap->participant_lease_duration; + } + else + { + nn_log (LC_DISCOVERY, " (PARTICIPANT_LEASE_DURATION defaulting to 100s)"); + lease_duration = nn_to_ddsi_duration (100 * T_SECOND); + } + + if (datap->present & PP_PRISMTECH_PARTICIPANT_VERSION_INFO) { + if (datap->prismtech_participant_version_info.flags & NN_PRISMTECH_FL_KERNEL_SEQUENCE_NUMBER) + custom_flags |= CF_INC_KERNEL_SEQUENCE_NUMBERS; + + if ((datap->prismtech_participant_version_info.flags & NN_PRISMTECH_FL_DDSI2_PARTICIPANT_FLAG) && + (datap->prismtech_participant_version_info.flags & NN_PRISMTECH_FL_PARTICIPANT_IS_DDSI2)) + custom_flags |= CF_PARTICIPANT_IS_DDSI2; + + nn_log (LC_DISCOVERY, " (0x%08x-0x%08x-0x%08x-0x%08x-0x%08x %s)", + datap->prismtech_participant_version_info.version, + datap->prismtech_participant_version_info.flags, + datap->prismtech_participant_version_info.unused[0], + datap->prismtech_participant_version_info.unused[1], + datap->prismtech_participant_version_info.unused[2], + datap->prismtech_participant_version_info.internals); + } + + /* If any of the SEDP announcer are missing AND the guid prefix of + the SPDP writer differs from the guid prefix of the new participant, + we make it dependent on the writer's participant. See also the + lease expiration handling. Note that the entityid MUST be + NN_ENTITYID_PARTICIPANT or ephash_lookup will assert. So we only + zero the prefix. */ + privileged_pp_guid.prefix = rst->src_guid_prefix; + privileged_pp_guid.entityid.u = NN_ENTITYID_PARTICIPANT; + if ((builtin_endpoint_set & bes_sedp_announcer_mask) != bes_sedp_announcer_mask && + memcmp (&privileged_pp_guid, &datap->participant_guid, sizeof (nn_guid_t)) != 0) + { + nn_log (LC_DISCOVERY, " (depends on %x:%x:%x:%x)", PGUID (privileged_pp_guid)); + /* never expire lease for this proxy: it won't actually expire + until the "privileged" one expires anyway */ + lease_duration = nn_to_ddsi_duration (T_NEVER); + } + else if (vendor_is_opensplice (rst->vendor) && !(custom_flags & CF_PARTICIPANT_IS_DDSI2)) + { + /* Non-DDSI2 participants are made dependent on DDSI2 (but DDSI2 + itself need not be discovered yet) */ + struct proxy_participant *ddsi2; + if ((ddsi2 = find_ddsi2_proxy_participant (&datap->participant_guid)) == NULL) + memset (&privileged_pp_guid.prefix, 0, sizeof (privileged_pp_guid.prefix)); + else + { + privileged_pp_guid.prefix = ddsi2->e.guid.prefix; + lease_duration = nn_to_ddsi_duration (T_NEVER); + nn_log (LC_DISCOVERY, " (depends on %x:%x:%x:%x)", PGUID (privileged_pp_guid)); + } + } + else + { + memset (&privileged_pp_guid.prefix, 0, sizeof (privileged_pp_guid.prefix)); + } + + /* Choose locators */ + { + nn_locator_t loc; + int uc_same_subnet; + + as_default = new_addrset (); + as_meta = new_addrset (); + + if ((datap->present & PP_DEFAULT_MULTICAST_LOCATOR) && (get_locator (&loc, &datap->default_multicast_locators, 0))) + allowmulticast_aware_add_to_addrset (as_default, &loc); + if ((datap->present & PP_METATRAFFIC_MULTICAST_LOCATOR) && (get_locator (&loc, &datap->metatraffic_multicast_locators, 0))) + allowmulticast_aware_add_to_addrset (as_meta, &loc); + + /* If no multicast locators or multicast TTL > 1, assume IP (multicast) routing can be relied upon to reach + the remote participant, else only accept nodes with an advertised unicast address in the same subnet to + protect against multicasts being received over an unexpected interface (which sometimes appears to occur) */ + if (addrset_empty_mc (as_default) && addrset_empty_mc (as_meta)) + uc_same_subnet = 0; + else if (config.multicast_ttl > 1) + uc_same_subnet = 0; + else + { + uc_same_subnet = 1; + nn_log (LC_DISCOVERY, " subnet-filter"); + } + + /* If unicast locators not present, then try to obtain from connection */ + if ((datap->present & PP_DEFAULT_UNICAST_LOCATOR) && (get_locator (&loc, &datap->default_unicast_locators, uc_same_subnet))) + add_to_addrset (as_default, &loc); + else if (ddsi_conn_peer_locator (rst->conn, &loc)) + add_to_addrset (as_default, &loc); + + if ((datap->present & PP_METATRAFFIC_UNICAST_LOCATOR) && (get_locator (&loc, &datap->metatraffic_unicast_locators, uc_same_subnet))) + add_to_addrset (as_meta, &loc); + else if (ddsi_conn_peer_locator (rst->conn, &loc)) + add_to_addrset (as_meta, &loc); + + nn_log_addrset (LC_DISCOVERY, " (data", as_default); + nn_log_addrset (LC_DISCOVERY, " meta", as_meta); + nn_log (LC_DISCOVERY, ")"); + } + + if (addrset_empty_uc (as_default) || addrset_empty_uc (as_meta)) + { + nn_log (LC_DISCOVERY, " (no unicast address"); + unref_addrset (as_default); + unref_addrset (as_meta); + return 1; + } + + nn_log (LC_DISCOVERY, " QOS={"); + nn_log_xqos (LC_DISCOVERY, &datap->qos); + nn_log (LC_DISCOVERY, "}\n"); + + maybe_add_pp_as_meta_to_as_disc (as_meta); + + new_proxy_participant + ( + &datap->participant_guid, + builtin_endpoint_set, + prismtech_builtin_endpoint_set, + &privileged_pp_guid, + as_default, + as_meta, + datap, + nn_from_ddsi_duration (lease_duration), + rst->vendor, + custom_flags, + timestamp + ); + + /* Force transmission of SPDP messages - we're not very careful + in avoiding the processing of SPDP packets addressed to others + so filter here */ + { + int have_dst = + (rst->dst_guid_prefix.u[0] != 0 || rst->dst_guid_prefix.u[1] != 0 || rst->dst_guid_prefix.u[2] != 0); + if (!have_dst) + { + nn_log (LC_DISCOVERY, "broadcasted SPDP packet -> answering"); + respond_to_spdp (&datap->participant_guid); + } + else + { + nn_log (LC_DISCOVERY, "directed SPDP packet -> not responding\n"); + } + } + + if (custom_flags & CF_PARTICIPANT_IS_DDSI2) + { + /* If we just discovered DDSI2, make sure any existing + participants served by it are made dependent on it */ + make_participants_dependent_on_ddsi2 (&datap->participant_guid, timestamp); + } + else if (privileged_pp_guid.prefix.u[0] || privileged_pp_guid.prefix.u[1] || privileged_pp_guid.prefix.u[2]) + { + /* If we just created a participant dependent on DDSI2, make sure + DDSI2 still exists. There is a risk of racing the lease expiry + of DDSI2. */ + if (ephash_lookup_proxy_participant_guid (&privileged_pp_guid) == NULL) + { + nn_log (LC_DISCOVERY, "make_participants_dependent_on_ddsi2: ddsi2 %x:%x:%x:%x is no more, delete %x:%x:%x:%x\n", PGUID (privileged_pp_guid), PGUID (datap->participant_guid)); + delete_proxy_participant_by_guid (&datap->participant_guid, timestamp, 1); + } + } + return 1; +} + +static void handle_SPDP (const struct receiver_state *rst, nn_wctime_t timestamp, unsigned statusinfo, const void *vdata, unsigned len) +{ + const struct CDRHeader *data = vdata; /* built-ins not deserialized (yet) */ + TRACE (("SPDP ST%x", statusinfo)); + if (data == NULL) + { + TRACE ((" no payload?\n")); + return; + } + else + { + nn_plist_t decoded_data; + nn_plist_src_t src; + int interesting = 0; + src.protocol_version = rst->protocol_version; + src.vendorid = rst->vendor; + src.encoding = data->identifier; + src.buf = (unsigned char *) data + 4; + src.bufsz = len - 4; + if (nn_plist_init_frommsg (&decoded_data, NULL, ~(uint64_t)0, ~(uint64_t)0, &src) < 0) + { + NN_WARNING ("SPDP (vendor %u.%u): invalid qos/parameters\n", src.vendorid.id[0], src.vendorid.id[1]); + return; + } + + switch (statusinfo & (NN_STATUSINFO_DISPOSE | NN_STATUSINFO_UNREGISTER)) + { + case 0: + interesting = handle_SPDP_alive (rst, timestamp, &decoded_data); + break; + + case NN_STATUSINFO_DISPOSE: + case NN_STATUSINFO_UNREGISTER: + case (NN_STATUSINFO_DISPOSE | NN_STATUSINFO_UNREGISTER): + interesting = handle_SPDP_dead (rst, timestamp, &decoded_data, statusinfo); + break; + } + + nn_plist_fini (&decoded_data); + nn_log (interesting ? LC_DISCOVERY : LC_TRACE, "\n"); + } +} + +static void add_sockaddr_to_ps (const nn_locator_t *loc, void *arg) +{ + nn_plist_t *ps = (nn_plist_t *) arg; + struct nn_locators_one *elem = os_malloc (sizeof (struct nn_locators_one)); + struct nn_locators *locs; + unsigned present_flag; + + elem->loc = *loc; + elem->next = NULL; + + if (is_mcaddr (loc)) { + locs = &ps->multicast_locators; + present_flag = PP_MULTICAST_LOCATOR; + } else { + locs = &ps->unicast_locators; + present_flag = PP_UNICAST_LOCATOR; + } + + if (!(ps->present & present_flag)) + { + locs->n = 0; + locs->first = locs->last = NULL; + ps->present |= present_flag; + } + locs->n++; + if (locs->first) + locs->last->next = elem; + else + locs->first = elem; + locs->last = elem; +} + +/****************************************************************************** + *** + *** SEDP + *** + *****************************************************************************/ + +static int sedp_write_endpoint +( + struct writer *wr, int end_of_life, const nn_guid_t *epguid, + const struct entity_common *common, const struct endpoint_common *epcommon, + const nn_xqos_t *xqos, struct addrset *as) +{ + const nn_xqos_t *defqos = is_writer_entityid (epguid->entityid) ? &gv.default_xqos_wr : &gv.default_xqos_rd; + const nn_vendorid_t my_vendor_id = MY_VENDOR_ID; + const int just_key = end_of_life; + struct nn_xmsg *mpayload; + uint64_t qosdiff; + nn_guid_t kh; + nn_plist_t ps; + serstate_t serstate; + serdata_t serdata; + void *payload_blob; + size_t payload_sz; + unsigned statusinfo; + + nn_plist_init_empty (&ps); + ps.present |= PP_ENDPOINT_GUID; + ps.endpoint_guid = *epguid; + + if (common && *common->name != 0) + { + ps.present |= PP_ENTITY_NAME; + ps.aliased |= PP_ENTITY_NAME; + ps.entity_name = common->name; + } + + if (end_of_life) + { + assert (xqos == NULL); + assert (epcommon == NULL); + qosdiff = 0; + } + else + { + assert (xqos != NULL); + assert (epcommon != NULL); + ps.present |= PP_PROTOCOL_VERSION | PP_VENDORID; + ps.protocol_version.major = RTPS_MAJOR; + ps.protocol_version.minor = RTPS_MINOR; + ps.vendorid = my_vendor_id; + + if (epcommon->group_guid.entityid.u != 0) + { + ps.present |= PP_GROUP_GUID; + ps.group_guid = epcommon->group_guid; + } + +#ifdef DDSI_INCLUDE_SSM + /* A bit of a hack -- the easy alternative would be to make it yet + another parameter. We only set "reader favours SSM" if we + really do: no point in telling the world that everything is at + the default. */ + if (!is_writer_entityid (epguid->entityid)) + { + const struct reader *rd = ephash_lookup_reader_guid (epguid); + assert (rd); + if (rd->favours_ssm) + { + ps.present |= PP_READER_FAVOURS_SSM; + ps.reader_favours_ssm.state = 1u; + } + } +#endif + + qosdiff = nn_xqos_delta (xqos, defqos, ~(uint64_t)0); + if (config.explicitly_publish_qos_set_to_default) + qosdiff |= ~QP_UNRECOGNIZED_INCOMPATIBLE_MASK; + + if (as) + { + addrset_forall (as, add_sockaddr_to_ps, &ps); + } + } + + /* The message is only a temporary thing, used only for encoding + the QoS and other settings. So the header fields aren't really + important, except that they need to be set to reasonable things + or it'll crash */ + mpayload = nn_xmsg_new (gv.xmsgpool, &wr->e.guid.prefix, 0, NN_XMSG_KIND_DATA); + nn_plist_addtomsg (mpayload, &ps, ~(uint64_t)0, ~(uint64_t)0); + if (xqos) nn_xqos_addtomsg (mpayload, xqos, qosdiff); + nn_xmsg_addpar_sentinel (mpayload); + nn_plist_fini (&ps); + + /* Then we take the payload from the message and turn it into a + serdata, and then we can write it as normal data */ + serstate = ddsi_serstate_new (gv.serpool, NULL); + payload_blob = nn_xmsg_payload (&payload_sz, mpayload); + ddsi_serstate_append_blob (serstate, 4, payload_sz, payload_blob); + kh = nn_hton_guid (*epguid); + serstate_set_key (serstate, just_key, &kh); + if (end_of_life) + statusinfo = NN_STATUSINFO_DISPOSE | NN_STATUSINFO_UNREGISTER; + else + statusinfo = 0; + ddsi_serstate_set_msginfo (serstate, statusinfo, now (), NULL); + serdata = ddsi_serstate_fix (serstate); + nn_xmsg_free (mpayload); + + TRACE (("sedp: write for %x:%x:%x:%x via %x:%x:%x:%x\n", PGUID (*epguid), PGUID (wr->e.guid))); + return write_sample_nogc_notk (NULL, wr, serdata); +} + +static struct writer *get_sedp_writer (const struct participant *pp, unsigned entityid) +{ + struct writer *sedp_wr = get_builtin_writer (pp, entityid); + if (sedp_wr == NULL) + NN_FATAL ("sedp_write_writer: no SEDP builtin writer %x for %x:%x:%x:%x\n", entityid, PGUID (pp->e.guid)); + return sedp_wr; +} + +int sedp_write_writer (struct writer *wr) +{ + if ((!is_builtin_entityid(wr->e.guid.entityid, ownvendorid)) && (!wr->e.onlylocal)) + { + struct writer *sedp_wr = get_sedp_writer (wr->c.pp, NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_WRITER); +#ifdef DDSI_INCLUDE_SSM + struct addrset *as = wr->ssm_as; +#else + struct addrset *as = NULL; +#endif + return sedp_write_endpoint (sedp_wr, 0, &wr->e.guid, &wr->e, &wr->c, wr->xqos, as); + } + return 0; +} + +int sedp_write_reader (struct reader *rd) +{ + if ((!is_builtin_entityid (rd->e.guid.entityid, ownvendorid)) && (!rd->e.onlylocal)) + { + struct writer *sedp_wr = get_sedp_writer (rd->c.pp, NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_WRITER); +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + struct addrset *as = rd->as; +#else + struct addrset *as = NULL; +#endif + return sedp_write_endpoint (sedp_wr, 0, &rd->e.guid, &rd->e, &rd->c, rd->xqos, as); + } + return 0; +} + +int sedp_dispose_unregister_writer (struct writer *wr) +{ + if ((!is_builtin_entityid(wr->e.guid.entityid, ownvendorid)) && (!wr->e.onlylocal)) + { + struct writer *sedp_wr = get_sedp_writer (wr->c.pp, NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_WRITER); + return sedp_write_endpoint (sedp_wr, 1, &wr->e.guid, NULL, NULL, NULL, NULL); + } + return 0; +} + +int sedp_dispose_unregister_reader (struct reader *rd) +{ + if ((!is_builtin_entityid(rd->e.guid.entityid, ownvendorid)) && (!rd->e.onlylocal)) + { + struct writer *sedp_wr = get_sedp_writer (rd->c.pp, NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_WRITER); + return sedp_write_endpoint (sedp_wr, 1, &rd->e.guid, NULL, NULL, NULL, NULL); + } + return 0; +} + +static const char *durability_to_string (nn_durability_kind_t k) +{ + switch (k) + { + case NN_VOLATILE_DURABILITY_QOS: return "volatile"; + case NN_TRANSIENT_LOCAL_DURABILITY_QOS: return "transient-local"; + case NN_TRANSIENT_DURABILITY_QOS: return "transient"; + case NN_PERSISTENT_DURABILITY_QOS: return "persistent"; + } + abort (); return 0; +} + +static struct proxy_participant *implicitly_create_proxypp (const nn_guid_t *ppguid, nn_plist_t *datap /* note: potentially modifies datap */, const nn_guid_prefix_t *src_guid_prefix, nn_vendorid_t vendorid, nn_wctime_t timestamp) +{ + nn_guid_t privguid; + nn_plist_t pp_plist; + + if (memcmp (&ppguid->prefix, src_guid_prefix, sizeof (ppguid->prefix)) == 0) + /* if the writer is owned by the participant itself, we're not interested */ + return NULL; + + privguid.prefix = *src_guid_prefix; + privguid.entityid = to_entityid (NN_ENTITYID_PARTICIPANT); + nn_plist_init_empty(&pp_plist); + + if (vendor_is_cloud (vendorid)) + { + /* Some endpoint that we discovered through the DS, but then it must have at least some locators */ + TRACE ((" from-DS %x:%x:%x:%x", PGUID (privguid))); + /* avoid "no address" case, so we never create the proxy participant for nothing (FIXME: rework some of this) */ + if (!(datap->present & (PP_UNICAST_LOCATOR | PP_MULTICAST_LOCATOR))) + { + TRACE ((" data locator absent\n")); + goto err; + } + nn_log (LC_DISCOVERY, " new-proxypp %x:%x:%x:%x\n", PGUID (*ppguid)); + new_proxy_participant(ppguid, 0, 0, &privguid, new_addrset(), new_addrset(), &pp_plist, T_NEVER, vendorid, CF_IMPLICITLY_CREATED_PROXYPP, timestamp); + } + else if (ppguid->prefix.u[0] == src_guid_prefix->u[0] && vendor_is_opensplice (vendorid)) + { + /* FIXME: requires address sets to be those of ddsi2, no built-in + readers or writers, only if remote ddsi2 is provably running + with a minimal built-in endpoint set */ + struct proxy_participant *privpp; + if ((privpp = ephash_lookup_proxy_participant_guid (&privguid)) == NULL) { + TRACE ((" unknown-src-proxypp?\n")); + goto err; + } else if (!privpp->is_ddsi2_pp) { + TRACE ((" src-proxypp-not-ddsi2?\n")); + goto err; + } else if (!privpp->minimal_bes_mode) { + TRACE ((" src-ddsi2-not-minimal-bes-mode?\n")); + goto err; + } else { + struct addrset *as_default, *as_meta; + nn_plist_t tmp_plist; + TRACE ((" from-ddsi2 %x:%x:%x:%x", PGUID (privguid))); + nn_plist_init_empty (&pp_plist); + + os_mutexLock (&privpp->e.lock); + as_default = ref_addrset(privpp->as_default); + as_meta = ref_addrset(privpp->as_meta); + /* copy just what we need */ + tmp_plist = *privpp->plist; + tmp_plist.present = PP_PARTICIPANT_GUID | PP_PRISMTECH_PARTICIPANT_VERSION_INFO; + tmp_plist.participant_guid = *ppguid; + nn_plist_mergein_missing (&pp_plist, &tmp_plist); + os_mutexUnlock (&privpp->e.lock); + + pp_plist.prismtech_participant_version_info.flags &= ~NN_PRISMTECH_FL_PARTICIPANT_IS_DDSI2; + new_proxy_participant (ppguid, 0, 0, &privguid, as_default, as_meta, &pp_plist, T_NEVER, vendorid, CF_IMPLICITLY_CREATED_PROXYPP | CF_PROXYPP_NO_SPDP, timestamp); + } + } + + err: + nn_plist_fini (&pp_plist); + return ephash_lookup_proxy_participant_guid (ppguid); +} + +static void handle_SEDP_alive (nn_plist_t *datap /* note: potentially modifies datap */, const nn_guid_prefix_t *src_guid_prefix, nn_vendorid_t vendorid, nn_wctime_t timestamp) +{ +#define E(msg, lbl) do { nn_log (LC_DISCOVERY, (msg)); goto lbl; } while (0) + struct proxy_participant *pp; + struct proxy_writer * pwr = NULL; + struct proxy_reader * prd = NULL; + nn_guid_t ppguid; + nn_xqos_t *xqos; + int reliable; + struct addrset *as; + int is_writer; +#ifdef DDSI_INCLUDE_SSM + int ssm; +#endif + + assert (datap); + + if (!(datap->present & PP_ENDPOINT_GUID)) + E (" no guid?\n", err); + nn_log (LC_DISCOVERY, " %x:%x:%x:%x", PGUID (datap->endpoint_guid)); + + ppguid.prefix = datap->endpoint_guid.prefix; + ppguid.entityid.u = NN_ENTITYID_PARTICIPANT; + if (is_deleted_participant_guid (&ppguid, DPG_REMOTE)) + E (" local dead pp?\n", err); + + if + ( + (! config.enableLoopback) && + (ephash_lookup_participant_guid (&ppguid) != NULL) + ) + E (" local pp?\n", err); + + if (is_builtin_entityid (datap->endpoint_guid.entityid, vendorid)) + E (" built-in\n", err); + if (!(datap->qos.present & QP_TOPIC_NAME)) + E (" no topic?\n", err); + if (!(datap->qos.present & QP_TYPE_NAME)) + E (" no typename?\n", err); + + if ((pp = ephash_lookup_proxy_participant_guid (&ppguid)) == NULL) + { + nn_log (LC_DISCOVERY, " unknown-proxypp"); + if ((pp = implicitly_create_proxypp (&ppguid, datap, src_guid_prefix, vendorid, timestamp)) == NULL) + E ("?\n", err); + /* Repeat regular SEDP trace for convenience */ + nn_log (LC_DISCOVERY, "SEDP ST0 %x:%x:%x:%x (cont)", PGUID (datap->endpoint_guid)); + } + + xqos = &datap->qos; + is_writer = is_writer_entityid (datap->endpoint_guid.entityid); + if (!is_writer) + nn_xqos_mergein_missing (xqos, &gv.default_xqos_rd); + else if (vendor_is_prismtech(vendorid)) + nn_xqos_mergein_missing (xqos, &gv.default_xqos_wr); + else + nn_xqos_mergein_missing (xqos, &gv.default_xqos_wr_nad); + + /* After copy + merge, should have at least the ones present in the + input. Also verify reliability and durability are present, + because we explicitly read those. */ + assert ((xqos->present & datap->qos.present) == datap->qos.present); + assert (xqos->present & QP_RELIABILITY); + assert (xqos->present & QP_DURABILITY); + reliable = (xqos->reliability.kind == NN_RELIABLE_RELIABILITY_QOS); + + nn_log (LC_DISCOVERY, " %s %s %s: %s%s.%s/%s", + reliable ? "reliable" : "best-effort", + durability_to_string (xqos->durability.kind), + is_writer ? "writer" : "reader", + ((!(xqos->present & QP_PARTITION) || xqos->partition.n == 0 || *xqos->partition.strs[0] == '\0') + ? "(default)" : xqos->partition.strs[0]), + ((xqos->present & QP_PARTITION) && xqos->partition.n > 1) ? "+" : "", + xqos->topic_name, xqos->type_name); + + if (! is_writer && (datap->present & PP_EXPECTS_INLINE_QOS) && datap->expects_inline_qos) + { + E ("******* AARGH - it expects inline QoS ********\n", err); + } + + if (is_writer) + { + pwr = ephash_lookup_proxy_writer_guid (&datap->endpoint_guid); + } + else + { + prd = ephash_lookup_proxy_reader_guid (&datap->endpoint_guid); + } + if (pwr || prd) + { + /* Cloud load balances by updating participant endpoints */ + + if (! vendor_is_cloud (vendorid)) + { + nn_log (LC_DISCOVERY, " known\n"); + goto err; + } + + /* Re-bind the proxy participant to the discovery service - and do this if it is currently + bound to another DS instance, because that other DS instance may have already failed and + with a new one taking over, without our noticing it. */ + nn_log (LC_DISCOVERY, " known-DS"); + if (vendor_is_cloud (vendorid) && pp->implicitly_created && memcmp(&pp->privileged_pp_guid.prefix, src_guid_prefix, sizeof(pp->privileged_pp_guid.prefix)) != 0) + { + nn_etime_t never = { T_NEVER }; + nn_log (LC_DISCOVERY, " %x:%x:%x:%x attach-to-DS %x:%x:%x:%x", PGUID(pp->e.guid), PGUIDPREFIX(*src_guid_prefix), pp->privileged_pp_guid.entityid.u); + os_mutexLock (&pp->e.lock); + pp->privileged_pp_guid.prefix = *src_guid_prefix; + lease_set_expiry(os_atomic_ldvoidp(&pp->lease), never); + os_mutexUnlock (&pp->e.lock); + } + nn_log (LC_DISCOVERY, "\n"); + } + else + { + nn_log (LC_DISCOVERY, " NEW"); + } + + { + nn_locator_t loc; + as = new_addrset (); + if ((datap->present & PP_UNICAST_LOCATOR) && get_locator (&loc, &datap->unicast_locators, 0)) + add_to_addrset (as, &loc); + else + copy_addrset_into_addrset_uc (as, pp->as_default); + if ((datap->present & PP_MULTICAST_LOCATOR) && get_locator (&loc, &datap->multicast_locators, 0)) + allowmulticast_aware_add_to_addrset (as, &loc); + else + copy_addrset_into_addrset_mc (as, pp->as_default); + } + if (addrset_empty (as)) + { + unref_addrset (as); + E (" no address", err); + } + + nn_log_addrset (LC_DISCOVERY, " (as", as); +#ifdef DDSI_INCLUDE_SSM + ssm = 0; + if (is_writer) + ssm = addrset_contains_ssm (as); + else if (datap->present & PP_READER_FAVOURS_SSM) + ssm = (datap->reader_favours_ssm.state != 0); + nn_log (LC_DISCOVERY, " ssm=%u", ssm); +#endif + nn_log (LC_DISCOVERY, ") QOS={"); + nn_log_xqos (LC_DISCOVERY, xqos); + nn_log (LC_DISCOVERY, "}\n"); + + if ((datap->endpoint_guid.entityid.u & NN_ENTITYID_SOURCE_MASK) == NN_ENTITYID_SOURCE_VENDOR && !vendor_is_prismtech (vendorid)) + { + nn_log (LC_DISCOVERY, "ignoring vendor-specific endpoint %x:%x:%x:%x\n", PGUID (datap->endpoint_guid)); + } + else + { + if (is_writer) + { + if (pwr) + { + update_proxy_writer (pwr, as); + } + else + { + /* not supposed to get here for built-in ones, so can determine the channel based on the transport priority */ + assert (!is_builtin_entityid (datap->endpoint_guid.entityid, vendorid)); +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + { + struct config_channel_listelem *channel = find_channel (xqos->transport_priority); + new_proxy_writer (&ppguid, &datap->endpoint_guid, as, datap, channel->dqueue, channel->evq ? channel->evq : gv.xevents, timestamp); + } +#else + new_proxy_writer (&ppguid, &datap->endpoint_guid, as, datap, gv.user_dqueue, gv.xevents, timestamp); +#endif + } + } + else + { + if (prd) + { + update_proxy_reader (prd, as); + } + else + { +#ifdef DDSI_INCLUDE_SSM + new_proxy_reader (&ppguid, &datap->endpoint_guid, as, datap, timestamp, ssm); +#else + new_proxy_reader (&ppguid, &datap->endpoint_guid, as, datap, timestamp); +#endif + } + } + } + + unref_addrset (as); + +err: + + return; +#undef E +} + +static void handle_SEDP_dead (nn_plist_t *datap, nn_wctime_t timestamp) +{ + int res; + if (!(datap->present & PP_ENDPOINT_GUID)) + { + nn_log (LC_DISCOVERY, " no guid?\n"); + return; + } + nn_log (LC_DISCOVERY, " %x:%x:%x:%x", PGUID (datap->endpoint_guid)); + if (is_writer_entityid (datap->endpoint_guid.entityid)) + { + res = delete_proxy_writer (&datap->endpoint_guid, timestamp, 0); + } + else + { + res = delete_proxy_reader (&datap->endpoint_guid, timestamp, 0); + } + nn_log (LC_DISCOVERY, " %s\n", (res < 0) ? " unknown" : " delete"); +} + +static void handle_SEDP (const struct receiver_state *rst, nn_wctime_t timestamp, unsigned statusinfo, const void *vdata, unsigned len) +{ + const struct CDRHeader *data = vdata; /* built-ins not deserialized (yet) */ + nn_log (LC_DISCOVERY, "SEDP ST%x", statusinfo); + if (data == NULL) + { + nn_log (LC_DISCOVERY, " no payload?\n"); + return; + } + else + { + nn_plist_t decoded_data; + nn_plist_src_t src; + src.protocol_version = rst->protocol_version; + src.vendorid = rst->vendor; + src.encoding = data->identifier; + src.buf = (unsigned char *) data + 4; + src.bufsz = len - 4; + if (nn_plist_init_frommsg (&decoded_data, NULL, ~(uint64_t)0, ~(uint64_t)0, &src) < 0) + { + NN_WARNING ("SEDP (vendor %u.%u): invalid qos/parameters\n", src.vendorid.id[0], src.vendorid.id[1]); + return; + } + + switch (statusinfo & (NN_STATUSINFO_DISPOSE | NN_STATUSINFO_UNREGISTER)) + { + case 0: + handle_SEDP_alive (&decoded_data, &rst->src_guid_prefix, rst->vendor, timestamp); + break; + + case NN_STATUSINFO_DISPOSE: + case NN_STATUSINFO_UNREGISTER: + case (NN_STATUSINFO_DISPOSE | NN_STATUSINFO_UNREGISTER): + handle_SEDP_dead (&decoded_data, timestamp); + break; + } + + nn_plist_fini (&decoded_data); + } +} + +/****************************************************************************** + *** + *** Topics + *** + *****************************************************************************/ + +int sedp_write_topic (struct participant *pp, const struct nn_plist *datap) +{ + struct writer *sedp_wr; + struct nn_xmsg *mpayload; + serstate_t serstate; + serdata_t serdata; + void *payload_blob; + size_t payload_sz; + uint32_t topic_name_sz; + uint32_t topic_name_sz_BE; + uint64_t delta; + unsigned char digest[16]; + md5_state_t md5st; + + assert (datap->qos.present & QP_TOPIC_NAME); + + if (pp->e.onlylocal) { + /* This topic is only locally available. */ + return 0; + } + + sedp_wr = get_sedp_writer (pp, NN_ENTITYID_SEDP_BUILTIN_TOPIC_WRITER); + + mpayload = nn_xmsg_new (gv.xmsgpool, &sedp_wr->e.guid.prefix, 0, NN_XMSG_KIND_DATA); + delta = nn_xqos_delta (&datap->qos, &gv.default_xqos_tp, ~(uint64_t)0); + if (config.explicitly_publish_qos_set_to_default) + delta |= ~QP_UNRECOGNIZED_INCOMPATIBLE_MASK; + nn_plist_addtomsg (mpayload, datap, ~(uint64_t)0, delta); + nn_xmsg_addpar_sentinel (mpayload); + + serstate = ddsi_serstate_new (gv.serpool, NULL); + payload_blob = nn_xmsg_payload (&payload_sz, mpayload); + ddsi_serstate_append_blob (serstate, 4, payload_sz, payload_blob); + + topic_name_sz = (uint32_t) strlen (datap->qos.topic_name) + 1; + topic_name_sz_BE = toBE4u (topic_name_sz); + + md5_init (&md5st); + md5_append (&md5st, (const md5_byte_t *) &topic_name_sz_BE, sizeof (topic_name_sz_BE)); + md5_append (&md5st, (const md5_byte_t *) datap->qos.topic_name, topic_name_sz); + md5_finish (&md5st, digest); + + serstate_set_key (serstate, 0, digest); + ddsi_serstate_set_msginfo (serstate, 0, now (), NULL); + serdata = ddsi_serstate_fix (serstate); + nn_xmsg_free (mpayload); + + TRACE (("sedp: write topic %s via %x:%x:%x:%x\n", datap->qos.topic_name, PGUID (sedp_wr->e.guid))); + return write_sample_nogc_notk (NULL, sedp_wr, serdata); +} + + +/****************************************************************************** + *** + *** PrismTech CM data + *** + *****************************************************************************/ + +int sedp_write_cm_participant (struct participant *pp, int alive) +{ + struct writer * sedp_wr; + struct nn_xmsg *mpayload; + serstate_t serstate; + serdata_t serdata; + nn_plist_t ps; + nn_guid_t kh; + void *payload_blob; + size_t payload_sz; + unsigned statusinfo; + + if (pp->e.onlylocal) { + /* This topic is only locally available. */ + return 0; + } + + propagate_builtin_topic_cmparticipant(&(pp->e), pp->plist, now(), alive); + + sedp_wr = get_sedp_writer (pp, NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_WRITER); + + /* The message is only a temporary thing, used only for encoding + the QoS and other settings. So the header fields aren't really + important, except that they need to be set to reasonable things + or it'll crash */ + mpayload = nn_xmsg_new (gv.xmsgpool, &sedp_wr->e.guid.prefix, 0, NN_XMSG_KIND_DATA); + nn_plist_init_empty (&ps); + ps.present = PP_PARTICIPANT_GUID; + ps.participant_guid = pp->e.guid; + if (alive) + { + nn_plist_addtomsg (mpayload, &ps, ~(uint64_t)0, ~(uint64_t)0); + nn_plist_addtomsg (mpayload, pp->plist, + PP_PRISMTECH_NODE_NAME | PP_PRISMTECH_EXEC_NAME | PP_PRISMTECH_PROCESS_ID | + PP_PRISMTECH_WATCHDOG_SCHEDULING | PP_PRISMTECH_LISTENER_SCHEDULING | + PP_PRISMTECH_SERVICE_TYPE | PP_ENTITY_NAME, + QP_PRISMTECH_ENTITY_FACTORY); + } + nn_xmsg_addpar_sentinel (mpayload); + + /* Then we take the payload from the message and turn it into a + serdata, and then we can write it as normal data */ + serstate = ddsi_serstate_new (gv.serpool, NULL); + payload_blob = nn_xmsg_payload (&payload_sz, mpayload); + ddsi_serstate_append_blob (serstate, 4, payload_sz, payload_blob); + kh = nn_hton_guid (pp->e.guid); + serstate_set_key (serstate, !alive, &kh); + if (!alive) + statusinfo = NN_STATUSINFO_DISPOSE | NN_STATUSINFO_UNREGISTER; + else + statusinfo = 0; + ddsi_serstate_set_msginfo (serstate, statusinfo, now (), NULL); + serdata = ddsi_serstate_fix (serstate); + nn_xmsg_free (mpayload); + + TRACE (("sedp: write CMParticipant ST%x for %x:%x:%x:%x via %x:%x:%x:%x\n", statusinfo, PGUID (pp->e.guid), PGUID (sedp_wr->e.guid))); + return write_sample_nogc_notk (NULL, sedp_wr, serdata); +} + +static void handle_SEDP_CM (const struct receiver_state *rst, nn_entityid_t wr_entity_id, nn_wctime_t timestamp, unsigned statusinfo, const void *vdata, unsigned len) +{ + const struct CDRHeader *data = vdata; /* built-ins not deserialized (yet) */ + nn_log (LC_DISCOVERY, "SEDP_CM ST%x", statusinfo); + assert (wr_entity_id.u == NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_WRITER); + (void) wr_entity_id; + if (data == NULL) + { + nn_log (LC_DISCOVERY, " no payload?\n"); + return; + } + else + { + nn_plist_t decoded_data; + nn_plist_src_t src; + src.protocol_version = rst->protocol_version; + src.vendorid = rst->vendor; + src.encoding = data->identifier; + src.buf = (unsigned char *) data + 4; + src.bufsz = len - 4; + if (nn_plist_init_frommsg (&decoded_data, NULL, ~(uint64_t)0, ~(uint64_t)0, &src) < 0) + { + NN_WARNING ("SEDP_CM (vendor %u.%u): invalid qos/parameters\n", src.vendorid.id[0], src.vendorid.id[1]); + return; + } + + /* ignore: dispose/unregister is tied to deleting the participant, which will take care of the dispose/unregister for us */; + if ((statusinfo & (NN_STATUSINFO_DISPOSE | NN_STATUSINFO_UNREGISTER)) == 0) + { + struct proxy_participant *proxypp; + if (!(decoded_data.present & PP_PARTICIPANT_GUID)) + NN_WARNING ("SEDP_CM (vendor %u.%u): missing participant GUID\n", src.vendorid.id[0], src.vendorid.id[1]); + else + { + if ((proxypp = ephash_lookup_proxy_participant_guid (&decoded_data.participant_guid)) == NULL) + proxypp = implicitly_create_proxypp (&decoded_data.participant_guid, &decoded_data, &rst->src_guid_prefix, rst->vendor, timestamp); + if (proxypp != NULL) + update_proxy_participant_plist (proxypp, &decoded_data, UPD_PROXYPP_CM, timestamp); + } + } + + nn_plist_fini (&decoded_data); + } + nn_log (LC_DISCOVERY, "\n"); +} + +static struct participant *group_guid_to_participant (const nn_guid_t *group_guid) +{ + nn_guid_t ppguid; + ppguid.prefix = group_guid->prefix; + ppguid.entityid.u = NN_ENTITYID_PARTICIPANT; + return ephash_lookup_participant_guid (&ppguid); +} + +int sedp_write_cm_publisher (const struct nn_plist *datap, int alive) +{ + struct participant *pp; + struct writer *sedp_wr; + struct nn_xmsg *mpayload; + serstate_t serstate; + serdata_t serdata; + nn_guid_t kh; + void *payload_blob; + size_t payload_sz; + unsigned statusinfo; + uint64_t delta; + + if ((pp = group_guid_to_participant (&datap->group_guid)) == NULL) + { + TRACE (("sedp: write CMPublisher alive:%d for %x:%x:%x:%x dropped: no participant\n", + alive, PGUID (datap->group_guid))); + return 0; + } + sedp_wr = get_sedp_writer (pp, NN_ENTITYID_SEDP_BUILTIN_CM_PUBLISHER_WRITER); + + /* The message is only a temporary thing, used only for encoding + the QoS and other settings. So the header fields aren't really + important, except that they need to be set to reasonable things + or it'll crash */ + mpayload = nn_xmsg_new (gv.xmsgpool, &sedp_wr->e.guid.prefix, 0, NN_XMSG_KIND_DATA); + if (!alive) + delta = 0; + else + { + delta = nn_xqos_delta (&datap->qos, &gv.default_xqos_pub, ~(uint64_t)0); + if (!config.explicitly_publish_qos_set_to_default) + delta |= ~QP_UNRECOGNIZED_INCOMPATIBLE_MASK; + } + nn_plist_addtomsg (mpayload, datap, ~(uint64_t)0, delta); + nn_xmsg_addpar_sentinel (mpayload); + + /* Then we take the payload from the message and turn it into a + serdata, and then we can write it as normal data */ + serstate = ddsi_serstate_new (gv.serpool, NULL); + payload_blob = nn_xmsg_payload (&payload_sz, mpayload); + ddsi_serstate_append_blob (serstate, 4, payload_sz, payload_blob); + kh = nn_hton_guid (datap->group_guid); + serstate_set_key (serstate, !alive, &kh); + if (!alive) + statusinfo = NN_STATUSINFO_DISPOSE | NN_STATUSINFO_UNREGISTER; + else + statusinfo = 0; + ddsi_serstate_set_msginfo (serstate, statusinfo, now (), NULL); + serdata = ddsi_serstate_fix (serstate); + nn_xmsg_free (mpayload); + + return write_sample_nogc_notk (NULL, sedp_wr, serdata); +} + +int sedp_write_cm_subscriber (const struct nn_plist *datap, int alive) +{ + struct participant *pp; + struct writer *sedp_wr; + struct nn_xmsg *mpayload; + serstate_t serstate; + serdata_t serdata; + nn_guid_t kh; + void *payload_blob; + size_t payload_sz; + unsigned statusinfo; + uint64_t delta; + + if ((pp = group_guid_to_participant (&datap->group_guid)) == NULL) + { + TRACE (("sedp: write CMSubscriber alive:%d for %x:%x:%x:%x dropped: no participant\n", + alive, PGUID (datap->group_guid))); + return 0; + } + sedp_wr = get_sedp_writer (pp, NN_ENTITYID_SEDP_BUILTIN_CM_SUBSCRIBER_WRITER); + + /* The message is only a temporary thing, used only for encoding + the QoS and other settings. So the header fields aren't really + important, except that they need to be set to reasonable things + or it'll crash */ + mpayload = nn_xmsg_new (gv.xmsgpool, &sedp_wr->e.guid.prefix, 0, NN_XMSG_KIND_DATA); + if (!alive) + delta = 0; + else + { + delta = nn_xqos_delta (&datap->qos, &gv.default_xqos_sub, ~(uint64_t)0); + if (!config.explicitly_publish_qos_set_to_default) + delta |= ~QP_UNRECOGNIZED_INCOMPATIBLE_MASK; + } + nn_plist_addtomsg (mpayload, datap, ~(uint64_t)0, delta); + nn_xmsg_addpar_sentinel (mpayload); + + /* Then we take the payload from the message and turn it into a + serdata, and then we can write it as normal data */ + serstate = ddsi_serstate_new (gv.serpool, NULL); + payload_blob = nn_xmsg_payload (&payload_sz, mpayload); + ddsi_serstate_append_blob (serstate, 4, payload_sz, payload_blob); + kh = nn_hton_guid (datap->group_guid); + serstate_set_key (serstate, !alive, &kh); + if (!alive) + statusinfo = NN_STATUSINFO_DISPOSE | NN_STATUSINFO_UNREGISTER; + else + statusinfo = 0; + ddsi_serstate_set_msginfo (serstate, statusinfo, now (), NULL); + serdata = ddsi_serstate_fix (serstate); + nn_xmsg_free (mpayload); + + return write_sample_nogc_notk (NULL, sedp_wr, serdata); +} + +static void handle_SEDP_GROUP_alive (nn_plist_t *datap /* note: potentially modifies datap */, nn_wctime_t timestamp) +{ +#define E(msg, lbl) do { nn_log (LC_DISCOVERY, (msg)); goto lbl; } while (0) + nn_guid_t ppguid; + + if (!(datap->present & PP_GROUP_GUID)) + E (" no guid?\n", err); + nn_log (LC_DISCOVERY, " %x:%x:%x:%x", PGUID (datap->group_guid)); + + ppguid.prefix = datap->group_guid.prefix; + ppguid.entityid.u = NN_ENTITYID_PARTICIPANT; + if (ephash_lookup_proxy_participant_guid (&ppguid) == NULL) + E (" unknown proxy pp?\n", err); + + nn_log (LC_DISCOVERY, " alive\n"); + + { + struct v_gid_s *gid = NULL; + char *name; + name = (datap->present & PP_ENTITY_NAME) ? datap->entity_name : ""; + new_proxy_group (&datap->group_guid, gid, name, &datap->qos, timestamp); + } +err: + return; +#undef E +} + +static void handle_SEDP_GROUP_dead (nn_plist_t *datap, nn_wctime_t timestamp) +{ + if (!(datap->present & PP_GROUP_GUID)) + { + nn_log (LC_DISCOVERY, " no guid?\n"); + return; + } + nn_log (LC_DISCOVERY, " %x:%x:%x:%x\n", PGUID (datap->group_guid)); + delete_proxy_group (&datap->group_guid, timestamp, 0); +} + +static void handle_SEDP_GROUP (const struct receiver_state *rst, nn_wctime_t timestamp, unsigned statusinfo, const void *vdata, unsigned len) +{ + const struct CDRHeader *data = vdata; /* built-ins not deserialized (yet) */ + nn_log (LC_DISCOVERY, "SEDP_GROUP ST%x", statusinfo); + if (data == NULL) + { + nn_log (LC_DISCOVERY, " no payload?\n"); + return; + } + else + { + nn_plist_t decoded_data; + nn_plist_src_t src; + src.protocol_version = rst->protocol_version; + src.vendorid = rst->vendor; + src.encoding = data->identifier; + src.buf = (unsigned char *) data + 4; + src.bufsz = len - 4; + if (nn_plist_init_frommsg (&decoded_data, NULL, ~(uint64_t)0, ~(uint64_t)0, &src) < 0) + { + NN_WARNING ("SEDP_GROUP (vendor %u.%u): invalid qos/parameters\n", src.vendorid.id[0], src.vendorid.id[1]); + return; + } + + switch (statusinfo & (NN_STATUSINFO_DISPOSE | NN_STATUSINFO_UNREGISTER)) + { + case 0: + handle_SEDP_GROUP_alive (&decoded_data, timestamp); + break; + + case NN_STATUSINFO_DISPOSE: + case NN_STATUSINFO_UNREGISTER: + case (NN_STATUSINFO_DISPOSE | NN_STATUSINFO_UNREGISTER): + handle_SEDP_GROUP_dead (&decoded_data, timestamp); + break; + } + + nn_plist_fini (&decoded_data); + } +} + +/****************************************************************************** + *****************************************************************************/ + +/* FIXME: defragment is a copy of the one in q_receive.c, but the deserialised should be enhanced to handle fragmented data (and arguably the processing here should be built on proper data readers) */ +static int defragment (unsigned char **datap, const struct nn_rdata *fragchain, uint32_t sz) +{ + if (fragchain->nextfrag == NULL) + { + *datap = NN_RMSG_PAYLOADOFF (fragchain->rmsg, NN_RDATA_PAYLOAD_OFF (fragchain)); + return 0; + } + else + { + unsigned char *buf; + uint32_t off = 0; + buf = os_malloc (sz); + while (fragchain) + { + assert (fragchain->min <= off); + assert (fragchain->maxp1 <= sz); + if (fragchain->maxp1 > off) + { + /* only copy if this fragment adds data */ + const unsigned char *payload = NN_RMSG_PAYLOADOFF (fragchain->rmsg, NN_RDATA_PAYLOAD_OFF (fragchain)); + memcpy (buf + off, payload + off - fragchain->min, fragchain->maxp1 - off); + off = fragchain->maxp1; + } + fragchain = fragchain->nextfrag; + } + *datap = buf; + return 1; + } +} + +int builtins_dqueue_handler (const struct nn_rsample_info *sampleinfo, const struct nn_rdata *fragchain, UNUSED_ARG (const nn_guid_t *rdguid), UNUSED_ARG (void *qarg)) +{ + struct proxy_writer *pwr; + struct { + struct CDRHeader cdr; + nn_parameter_t p_endpoint_guid; + char kh[16]; + nn_parameter_t p_sentinel; + } keyhash_payload; + unsigned statusinfo; + int need_keyhash; + nn_guid_t srcguid; + Data_DataFrag_common_t *msg; + unsigned char data_smhdr_flags; + nn_plist_t qos; + unsigned char *datap; + int needs_free; + uint32_t datasz = sampleinfo->size; + nn_wctime_t timestamp; + + needs_free = defragment (&datap, fragchain, sampleinfo->size); + + /* Luckily, most of the Data and DataFrag headers are the same - and + in particular, all that we care about here is the same. The + key/data flags of DataFrag are different from those of Data, but + DDSI2 used to treat them all as if they are data :( so now, + instead of splitting out all the code, we reformat these flags + from the submsg to always conform to that of the "Data" + submessage regardless of the input. */ + msg = (Data_DataFrag_common_t *) NN_RMSG_PAYLOADOFF (fragchain->rmsg, NN_RDATA_SUBMSG_OFF (fragchain)); + data_smhdr_flags = normalize_data_datafrag_flags (&msg->smhdr, config.buggy_datafrag_flags_mode); + srcguid.prefix = sampleinfo->rst->src_guid_prefix; + srcguid.entityid = msg->writerId; + + pwr = sampleinfo->pwr; + if (pwr == NULL) + assert (srcguid.entityid.u == NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER); + else + { + assert (is_builtin_entityid (pwr->e.guid.entityid, pwr->c.vendor)); + assert (memcmp (&pwr->e.guid, &srcguid, sizeof (srcguid)) == 0); + assert (srcguid.entityid.u != NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER); + } + + /* If there is no payload, it is either a completely invalid message + or a dispose/unregister in RTI style. We assume the latter, + consequently expect to need the keyhash. Then, if sampleinfo + says it is a complex qos, or the keyhash is required, extract all + we need from the inline qos. */ + need_keyhash = (datasz == 0 || (data_smhdr_flags & (DATA_FLAG_KEYFLAG | DATA_FLAG_DATAFLAG)) == 0); + if (!(sampleinfo->complex_qos || need_keyhash)) + { + nn_plist_init_empty (&qos); + statusinfo = sampleinfo->statusinfo; + } + else + { + nn_plist_src_t src; + size_t qos_offset = NN_RDATA_SUBMSG_OFF (fragchain) + offsetof (Data_DataFrag_common_t, octetsToInlineQos) + sizeof (msg->octetsToInlineQos) + msg->octetsToInlineQos; + src.protocol_version = sampleinfo->rst->protocol_version; + src.vendorid = sampleinfo->rst->vendor; + src.encoding = (msg->smhdr.flags & SMFLAG_ENDIANNESS) ? PL_CDR_LE : PL_CDR_BE; + src.buf = NN_RMSG_PAYLOADOFF (fragchain->rmsg, qos_offset); + src.bufsz = NN_RDATA_PAYLOAD_OFF (fragchain) - qos_offset; + if (nn_plist_init_frommsg (&qos, NULL, PP_STATUSINFO | PP_KEYHASH, 0, &src) < 0) + { + NN_WARNING ("data(builtin, vendor %u.%u): %x:%x:%x:%x #%"PRId64": invalid inline qos\n", + src.vendorid.id[0], src.vendorid.id[1], PGUID (srcguid), sampleinfo->seq); + goto done_upd_deliv; + } + /* Complex qos bit also gets set when statusinfo bits other than + dispose/unregister are set. They are not currently defined, + but this may save us if they do get defined one day. */ + statusinfo = (qos.present & PP_STATUSINFO) ? qos.statusinfo : 0; + } + + if (pwr && ut_avlIsEmpty (&pwr->readers)) + { + /* Wasn't empty when enqueued, but needn't still be; SPDP has no + proxy writer, and is always accepted */ + goto done_upd_deliv; + } + + /* Built-ins still do their own deserialization (SPDP <=> pwr == + NULL)). */ + assert (pwr == NULL || pwr->c.topic == NULL); + if (statusinfo == 0) + { + if (datasz == 0 || !(data_smhdr_flags & DATA_FLAG_DATAFLAG)) + { + NN_WARNING ("data(builtin, vendor %u.%u): %x:%x:%x:%x #%"PRId64": " + "built-in data but no payload\n", + sampleinfo->rst->vendor.id[0], sampleinfo->rst->vendor.id[1], + PGUID (srcguid), sampleinfo->seq); + goto done_upd_deliv; + } + } + else if (datasz) + { + /* Raw data must be full payload for write, just keys for + dispose and unregister. First has been checked; the second + hasn't been checked fully yet. */ + if (!(data_smhdr_flags & DATA_FLAG_KEYFLAG)) + { + NN_WARNING ("data(builtin, vendor %u.%u): %x:%x:%x:%x #%"PRId64": " + "dispose/unregister of built-in data but payload not just key\n", + sampleinfo->rst->vendor.id[0], sampleinfo->rst->vendor.id[1], + PGUID (srcguid), sampleinfo->seq); + goto done_upd_deliv; + } + } + else if ((qos.present & PP_KEYHASH) && !NN_STRICT_P) + { + /* For SPDP/SEDP, fake a parameter list with just a keyhash. For + PMD, just use the keyhash directly. Too hard to fix everything + at the same time ... */ + if (srcguid.entityid.u == NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_WRITER) + { + datap = qos.keyhash.value; + datasz = sizeof (qos.keyhash); + } + else + { + nn_parameterid_t pid; + keyhash_payload.cdr.identifier = PLATFORM_IS_LITTLE_ENDIAN ? PL_CDR_LE : PL_CDR_BE; + keyhash_payload.cdr.options = 0; + switch (srcguid.entityid.u) + { + case NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER: + case NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_WRITER: + pid = PID_PARTICIPANT_GUID; + break; + case NN_ENTITYID_SEDP_BUILTIN_CM_PUBLISHER_WRITER: + case NN_ENTITYID_SEDP_BUILTIN_CM_SUBSCRIBER_WRITER: + pid = PID_GROUP_GUID; + break; + case NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_WRITER: + case NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_WRITER: + pid = PID_ENDPOINT_GUID; + break; + case NN_ENTITYID_SEDP_BUILTIN_TOPIC_WRITER: + case NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_WRITER: + /* placeholders */ + pid = PID_ENDPOINT_GUID; + break; + default: + NN_WARNING ("data(builtin, vendor %u.%u): %x:%x:%x:%x #%"PRId64": mapping keyhash to ENDPOINT_GUID", + sampleinfo->rst->vendor.id[0], sampleinfo->rst->vendor.id[1], + PGUID (srcguid), sampleinfo->seq); + pid = PID_ENDPOINT_GUID; + break; + } + keyhash_payload.p_endpoint_guid.parameterid = pid; + keyhash_payload.p_endpoint_guid.length = sizeof (nn_keyhash_t); + memcpy (keyhash_payload.kh, &qos.keyhash, sizeof (qos.keyhash)); + keyhash_payload.p_sentinel.parameterid = PID_SENTINEL; + keyhash_payload.p_sentinel.length = 0; + datap = (unsigned char *) &keyhash_payload; + datasz = sizeof (keyhash_payload); + } + } + else + { + NN_WARNING ("data(builtin, vendor %u.%u): %x:%x:%x:%x #%"PRId64": " + "dispose/unregister with no content\n", + sampleinfo->rst->vendor.id[0], sampleinfo->rst->vendor.id[1], + PGUID (srcguid), sampleinfo->seq); + goto done_upd_deliv; + } + + timestamp = valid_ddsi_timestamp(sampleinfo->timestamp) ? nn_wctime_from_ddsi_time(sampleinfo->timestamp): now(); + switch (srcguid.entityid.u) + { + case NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER: + handle_SPDP (sampleinfo->rst, timestamp, statusinfo, datap, datasz); + break; + case NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_WRITER: + case NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_WRITER: + handle_SEDP (sampleinfo->rst, timestamp, statusinfo, datap, datasz); + break; + case NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_WRITER: + handle_PMD (sampleinfo->rst, timestamp, statusinfo, datap, datasz); + break; + case NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_WRITER: + handle_SEDP_CM (sampleinfo->rst, srcguid.entityid, timestamp, statusinfo, datap, datasz); + break; + case NN_ENTITYID_SEDP_BUILTIN_CM_PUBLISHER_WRITER: + case NN_ENTITYID_SEDP_BUILTIN_CM_SUBSCRIBER_WRITER: + handle_SEDP_GROUP (sampleinfo->rst, timestamp, statusinfo, datap, datasz); + break; + default: + NN_WARNING ("data(builtin, vendor %u.%u): %x:%x:%x:%x #%"PRId64": not handled\n", + sampleinfo->rst->vendor.id[0], sampleinfo->rst->vendor.id[1], + PGUID (srcguid), sampleinfo->seq); + break; + } + + done_upd_deliv: + if (needs_free) + os_free (datap); + if (pwr) + { + /* No proxy writer for SPDP */ + os_atomic_st32 (&pwr->next_deliv_seq_lowword, (uint32_t) (sampleinfo->seq + 1)); + } + return 0; +} diff --git a/src/core/ddsi/src/q_debmon.c b/src/core/ddsi/src/q_debmon.c new file mode 100644 index 0000000..c4ef91f --- /dev/null +++ b/src/core/ddsi/src/q_debmon.c @@ -0,0 +1,433 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include + +#include "os/os.h" + +#include "util/ut_avl.h" + +#include "ddsi/q_entity.h" +#include "ddsi/q_config.h" +#include "ddsi/q_time.h" +#include "ddsi/q_misc.h" +#include "ddsi/q_log.h" +#include "ddsi/q_whc.h" +#include "ddsi/q_plist.h" +#include "q__osplser.h" +#include "ddsi/q_ephash.h" +#include "ddsi/q_globals.h" +#include "ddsi/q_addrset.h" +#include "ddsi/q_radmin.h" +#include "ddsi/q_ddsi_discovery.h" +#include "ddsi/q_protocol.h" /* NN_ENTITYID_... */ +#include "ddsi/q_unused.h" +#include "ddsi/q_error.h" +#include "ddsi/q_debmon.h" +#include "ddsi/ddsi_ser.h" +#include "ddsi/ddsi_tran.h" +#include "ddsi/ddsi_tcp.h" + +#include "ddsi/sysdeps.h" + + +struct plugin { + debug_monitor_plugin_t fn; + void *arg; + struct plugin *next; +}; + +struct debug_monitor { + struct thread_state1 *servts; + ddsi_tran_factory_t tran_factory; + ddsi_tran_listener_t servsock; + os_mutex lock; + os_cond cond; + struct plugin *plugins; + int stop; +}; + +static int cpf (ddsi_tran_conn_t conn, const char *fmt, ...) +{ + nn_locator_t loc; + if (!ddsi_conn_peer_locator (conn, &loc)) + return 0; + else + { + os_sockaddr_storage addr; + va_list ap; + struct msghdr msg; + struct iovec iov; + char buf[4096]; + int n; + nn_loc_to_address(&addr, &loc); + va_start (ap, fmt); + n = os_vsnprintf (buf, sizeof (buf), fmt, ap); + va_end (ap); + iov.iov_base = buf; + iov.iov_len = (size_t) n; + memset (&msg, 0, sizeof (msg)); + msg.msg_iov = &iov; + msg.msg_iovlen = 1; + msg.msg_name = &addr; + msg.msg_namelen = (socklen_t) os_sockaddrSizeof ((os_sockaddr*) &addr); + return ddsi_conn_write (conn, &msg, iov.iov_len, 0) < 0 ? -1 : 0; + } +} + +struct print_address_arg { + ddsi_tran_conn_t conn; + int count; +}; + +static void print_address (const nn_locator_t *n, void *varg) +{ + struct print_address_arg *arg = varg; + char buf[INET6_ADDRSTRLEN_EXTENDED]; + arg->count += cpf (arg->conn, " %s", locator_to_string_with_port (buf, n)); +} + +static int print_addrset (ddsi_tran_conn_t conn, const char *prefix, struct addrset *as, const char *suffix) +{ + struct print_address_arg pa_arg; + pa_arg.conn = conn; + pa_arg.count = cpf (conn, "%s", prefix); + addrset_forall(as, print_address, &pa_arg); + pa_arg.count += cpf (conn, "%s", suffix); + return pa_arg.count; +} + +static int print_addrset_if_notempty (ddsi_tran_conn_t conn, const char *prefix, struct addrset *as, const char *suffix) +{ + if (addrset_empty(as)) + return 0; + else + return print_addrset (conn, prefix, as, suffix); +} + +static int print_any_endpoint_common (ddsi_tran_conn_t conn, const char *label, const struct entity_common *e, + const struct nn_xqos *xqos, const struct sertopic *topic) +{ + int x = 0; + x += cpf (conn, " %s %x:%x:%x:%x ", label, PGUID (e->guid)); + if (xqos->present & QP_PARTITION) + { + unsigned i; + if (xqos->partition.n > 1) cpf (conn, "{"); + for (i = 0; i < xqos->partition.n; i++) + x += cpf (conn, "%s%s", i == 0 ? "" : ",", xqos->partition.strs[i]); + if (xqos->partition.n > 1) cpf (conn, "}"); + x += cpf (conn, ".%s/%s", + topic && topic->name ? topic->name : (xqos->present & QP_TOPIC_NAME) ? xqos->topic_name : "(null)", + topic && topic->typename ? topic->typename : (xqos->present & QP_TYPE_NAME) ? xqos->type_name : "(null)"); + } + cpf (conn, "\n"); + return x; +} + +static int print_endpoint_common (ddsi_tran_conn_t conn, const char *label, const struct entity_common *e, const struct endpoint_common *c, const struct nn_xqos *xqos, const struct sertopic *topic) +{ + OS_UNUSED_ARG (c); + return print_any_endpoint_common (conn, label, e, xqos, topic); +} + +static int print_proxy_endpoint_common (ddsi_tran_conn_t conn, const char *label, const struct entity_common *e, const struct proxy_endpoint_common *c) +{ + int x = 0; + x += print_any_endpoint_common (conn, label, e, c->xqos, c->topic); + x += print_addrset_if_notempty (conn, " as", c->as, "\n"); + return x; +} + + +static int print_participants (struct thread_state1 *self, ddsi_tran_conn_t conn) +{ + struct ephash_enum_participant e; + struct participant *p; + int x = 0; + thread_state_awake (self); + ephash_enum_participant_init (&e); + while ((p = ephash_enum_participant_next (&e)) != NULL) + { + os_mutexLock (&p->e.lock); + x += cpf (conn, "pp %x:%x:%x:%x %s%s\n", PGUID (p->e.guid), p->e.name, p->is_ddsi2_pp ? " [ddsi2]" : ""); + os_mutexUnlock (&p->e.lock); + + { + struct ephash_enum_reader er; + struct reader *r; + ephash_enum_reader_init (&er); + while ((r = ephash_enum_reader_next (&er)) != NULL) + { + ut_avlIter_t writ; + struct rd_pwr_match *m; + if (r->c.pp != p) + continue; + os_mutexLock (&r->e.lock); + print_endpoint_common (conn, "rd", &r->e, &r->c, r->xqos, r->topic); +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + x += print_addrset_if_notempty (conn, " as", r->as, "\n"); +#endif + for (m = ut_avlIterFirst (&rd_writers_treedef, &r->writers, &writ); m; m = ut_avlIterNext (&writ)) + x += cpf (conn, " pwr %x:%x:%x:%x\n", PGUID (m->pwr_guid)); + os_mutexUnlock (&r->e.lock); + } + ephash_enum_reader_fini (&er); + } + + { + struct ephash_enum_writer ew; + struct writer *w; + ephash_enum_writer_init (&ew); + while ((w = ephash_enum_writer_next (&ew)) != NULL) + { + ut_avlIter_t rdit; + struct wr_prd_match *m; + if (w->c.pp != p) + continue; + os_mutexLock (&w->e.lock); + print_endpoint_common (conn, "wr", &w->e, &w->c, w->xqos, w->topic); + x += cpf (conn, " whc [%lld,%lld] unacked %"PRIuSIZE"%s [%u,%u] seq %lld seq_xmit %lld cs_seq %lld\n", + whc_empty (w->whc) ? -1 : whc_min_seq (w->whc), + whc_empty (w->whc) ? -1 : whc_max_seq (w->whc), + whc_unacked_bytes (w->whc), + w->throttling ? " THROTTLING" : "", + w->whc_low, w->whc_high, + w->seq, READ_SEQ_XMIT(w), w->cs_seq); + if (w->reliable) + { + x += cpf (conn, " hb %u ackhb %lld hb %lld wr %lld sched %lld #rel %d\n", + w->hbcontrol.hbs_since_last_write, w->hbcontrol.t_of_last_ackhb, + w->hbcontrol.t_of_last_hb, w->hbcontrol.t_of_last_write, + w->hbcontrol.tsched, w->num_reliable_readers); + x += cpf (conn, " #acks %u #nacks %u #rexmit %u #lost %u #throttle %u\n", + w->num_acks_received, w->num_nacks_received, w->rexmit_count, w->rexmit_lost_count, w->throttle_count); + x += cpf (conn, " max-drop-seq %lld\n", writer_max_drop_seq (w)); + } + x += print_addrset_if_notempty (conn, " as", w->as, "\n"); + for (m = ut_avlIterFirst (&wr_readers_treedef, &w->readers, &rdit); m; m = ut_avlIterNext (&rdit)) + { + char wr_prd_flags[4]; + wr_prd_flags[0] = m->is_reliable ? 'R' : 'U'; + wr_prd_flags[1] = m->assumed_in_sync ? 's' : '.'; + wr_prd_flags[2] = m->has_replied_to_hb ? 'a' : '.'; /* a = ack seen */ + wr_prd_flags[3] = 0; + x += cpf (conn, " prd %x:%x:%x:%x %s @ %lld [%lld,%lld] #nacks %u\n", + PGUID (m->prd_guid), wr_prd_flags, m->seq, m->min_seq, m->max_seq, m->rexmit_requests); + } + os_mutexUnlock (&w->e.lock); + } + ephash_enum_writer_fini (&ew); + } + } + ephash_enum_participant_fini (&e); + thread_state_asleep (self); + return x; +} + +static int print_proxy_participants (struct thread_state1 *self, ddsi_tran_conn_t conn) +{ + struct ephash_enum_proxy_participant e; + struct proxy_participant *p; + int x = 0; + thread_state_awake (self); + ephash_enum_proxy_participant_init (&e); + while ((p = ephash_enum_proxy_participant_next (&e)) != NULL) + { + os_mutexLock (&p->e.lock); + x += cpf (conn, "proxypp %x:%x:%x:%x%s\n", PGUID (p->e.guid), p->is_ddsi2_pp ? " [ddsi2]" : ""); + os_mutexUnlock (&p->e.lock); + x += print_addrset (conn, " as data", p->as_default, ""); + x += print_addrset (conn, " meta", p->as_default, "\n"); + + { + struct ephash_enum_proxy_reader er; + struct proxy_reader *r; + ephash_enum_proxy_reader_init (&er); + while ((r = ephash_enum_proxy_reader_next (&er)) != NULL) + { + ut_avlIter_t writ; + struct prd_wr_match *m; + if (r->c.proxypp != p) + continue; + os_mutexLock (&r->e.lock); + print_proxy_endpoint_common (conn, "prd", &r->e, &r->c); + for (m = ut_avlIterFirst (&rd_writers_treedef, &r->writers, &writ); m; m = ut_avlIterNext (&writ)) + x += cpf (conn, " wr %x:%x:%x:%x\n", PGUID (m->wr_guid)); + os_mutexUnlock (&r->e.lock); + } + ephash_enum_proxy_reader_fini (&er); + } + + { + struct ephash_enum_proxy_writer ew; + struct proxy_writer *w; + ephash_enum_proxy_writer_init (&ew); + while ((w = ephash_enum_proxy_writer_next (&ew)) != NULL) + { + ut_avlIter_t rdit; + struct pwr_rd_match *m; + if (w->c.proxypp != p) + continue; + os_mutexLock (&w->e.lock); + print_proxy_endpoint_common (conn, "pwr", &w->e, &w->c); + x += cpf (conn, " last_seq %lld last_fragnum %u\n", w->last_seq, w->last_fragnum); + for (m = ut_avlIterFirst (&wr_readers_treedef, &w->readers, &rdit); m; m = ut_avlIterNext (&rdit)) + { + x += cpf (conn, " rd %x:%x:%x:%x (nack %lld %lld)\n", + PGUID (m->rd_guid), m->seq_last_nack, m->t_last_nack); + switch (m->in_sync) + { + case PRMSS_SYNC: + break; + case PRMSS_TLCATCHUP: + x += cpf (conn, " tl-catchup end_of_tl_seq %lld\n", m->u.not_in_sync.end_of_tl_seq); + break; + case PRMSS_OUT_OF_SYNC: + x += cpf (conn, " out-of-sync end_of_tl_seq %lld end_of_out_of_sync_seq %lld\n", m->u.not_in_sync.end_of_tl_seq, m->u.not_in_sync.end_of_out_of_sync_seq); + break; + } + } + os_mutexUnlock (&w->e.lock); + } + ephash_enum_proxy_writer_fini (&ew); + } + } + ephash_enum_proxy_participant_fini (&e); + thread_state_asleep (self); + return x; +} + +static uint32_t debmon_main (void *vdm) +{ + struct debug_monitor *dm = vdm; + os_mutexLock (&dm->lock); + while (!dm->stop) + { + ddsi_tran_conn_t conn; + os_mutexUnlock (&dm->lock); + if ((conn = ddsi_listener_accept (dm->servsock)) != NULL) + { + struct plugin *p; + int r = 0; + r += print_participants (dm->servts, conn); + if (r == 0) + r += print_proxy_participants (dm->servts, conn); + + /* Note: can only add plugins (at the tail) */ + os_mutexLock (&dm->lock); + p = dm->plugins; + while (r == 0 && p != NULL) + { + os_mutexUnlock (&dm->lock); + r += p->fn (conn, cpf, p->arg); + os_mutexLock (&dm->lock); + p = p->next; + } + os_mutexUnlock (&dm->lock); + + ddsi_conn_free (conn); + } + os_mutexLock (&dm->lock); + } + os_mutexUnlock (&dm->lock); + return 0; +} + +struct debug_monitor *new_debug_monitor (int port) +{ + struct debug_monitor *dm; + + if (config.monitor_port < 0) + return NULL; + + if (ddsi_tcp_init () < 0) + return NULL; + + dm = os_malloc (sizeof (*dm)); + + dm->plugins = NULL; + dm->tran_factory = ddsi_factory_find ("tcp"); + dm->servsock = ddsi_factory_create_listener (dm->tran_factory, port, NULL); + if (dm->servsock == NULL) + { + NN_WARNING ("debmon: can't create socket\n"); + goto err_servsock; + } + + { + nn_locator_t loc; + os_sockaddr_storage addr; + char buf[INET6_ADDRSTRLEN_EXTENDED]; + (void) ddsi_listener_locator(dm->servsock, &loc); + nn_loc_to_address (&addr, &loc); + nn_log (LC_CONFIG, "debmon at %s\n", sockaddr_to_string_with_port (buf, &addr)); + } + + os_mutexInit (&dm->lock); + os_condInit (&dm->cond, &dm->lock); + if (ddsi_listener_listen (dm->servsock) < 0) + goto err_listen; + dm->stop = 0; + dm->servts = create_thread("debmon", debmon_main, dm); + return dm; + +err_listen: + os_condDestroy(&dm->cond); + os_mutexDestroy(&dm->lock); + ddsi_listener_free(dm->servsock); +err_servsock: + os_free(dm); + return NULL; +} + +void add_debug_monitor_plugin (struct debug_monitor *dm, debug_monitor_plugin_t fn, void *arg) +{ + struct plugin *p, **pp; + if (dm != NULL && (p = os_malloc (sizeof (*p))) != NULL) + { + p->fn = fn; + p->arg = arg; + p->next = NULL; + os_mutexLock (&dm->lock); + pp = &dm->plugins; + while (*pp) + pp = &(*pp)->next; + *pp = p; + os_mutexUnlock (&dm->lock); + } +} + +void free_debug_monitor (struct debug_monitor *dm) +{ + if (dm == NULL) + return; + + os_mutexLock (&dm->lock); + dm->stop = 1; + os_condBroadcast (&dm->cond); + os_mutexUnlock (&dm->lock); + ddsi_listener_unblock (dm->servsock); + join_thread (dm->servts); + ddsi_listener_free (dm->servsock); + os_condDestroy (&dm->cond); + os_mutexDestroy (&dm->lock); + + while (dm->plugins) { + struct plugin *p = dm->plugins; + dm->plugins = p->next; + os_free (p); + } + os_free (dm); +} + diff --git a/src/core/ddsi/src/q_entity.c b/src/core/ddsi/src/q_entity.c new file mode 100644 index 0000000..62dacdc --- /dev/null +++ b/src/core/ddsi/src/q_entity.c @@ -0,0 +1,4480 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include + +#include "os/os.h" + + +#include "ddsi/q_entity.h" +#include "ddsi/q_config.h" +#include "ddsi/q_time.h" +#include "ddsi/q_misc.h" +#include "ddsi/q_log.h" +#include "util/ut_avl.h" +#include "ddsi/q_whc.h" +#include "ddsi/q_plist.h" +#include "ddsi/q_lease.h" +#include "q__osplser.h" +#include "ddsi/q_qosmatch.h" +#include "ddsi/q_ephash.h" +#include "ddsi/q_globals.h" +#include "ddsi/q_addrset.h" +#include "ddsi/q_xevent.h" /* qxev_spdp, &c. */ +#include "ddsi/q_ddsi_discovery.h" /* spdp_write, &c. */ +#include "ddsi/q_gc.h" +#include "ddsi/q_radmin.h" +#include "ddsi/q_protocol.h" /* NN_ENTITYID_... */ +#include "ddsi/q_unused.h" +#include "ddsi/q_error.h" +#include "ddsi/q_builtin_topic.h" +#include "ddsi/ddsi_ser.h" + +#include "ddsi/sysdeps.h" + +struct deleted_participant { + ut_avlNode_t avlnode; + nn_guid_t guid; + unsigned for_what; + nn_mtime_t t_prune; +}; + +static os_mutex deleted_participants_lock; +static ut_avlTree_t deleted_participants; +static const nn_vendorid_t ownvendorid = MY_VENDOR_ID; + +static int compare_guid (const void *va, const void *vb); +static void augment_wr_prd_match (void *vnode, const void *vleft, const void *vright); + +const ut_avlTreedef_t wr_readers_treedef = + UT_AVL_TREEDEF_INITIALIZER (offsetof (struct wr_prd_match, avlnode), offsetof (struct wr_prd_match, prd_guid), compare_guid, augment_wr_prd_match); +const ut_avlTreedef_t wr_local_readers_treedef = + UT_AVL_TREEDEF_INITIALIZER (offsetof (struct wr_rd_match, avlnode), offsetof (struct wr_rd_match, rd_guid), compare_guid, 0); +const ut_avlTreedef_t rd_writers_treedef = + UT_AVL_TREEDEF_INITIALIZER (offsetof (struct rd_pwr_match, avlnode), offsetof (struct rd_pwr_match, pwr_guid), compare_guid, 0); +const ut_avlTreedef_t rd_local_writers_treedef = + UT_AVL_TREEDEF_INITIALIZER (offsetof (struct rd_wr_match, avlnode), offsetof (struct rd_wr_match, wr_guid), compare_guid, 0); +const ut_avlTreedef_t pwr_readers_treedef = + UT_AVL_TREEDEF_INITIALIZER (offsetof (struct pwr_rd_match, avlnode), offsetof (struct pwr_rd_match, rd_guid), compare_guid, 0); +const ut_avlTreedef_t prd_writers_treedef = + UT_AVL_TREEDEF_INITIALIZER (offsetof (struct prd_wr_match, avlnode), offsetof (struct prd_wr_match, wr_guid), compare_guid, 0); +const ut_avlTreedef_t deleted_participants_treedef = + UT_AVL_TREEDEF_INITIALIZER (offsetof (struct deleted_participant, avlnode), offsetof (struct deleted_participant, guid), compare_guid, 0); +const ut_avlTreedef_t proxypp_groups_treedef = + UT_AVL_TREEDEF_INITIALIZER (offsetof (struct proxy_group, avlnode), offsetof (struct proxy_group, guid), compare_guid, 0); + +static const unsigned builtin_writers_besmask = + NN_DISC_BUILTIN_ENDPOINT_PARTICIPANT_ANNOUNCER | + NN_DISC_BUILTIN_ENDPOINT_SUBSCRIPTION_ANNOUNCER | + NN_DISC_BUILTIN_ENDPOINT_PUBLICATION_ANNOUNCER | + NN_BUILTIN_ENDPOINT_PARTICIPANT_MESSAGE_DATA_WRITER; +static const unsigned prismtech_builtin_writers_besmask = + NN_DISC_BUILTIN_ENDPOINT_CM_PARTICIPANT_WRITER | + NN_DISC_BUILTIN_ENDPOINT_CM_PUBLISHER_WRITER | + NN_DISC_BUILTIN_ENDPOINT_CM_SUBSCRIBER_WRITER; + +static struct writer * new_writer_guid +( + const struct nn_guid *guid, + const struct nn_guid *group_guid, + struct participant *pp, + const struct sertopic *topic, + const struct nn_xqos *xqos, + status_cb_t status_cb, + void *status_cbarg +); +static struct reader * new_reader_guid +( + const struct nn_guid *guid, + const struct nn_guid *group_guid, + struct participant *pp, + const struct sertopic *topic, + const struct nn_xqos *xqos, + struct rhc *rhc, + status_cb_t status_cb, + void *status_cbarg +); +static struct participant *ref_participant (struct participant *pp, const struct nn_guid *guid_of_refing_entity); +static void unref_participant (struct participant *pp, const struct nn_guid *guid_of_refing_entity); +static void delete_proxy_group_locked (struct proxy_group *pgroup, nn_wctime_t timestamp, int isimplicit); + +static int gcreq_participant (struct participant *pp); +static int gcreq_writer (struct writer *wr); +static int gcreq_reader (struct reader *rd); +static int gcreq_proxy_participant (struct proxy_participant *proxypp); +static int gcreq_proxy_writer (struct proxy_writer *pwr); +static int gcreq_proxy_reader (struct proxy_reader *prd); + +static int compare_guid (const void *va, const void *vb) +{ + return memcmp (va, vb, sizeof (nn_guid_t)); +} + +nn_entityid_t to_entityid (unsigned u) +{ + nn_entityid_t e; + e.u = u; + return e; +} + +int is_writer_entityid (nn_entityid_t id) +{ + switch (id.u & NN_ENTITYID_KIND_MASK) + { + case NN_ENTITYID_KIND_WRITER_WITH_KEY: + case NN_ENTITYID_KIND_WRITER_NO_KEY: + return 1; + default: + return 0; + } +} + +int is_reader_entityid (nn_entityid_t id) +{ + switch (id.u & NN_ENTITYID_KIND_MASK) + { + case NN_ENTITYID_KIND_READER_WITH_KEY: + case NN_ENTITYID_KIND_READER_NO_KEY: + return 1; + default: + return 0; + } +} + +int is_builtin_entityid (nn_entityid_t id, nn_vendorid_t vendorid) +{ + if ((id.u & NN_ENTITYID_SOURCE_MASK) == NN_ENTITYID_SOURCE_BUILTIN) + return 1; + else if ((id.u & NN_ENTITYID_SOURCE_MASK) != NN_ENTITYID_SOURCE_VENDOR) + return 0; + else if (!vendor_is_prismtech (vendorid)) + return 0; + else + { + /* Currently only SOURCE_VENDOR entities are for CM "topics". */ + return 1; + } +} + +static int is_builtin_endpoint (nn_entityid_t id, nn_vendorid_t vendorid) +{ + return is_builtin_entityid (id, vendorid) && id.u != NN_ENTITYID_PARTICIPANT; +} + +static void entity_common_init (struct entity_common *e, const struct nn_guid *guid, const char *name, enum entity_kind kind, bool onlylocal) +{ + e->guid = *guid; + e->kind = kind; + e->name = os_strdup (name ? name : ""); + e->iid = (ddsi_plugin.iidgen_fn) (); + os_mutexInit (&e->lock); + e->onlylocal = onlylocal; +} + +static void entity_common_fini (struct entity_common *e) +{ + os_free (e->name); + os_mutexDestroy (&e->lock); +} + +void local_reader_ary_init (struct local_reader_ary *x) +{ + os_mutexInit (&x->rdary_lock); + x->valid = 1; + x->fastpath_ok = 1; + x->n_readers = 0; + x->rdary = os_malloc (sizeof (*x->rdary)); + x->rdary[0] = NULL; +} + +void local_reader_ary_fini (struct local_reader_ary *x) +{ + os_free (x->rdary); + os_mutexDestroy (&x->rdary_lock); +} + +void local_reader_ary_insert (struct local_reader_ary *x, struct reader *rd) +{ + os_mutexLock (&x->rdary_lock); + x->n_readers++; + x->rdary = os_realloc (x->rdary, (x->n_readers + 1) * sizeof (*x->rdary)); + x->rdary[x->n_readers - 1] = rd; + x->rdary[x->n_readers] = NULL; + os_mutexUnlock (&x->rdary_lock); +} + +void local_reader_ary_remove (struct local_reader_ary *x, struct reader *rd) +{ + int i; + os_mutexLock (&x->rdary_lock); + for (i = 0; i < x->n_readers; i++) + { + if (x->rdary[i] == rd) + break; + } + assert (i < x->n_readers); + /* if i == N-1 copy is a no-op */ + x->rdary[i] = x->rdary[x->n_readers-1]; + x->n_readers--; + x->rdary[x->n_readers] = NULL; + x->rdary = os_realloc (x->rdary, (x->n_readers + 1) * sizeof (*x->rdary)); + os_mutexUnlock (&x->rdary_lock); +} + +void local_reader_ary_setinvalid (struct local_reader_ary *x) +{ + os_mutexLock (&x->rdary_lock); + x->valid = 0; + x->fastpath_ok = 0; + os_mutexUnlock (&x->rdary_lock); +} + +/* DELETED PARTICIPANTS --------------------------------------------- */ + +int deleted_participants_admin_init (void) +{ + os_mutexInit (&deleted_participants_lock); + ut_avlInit (&deleted_participants_treedef, &deleted_participants); + return 0; +} + +void deleted_participants_admin_fini (void) +{ + ut_avlFree (&deleted_participants_treedef, &deleted_participants, os_free); + os_mutexDestroy (&deleted_participants_lock); +} + +static void prune_deleted_participant_guids_unlocked (nn_mtime_t tnow) +{ + /* Could do a better job of finding prunable ones efficiently under + all circumstances, but I expect the tree to be very small at all + times, so a full scan is fine, too ... */ + struct deleted_participant *dpp; + dpp = ut_avlFindMin (&deleted_participants_treedef, &deleted_participants); + while (dpp) + { + struct deleted_participant *dpp1 = ut_avlFindSucc (&deleted_participants_treedef, &deleted_participants, dpp); + if (dpp->t_prune.v < tnow.v) + { + ut_avlDelete (&deleted_participants_treedef, &deleted_participants, dpp); + os_free (dpp); + } + dpp = dpp1; + } +} + +static void prune_deleted_participant_guids (nn_mtime_t tnow) +{ + os_mutexLock (&deleted_participants_lock); + prune_deleted_participant_guids_unlocked (tnow); + os_mutexUnlock (&deleted_participants_lock); +} + +static void remember_deleted_participant_guid (const struct nn_guid *guid) +{ + struct deleted_participant *n; + ut_avlIPath_t path; + os_mutexLock (&deleted_participants_lock); + if (ut_avlLookupIPath (&deleted_participants_treedef, &deleted_participants, guid, &path) == NULL) + { + if ((n = os_malloc (sizeof (*n))) != NULL) + { + n->guid = *guid; + n->t_prune.v = T_NEVER; + n->for_what = DPG_LOCAL | DPG_REMOTE; + ut_avlInsertIPath (&deleted_participants_treedef, &deleted_participants, n, &path); + } + } + os_mutexUnlock (&deleted_participants_lock); +} + +int is_deleted_participant_guid (const struct nn_guid *guid, unsigned for_what) +{ + struct deleted_participant *n; + int known; + os_mutexLock (&deleted_participants_lock); + prune_deleted_participant_guids_unlocked (now_mt()); + if ((n = ut_avlLookup (&deleted_participants_treedef, &deleted_participants, guid)) == NULL) + known = 0; + else + known = ((n->for_what & for_what) != 0); + os_mutexUnlock (&deleted_participants_lock); + return known; +} + +static void remove_deleted_participant_guid (const struct nn_guid *guid, unsigned for_what) +{ + struct deleted_participant *n; + nn_log (LC_DISCOVERY, "remove_deleted_participant_guid(%x:%x:%x:%x for_what=%x)\n", PGUID (*guid), for_what); + os_mutexLock (&deleted_participants_lock); + if ((n = ut_avlLookup (&deleted_participants_treedef, &deleted_participants, guid)) != NULL) + { + if (config.prune_deleted_ppant.enforce_delay) + { + n->t_prune = add_duration_to_mtime (now_mt (), config.prune_deleted_ppant.delay); + } + else + { + n->for_what &= ~for_what; + if (n->for_what != 0) + { + /* For local participants (remove called with LOCAL, leaving + REMOTE blacklisted, and has to do with network briding) */ + n->t_prune = add_duration_to_mtime (now_mt (), config.prune_deleted_ppant.delay); + } + else + { + ut_avlDelete (&deleted_participants_treedef, &deleted_participants, n); + os_free (n); + } + } + } + os_mutexUnlock (&deleted_participants_lock); +} + + +/* PARTICIPANT ------------------------------------------------------ */ + +int pp_allocate_entityid (nn_entityid_t *id, unsigned kind, struct participant *pp) +{ + os_mutexLock (&pp->e.lock); + if (pp->next_entityid + NN_ENTITYID_ALLOCSTEP < pp->next_entityid) + { + os_mutexUnlock (&pp->e.lock); + return ERR_OUT_OF_IDS; + } + *id = to_entityid (pp->next_entityid | kind); + pp->next_entityid += NN_ENTITYID_ALLOCSTEP; + os_mutexUnlock (&pp->e.lock); + return 0; +} + +int new_participant_guid (const nn_guid_t *ppguid, unsigned flags, const nn_plist_t *plist) +{ + struct participant *pp; + nn_guid_t subguid, group_guid; + + /* no reserved bits may be set */ + assert ((flags & ~(RTPS_PF_NO_BUILTIN_READERS | RTPS_PF_NO_BUILTIN_WRITERS | RTPS_PF_PRIVILEGED_PP | RTPS_PF_IS_DDSI2_PP | RTPS_PF_ONLY_LOCAL)) == 0); + /* privileged participant MUST have builtin readers and writers */ + assert (!(flags & RTPS_PF_PRIVILEGED_PP) || (flags & (RTPS_PF_NO_BUILTIN_READERS | RTPS_PF_NO_BUILTIN_WRITERS)) == 0); + + prune_deleted_participant_guids (now_mt ()); + + /* FIXME: FULL LOCKING AROUND NEW_XXX FUNCTIONS, JUST SO EXISTENCE TESTS ARE PRECISE */ + + /* Participant may not exist yet, but this test is imprecise: if it + used to exist, but is currently being deleted and we're trying to + recreate it. */ + if (ephash_lookup_participant_guid (ppguid) != NULL) + return ERR_ENTITY_EXISTS; + + if (config.max_participants == 0) + { + os_mutexLock (&gv.participant_set_lock); + ++gv.nparticipants; + os_mutexUnlock (&gv.participant_set_lock); + } + else + { + os_mutexLock (&gv.participant_set_lock); + if (gv.nparticipants < config.max_participants) + { + ++gv.nparticipants; + os_mutexUnlock (&gv.participant_set_lock); + } + else + { + os_mutexUnlock (&gv.participant_set_lock); + NN_ERROR ("new_participant(%x:%x:%x:%x, %x) failed: max participants reached\n", PGUID (*ppguid), flags); + return ERR_OUT_OF_IDS; + } + } + + nn_log (LC_DISCOVERY, "new_participant(%x:%x:%x:%x, %x)\n", PGUID (*ppguid), flags); + + pp = os_malloc (sizeof (*pp)); + + entity_common_init (&pp->e, ppguid, "", EK_PARTICIPANT, ((flags & RTPS_PF_ONLY_LOCAL) != 0)); + pp->user_refc = 1; + pp->builtin_refc = 0; + pp->builtins_deleted = 0; + pp->is_ddsi2_pp = (flags & (RTPS_PF_PRIVILEGED_PP | RTPS_PF_IS_DDSI2_PP)) ? 1 : 0; + os_mutexInit (&pp->refc_lock); + pp->next_entityid = NN_ENTITYID_ALLOCSTEP; + pp->lease_duration = config.lease_duration; + pp->plist = os_malloc (sizeof (*pp->plist)); + nn_plist_copy (pp->plist, plist); + nn_plist_mergein_missing (pp->plist, &gv.default_plist_pp); + + if (config.enabled_logcats & LC_DISCOVERY) + { + nn_log (LC_DISCOVERY, "PARTICIPANT %x:%x:%x:%x QOS={", PGUID (pp->e.guid)); + nn_log_xqos (LC_DISCOVERY, &pp->plist->qos); + nn_log (LC_DISCOVERY, "}\n"); + } + + if (config.many_sockets_mode) + { + pp->m_conn = ddsi_factory_create_conn (gv.m_factory, 0, NULL); + ddsi_conn_locator (pp->m_conn, &pp->m_locator); + } + + /* Before we create endpoints -- and may call unref_participant if + things go wrong -- we must initialize all that unref_participant + depends on. */ + pp->spdp_xevent = NULL; + pp->pmd_update_xevent = NULL; + + /* Create built-in endpoints (note: these have no GID, and no group GUID). */ + pp->bes = 0; + pp->prismtech_bes = 0; + subguid.prefix = pp->e.guid.prefix; + memset (&group_guid, 0, sizeof (group_guid)); + /* SPDP writer */ +#define LAST_WR_PARAMS NULL, NULL + + /* Note: skip SEDP <=> skip SPDP because of the way ddsi_discovery.c does things + currently. */ + if (!(flags & RTPS_PF_NO_BUILTIN_WRITERS)) + { + subguid.entityid = to_entityid (NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER); + new_writer_guid (&subguid, &group_guid, pp, NULL, &gv.spdp_endpoint_xqos, LAST_WR_PARAMS); + /* But we need the as_disc address set for SPDP, because we need to + send it to everyone regardless of the existence of readers. */ + { + struct writer *wr = ephash_lookup_writer_guid (&subguid); + assert (wr != NULL); + os_mutexLock (&wr->e.lock); + unref_addrset (wr->as); + unref_addrset (wr->as_group); + wr->as = ref_addrset (gv.as_disc); + wr->as_group = ref_addrset (gv.as_disc_group); + os_mutexUnlock (&wr->e.lock); + } + pp->bes |= NN_DISC_BUILTIN_ENDPOINT_PARTICIPANT_ANNOUNCER; + } + + /* Make it globally visible, else the endpoint matching won't work. */ + ephash_insert_participant_guid (pp); + + /* SEDP writers: */ + if (!(flags & RTPS_PF_NO_BUILTIN_WRITERS)) + { + subguid.entityid = to_entityid (NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_WRITER); + new_writer_guid (&subguid, &group_guid, pp, NULL, &gv.builtin_endpoint_xqos_wr, LAST_WR_PARAMS); + pp->bes |= NN_DISC_BUILTIN_ENDPOINT_SUBSCRIPTION_ANNOUNCER; + + subguid.entityid = to_entityid (NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_WRITER); + new_writer_guid (&subguid, &group_guid, pp, NULL, &gv.builtin_endpoint_xqos_wr, LAST_WR_PARAMS); + pp->bes |= NN_DISC_BUILTIN_ENDPOINT_PUBLICATION_ANNOUNCER; + + subguid.entityid = to_entityid (NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_WRITER); + new_writer_guid (&subguid, &group_guid, pp, NULL, &gv.builtin_endpoint_xqos_wr, LAST_WR_PARAMS); + pp->prismtech_bes |= NN_DISC_BUILTIN_ENDPOINT_CM_PARTICIPANT_WRITER; + + subguid.entityid = to_entityid (NN_ENTITYID_SEDP_BUILTIN_CM_PUBLISHER_WRITER); + new_writer_guid (&subguid, &group_guid, pp, NULL, &gv.builtin_endpoint_xqos_wr, LAST_WR_PARAMS); + pp->prismtech_bes |= NN_DISC_BUILTIN_ENDPOINT_CM_PUBLISHER_WRITER; + + subguid.entityid = to_entityid (NN_ENTITYID_SEDP_BUILTIN_CM_SUBSCRIBER_WRITER); + new_writer_guid (&subguid, &group_guid, pp, NULL, &gv.builtin_endpoint_xqos_wr, LAST_WR_PARAMS); + pp->prismtech_bes |= NN_DISC_BUILTIN_ENDPOINT_CM_SUBSCRIBER_WRITER; + } + + if (config.do_topic_discovery) + { + /* TODO: make this one configurable, we don't want all participants to publish all topics (or even just those that they use themselves) */ + subguid.entityid = to_entityid (NN_ENTITYID_SEDP_BUILTIN_TOPIC_WRITER); + new_writer_guid (&subguid, &group_guid, pp, NULL, &gv.builtin_endpoint_xqos_wr, LAST_WR_PARAMS); + pp->bes |= NN_DISC_BUILTIN_ENDPOINT_TOPIC_ANNOUNCER; + } + + /* PMD writer: */ + if (!(flags & RTPS_PF_NO_BUILTIN_WRITERS)) + { + subguid.entityid = to_entityid (NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_WRITER); + new_writer_guid (&subguid, &group_guid, pp, NULL, &gv.builtin_endpoint_xqos_wr, LAST_WR_PARAMS); + pp->bes |= NN_BUILTIN_ENDPOINT_PARTICIPANT_MESSAGE_DATA_WRITER; + } + + /* SPDP, SEDP, PMD readers: */ + if (!(flags & RTPS_PF_NO_BUILTIN_READERS)) + { + subguid.entityid = to_entityid (NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_READER); + new_reader_guid (&subguid, &group_guid, pp, NULL, &gv.spdp_endpoint_xqos, NULL, NULL, NULL); + pp->bes |= NN_DISC_BUILTIN_ENDPOINT_PARTICIPANT_DETECTOR; + + subguid.entityid = to_entityid (NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_READER); + new_reader_guid (&subguid, &group_guid, pp, NULL, &gv.builtin_endpoint_xqos_rd, NULL, NULL, NULL); + pp->bes |= NN_DISC_BUILTIN_ENDPOINT_SUBSCRIPTION_DETECTOR; + + subguid.entityid = to_entityid (NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_READER); + new_reader_guid (&subguid, &group_guid, pp, NULL, &gv.builtin_endpoint_xqos_rd, NULL, NULL, NULL); + pp->bes |= NN_DISC_BUILTIN_ENDPOINT_PUBLICATION_DETECTOR; + + subguid.entityid = to_entityid (NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_READER); + new_reader_guid (&subguid, &group_guid, pp, NULL, &gv.builtin_endpoint_xqos_rd, NULL, NULL, NULL); + pp->bes |= NN_BUILTIN_ENDPOINT_PARTICIPANT_MESSAGE_DATA_READER; + + subguid.entityid = to_entityid (NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_READER); + new_reader_guid (&subguid, &group_guid, pp, NULL, &gv.builtin_endpoint_xqos_rd, NULL, NULL, NULL); + pp->prismtech_bes |= NN_DISC_BUILTIN_ENDPOINT_CM_PARTICIPANT_READER; + + subguid.entityid = to_entityid (NN_ENTITYID_SEDP_BUILTIN_CM_PUBLISHER_READER); + new_reader_guid (&subguid, &group_guid, pp, NULL, &gv.builtin_endpoint_xqos_rd, NULL, NULL, NULL); + pp->prismtech_bes |= NN_DISC_BUILTIN_ENDPOINT_CM_PUBLISHER_READER; + + subguid.entityid = to_entityid (NN_ENTITYID_SEDP_BUILTIN_CM_SUBSCRIBER_READER); + new_reader_guid (&subguid, &group_guid, pp, NULL, &gv.builtin_endpoint_xqos_rd, NULL, NULL, NULL); + pp->prismtech_bes |= NN_DISC_BUILTIN_ENDPOINT_CM_SUBSCRIBER_READER; + + } +#undef LAST_WR_PARAMS + + /* If the participant doesn't have the full set of builtin writers + it depends on the privileged participant, which must exist, hence + the reference count of the privileged participant is incremented. + If it is the privileged participant, set the global variable + pointing to it. + Except when the participant is only locally available. */ + if (!(flags & RTPS_PF_ONLY_LOCAL)) { + os_mutexLock (&gv.privileged_pp_lock); + if ((pp->bes & builtin_writers_besmask) != builtin_writers_besmask || + (pp->prismtech_bes & prismtech_builtin_writers_besmask) != prismtech_builtin_writers_besmask) + { + /* Simply crash when the privileged participant doesn't exist when + it is needed. Its existence is a precondition, and this is not + a public API */ + assert (gv.privileged_pp != NULL); + ref_participant (gv.privileged_pp, &pp->e.guid); + } + if (flags & RTPS_PF_PRIVILEGED_PP) + { + /* Crash when two privileged participants are created -- this is + not a public API. */ + assert (gv.privileged_pp == NULL); + gv.privileged_pp = pp; + } + os_mutexUnlock (&gv.privileged_pp_lock); + } + + /* Make it globally visible, then signal receive threads if + necessary. Must do in this order, or the receive thread won't + find the new participant */ + + if (config.many_sockets_mode) + { + os_atomic_fence (); + os_atomic_inc32 (&gv.participant_set_generation); + os_sockWaitsetTrigger (gv.waitset); + } + + /* SPDP periodic broadcast uses the retransmit path, so the initial + publication must be done differently. Must be later than making + the participant globally visible, or the SPDP processing won't + recognise the participant as a local one. */ + if (spdp_write (pp) >= 0) + { + /* Once the initial sample has been written, the automatic and + asynchronous broadcasting required by SPDP can start. Also, + since we're new alive, PMD updates can now start, too. + Schedule the first update for 100ms in the future to reduce the + impact of the first sample getting lost. Note: these two may + fire before the calls return. If the initial sample wasn't + accepted, all is lost, but we continue nonetheless, even though + the participant won't be able to discover or be discovered. */ + pp->spdp_xevent = qxev_spdp (add_duration_to_mtime (now_mt (), 100 * T_MILLISECOND), &pp->e.guid, NULL); + } + + /* Also write the CM data - this one being transient local, we only + need to write it once (or when it changes, I suppose) */ + sedp_write_cm_participant (pp, 1); + + { + nn_mtime_t tsched; + tsched.v = (pp->lease_duration == T_NEVER) ? T_NEVER : 0; + pp->pmd_update_xevent = qxev_pmd_update (tsched, &pp->e.guid); + } + + return 0; +} + +int new_participant (nn_guid_t *p_ppguid, unsigned flags, const nn_plist_t *plist) +{ + nn_guid_t ppguid; + + os_mutexLock (&gv.privileged_pp_lock); + ppguid = gv.next_ppguid; + if (gv.next_ppguid.prefix.u[2]++ == ~0u) + { + os_mutexUnlock (&gv.privileged_pp_lock); + return ERR_OUT_OF_IDS; + } + os_mutexUnlock (&gv.privileged_pp_lock); + *p_ppguid = ppguid; + + return new_participant_guid (p_ppguid, flags, plist); +} + +static void delete_builtin_endpoint (const struct nn_guid *ppguid, unsigned entityid) +{ + nn_guid_t guid; + guid.prefix = ppguid->prefix; + guid.entityid.u = entityid; + assert (is_builtin_entityid (to_entityid (entityid), ownvendorid)); + if (is_writer_entityid (to_entityid (entityid))) + delete_writer_nolinger (&guid); + else + (void)delete_reader (&guid); +} + +static struct participant *ref_participant (struct participant *pp, const struct nn_guid *guid_of_refing_entity) +{ + nn_guid_t stguid; + os_mutexLock (&pp->refc_lock); + if (guid_of_refing_entity && is_builtin_endpoint (guid_of_refing_entity->entityid, ownvendorid)) + pp->builtin_refc++; + else + pp->user_refc++; + + if (guid_of_refing_entity) + stguid = *guid_of_refing_entity; + else + memset (&stguid, 0, sizeof (stguid)); + nn_log (LC_DISCOVERY, "ref_participant(%x:%x:%x:%x @ %p <- %x:%x:%x:%x @ %p) user %d builtin %d\n", + PGUID (pp->e.guid), (void*)pp, PGUID (stguid), (void*)guid_of_refing_entity, pp->user_refc, pp->builtin_refc); + os_mutexUnlock (&pp->refc_lock); + return pp; +} + +static void unref_participant (struct participant *pp, const struct nn_guid *guid_of_refing_entity) +{ + static const unsigned builtin_endpoints_tab[] = { + NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER, + NN_ENTITYID_SEDP_BUILTIN_TOPIC_WRITER, + NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_WRITER, + NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_WRITER, + NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_WRITER, + NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_READER, + NN_ENTITYID_SEDP_BUILTIN_TOPIC_READER, + NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_READER, + NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_READER, + NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_READER, + /* PrismTech ones: */ + NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_WRITER, + NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_READER, + NN_ENTITYID_SEDP_BUILTIN_CM_PUBLISHER_WRITER, + NN_ENTITYID_SEDP_BUILTIN_CM_PUBLISHER_READER, + NN_ENTITYID_SEDP_BUILTIN_CM_SUBSCRIBER_WRITER, + NN_ENTITYID_SEDP_BUILTIN_CM_SUBSCRIBER_READER + }; + nn_guid_t stguid; + + os_mutexLock (&pp->refc_lock); + if (guid_of_refing_entity && is_builtin_endpoint (guid_of_refing_entity->entityid, ownvendorid)) + pp->builtin_refc--; + else + pp->user_refc--; + assert (pp->user_refc >= 0); + assert (pp->builtin_refc >= 0); + + if (guid_of_refing_entity) + stguid = *guid_of_refing_entity; + else + memset (&stguid, 0, sizeof (stguid)); + nn_log (LC_DISCOVERY, "unref_participant(%x:%x:%x:%x @ %p <- %x:%x:%x:%x @ %p) user %d builtin %d\n", + PGUID (pp->e.guid), (void*)pp, PGUID (stguid), (void*)guid_of_refing_entity, pp->user_refc, pp->builtin_refc); + + if (pp->user_refc == 0 && (pp->bes != 0 || pp->prismtech_bes != 0) && !pp->builtins_deleted) + { + int i; + + /* The builtin ones are never deleted explicitly by the glue code, + only implicitly by unref_participant, and we need to make sure + they go at the very end, or else the SEDP disposes and the + final SPDP message can't be sent. + + If there are no builtins at all, then we must go straight to + deleting the participant, as unref_participant will never be + called upon deleting a builtin endpoint. + + First we stop the asynchronous SPDP and PMD publication, then + we send a dispose+unregister message on SPDP (wonder if I ought + to send a final PMD one as well), then we kill the readers and + expect us to finally hit the new_refc == 0 to really free this + participant. + + The conditional execution of some of this is so we can use + unref_participant() for some of the error handling in + new_participant(). Non-existent built-in endpoints can't be + found in guid_hash and are simply ignored. */ + pp->builtins_deleted = 1; + os_mutexUnlock (&pp->refc_lock); + + if (pp->spdp_xevent) + delete_xevent (pp->spdp_xevent); + if (pp->pmd_update_xevent) + delete_xevent (pp->pmd_update_xevent); + + /* SPDP relies on the WHC, but dispose-unregister will empty + it. The event handler verifies the event has already been + scheduled for deletion when it runs into an empty WHC */ + spdp_dispose_unregister (pp); + + /* We don't care, but other implementations might: */ + sedp_write_cm_participant (pp, 0); + + /* If this happens to be the privileged_pp, clear it */ + os_mutexLock (&gv.privileged_pp_lock); + if (pp == gv.privileged_pp) + gv.privileged_pp = NULL; + os_mutexUnlock (&gv.privileged_pp_lock); + + for (i = 0; i < (int) (sizeof (builtin_endpoints_tab) / sizeof (builtin_endpoints_tab[0])); i++) + delete_builtin_endpoint (&pp->e.guid, builtin_endpoints_tab[i]); + } + else if (pp->user_refc == 0 && pp->builtin_refc == 0) + { + os_mutexUnlock (&pp->refc_lock); + + if (!(pp->e.onlylocal)) + { + if ((pp->bes & builtin_writers_besmask) != builtin_writers_besmask || + (pp->prismtech_bes & prismtech_builtin_writers_besmask) != prismtech_builtin_writers_besmask) + { + /* Participant doesn't have a full complement of built-in + writers, therefore, it relies on gv.privileged_pp, and + therefore we must decrement the reference count of that one. + + Why read it with the lock held, only to release it and use it + without any attempt to maintain a consistent state? We DO + have a counted reference, so it can't be freed, but there is + no formal guarantee that the pointer we read is valid unless + we read it with the lock held. We can't keep the lock across + the unref_participant, because we may trigger a clean-up of + it. */ + struct participant *ppp; + os_mutexLock (&gv.privileged_pp_lock); + ppp = gv.privileged_pp; + os_mutexUnlock (&gv.privileged_pp_lock); + assert (ppp != NULL); + unref_participant (ppp, &pp->e.guid); + } + } + + os_mutexLock (&gv.participant_set_lock); + assert (gv.nparticipants > 0); + if (--gv.nparticipants == 0) + os_condBroadcast (&gv.participant_set_cond); + os_mutexUnlock (&gv.participant_set_lock); + if (config.many_sockets_mode) + { + os_atomic_fence_rel (); + os_atomic_inc32 (&gv.participant_set_generation); + + /* Deleting the socket will usually suffice to wake up the + receiver threads, but in general, no one cares if it takes a + while longer for it to wakeup. */ + + ddsi_conn_free (pp->m_conn); + } + nn_plist_fini (pp->plist); + os_free (pp->plist); + os_mutexDestroy (&pp->refc_lock); + entity_common_fini (&pp->e); + remove_deleted_participant_guid (&pp->e.guid, DPG_LOCAL); + os_free (pp); + } + else + { + os_mutexUnlock (&pp->refc_lock); + } +} + +static void gc_delete_participant (struct gcreq *gcreq) +{ + struct participant *pp = gcreq->arg; + nn_log (LC_DISCOVERY, "gc_delete_participant(%p, %x:%x:%x:%x)\n", (void *) gcreq, PGUID (pp->e.guid)); + gcreq_free (gcreq); + unref_participant (pp, NULL); +} + +int delete_participant (const struct nn_guid *ppguid) +{ + struct participant *pp; + if ((pp = ephash_lookup_participant_guid (ppguid)) == NULL) + return ERR_UNKNOWN_ENTITY; + if (!(pp->e.onlylocal)) + { + propagate_builtin_topic_cmparticipant(&(pp->e), pp->plist, now(), false); + propagate_builtin_topic_participant(&(pp->e), pp->plist, now(), false); + } + remember_deleted_participant_guid (&pp->e.guid); + ephash_remove_participant_guid (pp); + gcreq_participant (pp); + return 0; +} + +struct writer *get_builtin_writer (const struct participant *pp, unsigned entityid) +{ + nn_guid_t bwr_guid; + unsigned bes_mask = 0, prismtech_bes_mask = 0; + + if (pp->e.onlylocal) { + return NULL; + } + + /* If the participant the required built-in writer, we use it. We + check by inspecting the "built-in endpoint set" advertised by the + participant, which is a constant. */ + switch (entityid) + { + case NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER: + bes_mask = NN_DISC_BUILTIN_ENDPOINT_PARTICIPANT_ANNOUNCER; + break; + case NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_WRITER: + bes_mask = NN_DISC_BUILTIN_ENDPOINT_SUBSCRIPTION_ANNOUNCER; + break; + case NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_WRITER: + bes_mask = NN_DISC_BUILTIN_ENDPOINT_PUBLICATION_ANNOUNCER; + break; + case NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_WRITER: + bes_mask = NN_BUILTIN_ENDPOINT_PARTICIPANT_MESSAGE_DATA_WRITER; + break; + case NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_WRITER: + prismtech_bes_mask = NN_DISC_BUILTIN_ENDPOINT_CM_PARTICIPANT_WRITER; + break; + case NN_ENTITYID_SEDP_BUILTIN_CM_PUBLISHER_WRITER: + prismtech_bes_mask = NN_DISC_BUILTIN_ENDPOINT_CM_PUBLISHER_WRITER; + break; + case NN_ENTITYID_SEDP_BUILTIN_CM_SUBSCRIBER_WRITER: + prismtech_bes_mask = NN_DISC_BUILTIN_ENDPOINT_CM_SUBSCRIBER_WRITER; + break; + case NN_ENTITYID_SEDP_BUILTIN_TOPIC_WRITER: + bes_mask = NN_DISC_BUILTIN_ENDPOINT_TOPIC_ANNOUNCER; + break; + default: + NN_FATAL ("get_builtin_writer called with entityid %x\n", entityid); + return NULL; + } + + if ((pp->bes & bes_mask) || (pp->prismtech_bes & prismtech_bes_mask)) + { + /* Participant has this SEDP writer => use it. */ + bwr_guid.prefix = pp->e.guid.prefix; + bwr_guid.entityid.u = entityid; + } + else + { + /* Must have a designated participant to use -- that is, before + any application readers and writers may be created (indeed, + before any PMD message may go out), one participant must be + created with the built-in writers, and this participant then + automatically becomes the designated participant. Woe betide + who deletes it early! Lock's not really needed but provides + the memory barriers that guarantee visibility of the correct + value of privileged_pp. */ + os_mutexLock (&gv.privileged_pp_lock); + assert (gv.privileged_pp != NULL); + bwr_guid.prefix = gv.privileged_pp->e.guid.prefix; + os_mutexUnlock (&gv.privileged_pp_lock); + bwr_guid.entityid.u = entityid; + } + + return ephash_lookup_writer_guid (&bwr_guid); +} + +/* WRITER/READER/PROXY-WRITER/PROXY-READER CONNECTION --------------- + + These are all located in a separate section because they are so + very similar that co-locating them eases editing and checking. */ + +struct rebuild_flatten_locs_arg { + nn_locator_t *locs; + int idx; +#ifndef NDEBUG + int size; +#endif +}; + +static void rebuild_flatten_locs(const nn_locator_t *loc, void *varg) +{ + struct rebuild_flatten_locs_arg *arg = varg; + assert(arg->idx < arg->size); + arg->locs[arg->idx++] = *loc; +} + +static int rebuild_compare_locs(const void *va, const void *vb) +{ + const nn_locator_t *a = va; + const nn_locator_t *b = vb; + if (a->kind != b->kind || a->kind != NN_LOCATOR_KIND_UDPv4MCGEN) + return compare_locators(a, b); + else + { + nn_locator_t u = *a, v = *b; + nn_udpv4mcgen_address_t *u1 = (nn_udpv4mcgen_address_t *) u.address; + nn_udpv4mcgen_address_t *v1 = (nn_udpv4mcgen_address_t *) v.address; + u1->idx = v1->idx = 0; + return compare_locators(&u, &v); + } +} + +static struct addrset *rebuild_make_all_addrs (int *nreaders, struct writer *wr) +{ + struct addrset *all_addrs = new_addrset(); + struct wr_prd_match *m; + ut_avlIter_t it; +#ifdef DDSI_INCLUDE_SSM + if (wr->supports_ssm && wr->ssm_as) + copy_addrset_into_addrset_mc (all_addrs, wr->ssm_as); +#endif + *nreaders = 0; + for (m = ut_avlIterFirst (&wr_readers_treedef, &wr->readers, &it); m; m = ut_avlIterNext (&it)) + { + struct proxy_reader *prd; + if ((prd = ephash_lookup_proxy_reader_guid (&m->prd_guid)) == NULL) + continue; + (*nreaders)++; + copy_addrset_into_addrset(all_addrs, prd->c.as); + } + if (addrset_empty(all_addrs) || *nreaders == 0) + { + unref_addrset(all_addrs); + return NULL; + } + else + { + return all_addrs; + } +} + +static void rebuild_make_locs(int *p_nlocs, nn_locator_t **p_locs, struct addrset *all_addrs) +{ + struct rebuild_flatten_locs_arg flarg; + int nlocs; + int i, j; + nn_locator_t *locs; + nlocs = (int)addrset_count(all_addrs); + locs = os_malloc((size_t)nlocs * sizeof(*locs)); + flarg.locs = locs; + flarg.idx = 0; +#ifndef NDEBUG + flarg.size = nlocs; +#endif + addrset_forall(all_addrs, rebuild_flatten_locs, &flarg); + assert(flarg.idx == flarg.size); + qsort(locs, (size_t)nlocs, sizeof(*locs), rebuild_compare_locs); + /* We want MC gens just once for each IP,BASE,COUNT pair, not once for each node */ + i = 0; j = 1; + while (j < nlocs) + { + if (rebuild_compare_locs(&locs[i], &locs[j]) != 0) + locs[++i] = locs[j]; + j++; + } + nlocs = i+1; + nn_log(LC_DISCOVERY, "reduced nlocs=%d\n", nlocs); + *p_nlocs = nlocs; + *p_locs = locs; +} + +static void rebuild_make_covered(char **covered, const struct writer *wr, int *nreaders, int nlocs, const nn_locator_t *locs) +{ + struct rebuild_flatten_locs_arg flarg; + struct wr_prd_match *m; + ut_avlIter_t it; + int rdidx, i, j; + char *cov = os_malloc((size_t) *nreaders * (size_t) nlocs); + memset(cov, 0xff, (size_t) *nreaders * (size_t) nlocs); + rdidx = 0; + flarg.locs = os_malloc((size_t) nlocs * sizeof(*flarg.locs)); +#ifndef NDEBUG + flarg.size = nlocs; +#endif + for (m = ut_avlIterFirst (&wr_readers_treedef, &wr->readers, &it); m; m = ut_avlIterNext (&it)) + { + struct proxy_reader *prd; + struct addrset *ass[] = { NULL, NULL, NULL }; + if ((prd = ephash_lookup_proxy_reader_guid (&m->prd_guid)) == NULL) + continue; + ass[0] = prd->c.as; +#ifdef DDSI_INCLUDE_SSM + if (prd->favours_ssm && wr->supports_ssm) + ass[1] = wr->ssm_as; +#endif + for (i = 0; ass[i]; i++) + { + flarg.idx = 0; + addrset_forall(ass[i], rebuild_flatten_locs, &flarg); + for (j = 0; j < flarg.idx; j++) + { + /* all addresses should be in the combined set of addresses -- FIXME: this doesn't hold if the address sets can change */ + const nn_locator_t *l = bsearch(&flarg.locs[j], locs, (size_t) nlocs, sizeof(*locs), rebuild_compare_locs); + int lidx; + char x; + assert(l != NULL); + lidx = (int) (l - locs); + if (l->kind != NN_LOCATOR_KIND_UDPv4MCGEN) + x = 0; + else + { + const nn_udpv4mcgen_address_t *l1 = (const nn_udpv4mcgen_address_t *) flarg.locs[j].address; + assert(l1->base + l1->idx <= 127); + x = (char) (l1->base + l1->idx); + } + cov[rdidx * nlocs + lidx] = x; + } + } + rdidx++; + } + os_free(flarg.locs); + *covered = cov; + *nreaders = rdidx; +} + +static void rebuild_make_locs_nrds(int **locs_nrds, int nreaders, int nlocs, const char *covered) +{ + int i, j; + int *ln = os_malloc((size_t) nlocs * sizeof(*ln)); + for (i = 0; i < nlocs; i++) + { + int n = 0; + for (j = 0; j < nreaders; j++) + if (covered[j * nlocs + i] >= 0) + n++; + +/* The compiler doesn't realize that ln is large enough. */ +OS_WARNING_MSVC_OFF(6386); + ln[i] = n; +OS_WARNING_MSVC_ON(6386); + } + *locs_nrds = ln; +} + +static void rebuild_trace_covered(int nreaders, int nlocs, const nn_locator_t *locs, const int *locs_nrds, const char *covered) +{ + int i, j; + for (i = 0; i < nlocs; i++) + { + char buf[INET6_ADDRSTRLEN_EXTENDED]; + locator_to_string_with_port(buf, &locs[i]); + nn_log(LC_DISCOVERY, " loc %2d = %-20s %2d {", i, buf, locs_nrds[i]); + for (j = 0; j < nreaders; j++) + if (covered[j * nlocs + i] >= 0) + nn_log(LC_DISCOVERY, " %d", covered[j * nlocs + i]); + else + nn_log(LC_DISCOVERY, " ."); + nn_log(LC_DISCOVERY, " }\n"); + } +} + +static int rebuild_select(int nlocs, const nn_locator_t *locs, const int *locs_nrds) +{ + int i, j; + if (nlocs == 0) + return -1; + for (j = 0, i = 1; i < nlocs; i++) { + if (locs_nrds[i] > locs_nrds[j]) + j = i; /* better coverage */ + else if (locs_nrds[i] == locs_nrds[j]) + { + if (locs_nrds[i] == 1 && !is_mcaddr(&locs[i])) + j = i; /* prefer unicast for single nodes */ +#if DDSI_INCLUDE_SSM + else if (is_ssm_mcaddr(&locs[i])) + j = i; /* "reader favours SSM": all else being equal, use SSM */ +#endif + } + } + return (locs_nrds[j] > 0) ? j : -1; +} + +static void rebuild_add(struct addrset *newas, int locidx, int nreaders, int nlocs, const nn_locator_t *locs, const char *covered) +{ + char str[INET6_ADDRSTRLEN_EXTENDED]; + if (locs[locidx].kind != NN_LOCATOR_KIND_UDPv4MCGEN) + { + locator_to_string_with_port(str, &locs[locidx]); + nn_log(LC_DISCOVERY, " simple %s\n", str); + add_to_addrset(newas, &locs[locidx]); + } + else /* convert MC gen to the correct multicast address */ + { + nn_locator_t l = locs[locidx]; + nn_udpv4mcgen_address_t l1; + uint32_t iph, ipn; + int i; + memcpy(&l1, l.address, sizeof(l1)); + l.kind = NN_LOCATOR_KIND_UDPv4; + memset(l.address, 0, 12); + iph = ntohl(l1.ipv4.s_addr); + for(i = 0; i < nreaders; i++) + if (covered[i * nlocs + locidx] >= 0) + iph |= 1u << covered[i * nlocs + locidx]; + ipn = htonl(iph); + memcpy(l.address + 12, &ipn, 4); + locator_to_string_with_port(str, &l); + nn_log(LC_DISCOVERY, " mcgen %s\n", str); + add_to_addrset(newas, &l); + } +} + +static void rebuild_drop(int locidx, int nreaders, int nlocs, int *locs_nrds, char *covered) +{ + /* readers covered by this locator no longer matter */ + int i, j; + for (i = 0; i < nreaders; i++) + { + if (covered[i * nlocs + locidx] < 0) + continue; + for (j = 0; j < nlocs; j++) + if (covered[i * nlocs + j] >= 0) + { + assert(locs_nrds[j] > 0); + locs_nrds[j]--; + covered[i * nlocs + j] = -1; + } + } +} + +static void rebuild_writer_addrset_setcover(struct addrset *newas, struct writer *wr) +{ + struct addrset *all_addrs; + int nreaders, nlocs; + nn_locator_t *locs; + int *locs_nrds; + char *covered; + int best; + if ((all_addrs = rebuild_make_all_addrs(&nreaders, wr)) == NULL) + return; + nn_log_addrset(LC_DISCOVERY, "setcover: all_addrs", all_addrs); nn_log(LC_DISCOVERY, "\n"); + rebuild_make_locs(&nlocs, &locs, all_addrs); + unref_addrset(all_addrs); + rebuild_make_covered(&covered, wr, &nreaders, nlocs, locs); + if (nreaders == 0) + goto done; + rebuild_make_locs_nrds(&locs_nrds, nreaders, nlocs, covered); + while ((best = rebuild_select(nlocs, locs, locs_nrds)) >= 0) + { + rebuild_trace_covered(nreaders, nlocs, locs, locs_nrds, covered); + nn_log(LC_DISCOVERY, " best = %d\n", best); + rebuild_add(newas, best, nreaders, nlocs, locs, covered); + rebuild_drop(best, nreaders, nlocs, locs_nrds, covered); + assert (locs_nrds[best] == 0); + } + os_free(locs_nrds); + done: + os_free(locs); + os_free(covered); +} + +static void rebuild_writer_addrset (struct writer *wr) +{ + /* FIXME way too inefficient in this form */ + struct addrset *newas = new_addrset (); + struct addrset *oldas = wr->as; + + /* only one operation at a time */ + ASSERT_MUTEX_HELD (&wr->e.lock); + + /* compute new addrset */ + rebuild_writer_addrset_setcover(newas, wr); + + /* swap in new address set; this simple procedure is ok as long as + wr->as is never accessed without the wr->e.lock held */ + wr->as = newas; + unref_addrset (oldas); + + nn_log (LC_DISCOVERY, "rebuild_writer_addrset(%x:%x:%x:%x):", PGUID (wr->e.guid)); + nn_log_addrset (LC_DISCOVERY, "", wr->as); + nn_log (LC_DISCOVERY, "\n"); +} + + +static void free_wr_prd_match (struct wr_prd_match *m) +{ + if (m) + { + nn_lat_estim_fini (&m->hb_to_ack_latency); + os_free (m); + } +} + +static void free_rd_pwr_match (struct rd_pwr_match *m) +{ + if (m) + { +#ifdef DDSI_INCLUDE_SSM + if (!is_unspec_locator (&m->ssm_mc_loc)) + { + assert (is_mcaddr (&m->ssm_mc_loc)); + assert (!is_unspec_locator (&m->ssm_src_loc)); + if (ddsi_conn_leave_mc (gv.data_conn_mc, &m->ssm_src_loc, &m->ssm_mc_loc) < 0) + nn_log (LC_WARNING, "failed to leave network partition ssm group\n"); + } +#endif + os_free (m); + } +} + +static void free_pwr_rd_match (struct pwr_rd_match *m) +{ + if (m) + { + if (m->acknack_xevent) + delete_xevent (m->acknack_xevent); + nn_reorder_free (m->u.not_in_sync.reorder); + os_free (m); + } +} + +static void free_prd_wr_match (struct prd_wr_match *m) +{ + if (m) os_free (m); +} + +static void free_rd_wr_match (struct rd_wr_match *m) +{ + if (m) os_free (m); +} + +static void free_wr_rd_match (struct wr_rd_match *m) +{ + if (m) os_free (m); +} + +static void writer_drop_connection (const struct nn_guid * wr_guid, const struct proxy_reader * prd) +{ + struct whc_node *deferred_free_list; + struct writer *wr; + if ((wr = ephash_lookup_writer_guid (wr_guid)) != NULL) + { + struct wr_prd_match *m; + os_mutexLock (&wr->e.lock); + if ((m = ut_avlLookup (&wr_readers_treedef, &wr->readers, &prd->e.guid)) != NULL) + { + ut_avlDelete (&wr_readers_treedef, &wr->readers, m); + rebuild_writer_addrset (wr); + remove_acked_messages (wr, &deferred_free_list); + wr->num_reliable_readers -= m->is_reliable; + if (wr->status_cb) + { + status_cb_data_t data; + data.status = DDS_PUBLICATION_MATCHED_STATUS; + data.add = false; + data.handle = prd->e.iid; + (wr->status_cb) (wr->status_cb_entity, &data); + } + } + os_mutexUnlock (&wr->e.lock); + free_wr_prd_match (m); + } +} + +static void writer_drop_local_connection (const struct nn_guid *wr_guid, struct reader *rd) +{ + /* Only called by gc_delete_reader, so we actually have a reader pointer */ + struct writer *wr; + if ((wr = ephash_lookup_writer_guid (wr_guid)) != NULL) + { + struct wr_rd_match *m; + + os_mutexLock (&wr->e.lock); + if ((m = ut_avlLookup (&wr_local_readers_treedef, &wr->local_readers, &rd->e.guid)) != NULL) + { + ut_avlDelete (&wr_local_readers_treedef, &wr->local_readers, m); + } + local_reader_ary_remove (&wr->rdary, rd); + if (wr->status_cb) + { + status_cb_data_t data; + data.status = DDS_PUBLICATION_MATCHED_STATUS; + data.add = false; + data.handle = rd->e.iid; + (wr->status_cb) (wr->status_cb_entity, &data); + } + os_mutexUnlock (&wr->e.lock); + free_wr_rd_match (m); + } +} + +static void reader_drop_connection (const struct nn_guid *rd_guid, const struct proxy_writer * pwr) +{ + struct reader *rd; + if ((rd = ephash_lookup_reader_guid (rd_guid)) != NULL) + { + struct rd_pwr_match *m; + os_mutexLock (&rd->e.lock); + if ((m = ut_avlLookup (&rd_writers_treedef, &rd->writers, &pwr->e.guid)) != NULL) + ut_avlDelete (&rd_writers_treedef, &rd->writers, m); + os_mutexUnlock (&rd->e.lock); + free_rd_pwr_match (m); + + if (rd->rhc) + { + struct proxy_writer_info pwr_info; + pwr_info.guid = pwr->e.guid; + pwr_info.ownership_strength = pwr->c.xqos->ownership_strength.value; + pwr_info.auto_dispose = pwr->c.xqos->writer_data_lifecycle.autodispose_unregistered_instances; + pwr_info.iid = pwr->e.iid; + + (ddsi_plugin.rhc_unregister_wr_fn) (rd->rhc, &pwr_info); + } + if (rd->status_cb) + { + status_cb_data_t data; + + data.add = false; + data.handle = pwr->e.iid; + + data.status = DDS_LIVELINESS_CHANGED_STATUS; + (rd->status_cb) (rd->status_cb_entity, &data); + + data.status = DDS_SUBSCRIPTION_MATCHED_STATUS; + (rd->status_cb) (rd->status_cb_entity, &data); + } + } +} + +static void reader_drop_local_connection (const struct nn_guid *rd_guid, const struct writer * wr) +{ + struct reader *rd; + if ((rd = ephash_lookup_reader_guid (rd_guid)) != NULL) + { + struct rd_wr_match *m; + os_mutexLock (&rd->e.lock); + if ((m = ut_avlLookup (&rd_local_writers_treedef, &rd->local_writers, &wr->e.guid)) != NULL) + ut_avlDelete (&rd_local_writers_treedef, &rd->local_writers, m); + os_mutexUnlock (&rd->e.lock); + free_rd_wr_match (m); + + if (rd->rhc) + { + /* FIXME: */ + struct proxy_writer_info pwr_info; + pwr_info.guid = wr->e.guid; + pwr_info.ownership_strength = wr->xqos->ownership_strength.value; + pwr_info.auto_dispose = wr->xqos->writer_data_lifecycle.autodispose_unregistered_instances; + pwr_info.iid = wr->e.iid; + + (ddsi_plugin.rhc_unregister_wr_fn) (rd->rhc, &pwr_info); + } + if (rd->status_cb) + { + status_cb_data_t data; + + data.add = false; + data.handle = wr->e.iid; + + data.status = DDS_LIVELINESS_CHANGED_STATUS; + (rd->status_cb) (rd->status_cb_entity, &data); + + data.status = DDS_SUBSCRIPTION_MATCHED_STATUS; + (rd->status_cb) (rd->status_cb_entity, &data); + } + } +} + +static void update_reader_init_acknack_count (const struct nn_guid *rd_guid, nn_count_t count) +{ + struct reader *rd; + + /* Update the initial acknack sequence number for the reader. See + also reader_add_connection(). */ + nn_log (LC_DISCOVERY, "update_reader_init_acknack_count (%x:%x:%x:%x, %d): ", PGUID (*rd_guid), count); + if ((rd = ephash_lookup_reader_guid (rd_guid)) != NULL) + { + os_mutexLock (&rd->e.lock); + nn_log (LC_DISCOVERY, "%d -> ", rd->init_acknack_count); + if (count > rd->init_acknack_count) + rd->init_acknack_count = count; + nn_log (LC_DISCOVERY, "%d\n", count); + os_mutexUnlock (&rd->e.lock); + } + else + { + nn_log (LC_DISCOVERY, "reader no longer exists\n"); + } +} + +static void proxy_writer_drop_connection (const struct nn_guid *pwr_guid, struct reader *rd) +{ + /* Only called by gc_delete_reader, so we actually have a reader pointer */ + struct proxy_writer *pwr; + if ((pwr = ephash_lookup_proxy_writer_guid (pwr_guid)) != NULL) + { + struct pwr_rd_match *m; + + os_mutexLock (&pwr->e.lock); + if ((m = ut_avlLookup (&pwr_readers_treedef, &pwr->readers, &rd->e.guid)) != NULL) + { + ut_avlDelete (&pwr_readers_treedef, &pwr->readers, m); + if (m->in_sync != PRMSS_SYNC) + { + pwr->n_readers_out_of_sync--; + } + } + if (rd->reliable) + { + pwr->n_reliable_readers--; + } + local_reader_ary_remove (&pwr->rdary, rd); + os_mutexUnlock (&pwr->e.lock); + if (m != NULL) + { + update_reader_init_acknack_count (&rd->e.guid, m->count); + } + free_pwr_rd_match (m); + } +} + +static void proxy_reader_drop_connection + (const struct nn_guid *prd_guid, struct writer * wr) +{ + struct proxy_reader *prd; + if ((prd = ephash_lookup_proxy_reader_guid (prd_guid)) != NULL) + { + struct prd_wr_match *m; + os_mutexLock (&prd->e.lock); + m = ut_avlLookup (&prd_writers_treedef, &prd->writers, &wr->e.guid); + if (m) + { + ut_avlDelete (&prd_writers_treedef, &prd->writers, m); + } + os_mutexUnlock (&prd->e.lock); + free_prd_wr_match (m); + } +} + +static void writer_add_connection (struct writer *wr, struct proxy_reader *prd) +{ + struct wr_prd_match *m = os_malloc (sizeof (*m)); + ut_avlIPath_t path; + int pretend_everything_acked; + m->prd_guid = prd->e.guid; + m->is_reliable = (prd->c.xqos->reliability.kind > NN_BEST_EFFORT_RELIABILITY_QOS); + m->assumed_in_sync = (config.retransmit_merging == REXMIT_MERGE_ALWAYS); + m->has_replied_to_hb = !m->is_reliable; + m->all_have_replied_to_hb = 0; + m->non_responsive_count = 0; + m->rexmit_requests = 0; + /* m->demoted: see below */ + os_mutexLock (&prd->e.lock); + if (prd->deleting) + { + nn_log (LC_DISCOVERY, " writer_add_connection(wr %x:%x:%x:%x prd %x:%x:%x:%x) - prd is being deleted\n", + PGUID (wr->e.guid), PGUID (prd->e.guid)); + pretend_everything_acked = 1; + } + else if (!m->is_reliable) + { + /* Pretend a best-effort reader has ack'd everything, even waht is + still to be published. */ + pretend_everything_acked = 1; + } + else + { + pretend_everything_acked = 0; + } + os_mutexUnlock (&prd->e.lock); + m->next_acknack = DDSI_COUNT_MIN; + m->next_nackfrag = DDSI_COUNT_MIN; + nn_lat_estim_init (&m->hb_to_ack_latency); + m->hb_to_ack_latency_tlastlog = now (); + m->t_acknack_accepted.v = 0; + + os_mutexLock (&wr->e.lock); + if (pretend_everything_acked) + m->seq = MAX_SEQ_NUMBER; + else + m->seq = wr->seq; + if (ut_avlLookupIPath (&wr_readers_treedef, &wr->readers, &prd->e.guid, &path)) + { + nn_log(LC_DISCOVERY, " writer_add_connection(wr %x:%x:%x:%x prd %x:%x:%x:%x) - already connected\n", PGUID (wr->e.guid), PGUID (prd->e.guid)); + os_mutexUnlock (&wr->e.lock); + nn_lat_estim_fini (&m->hb_to_ack_latency); + os_free (m); + } + else + { + nn_log(LC_DISCOVERY, " writer_add_connection(wr %x:%x:%x:%x prd %x:%x:%x:%x) - ack seq %"PRId64"\n", PGUID (wr->e.guid), PGUID (prd->e.guid), m->seq); + ut_avlInsertIPath (&wr_readers_treedef, &wr->readers, m, &path); + rebuild_writer_addrset (wr); + wr->num_reliable_readers += m->is_reliable; + os_mutexUnlock (&wr->e.lock); + + if (wr->status_cb) + { + status_cb_data_t data; + data.status = DDS_PUBLICATION_MATCHED_STATUS; + data.add = true; + data.handle = prd->e.iid; + (wr->status_cb) (wr->status_cb_entity, &data); + } + + /* If reliable and/or transient-local, we may have data available + in the WHC, but if all has been acknowledged by the previously + known proxy readers (or if the is the first proxy reader), + there is no heartbeat event scheduled. + + A pre-emptive AckNack may be sent, but need not be, and we + can't be certain it won't have the final flag set. So we must + ensure a heartbeat is scheduled soon. */ + if (wr->heartbeat_xevent) + { + const int64_t delta = 1 * T_MILLISECOND; + const nn_mtime_t tnext = add_duration_to_mtime (now_mt (), delta); + os_mutexLock (&wr->e.lock); + /* To make sure that we keep sending heartbeats at a higher rate + at the start of this discovery, reset the hbs_since_last_write + count to zero. */ + wr->hbcontrol.hbs_since_last_write = 0; + if (tnext.v < wr->hbcontrol.tsched.v) + { + wr->hbcontrol.tsched = tnext; + resched_xevent_if_earlier (wr->heartbeat_xevent, tnext); + } + os_mutexUnlock (&wr->e.lock); + } + } +} + +static void +init_sampleinfo( + _Out_ struct nn_rsample_info *sampleinfo, + _In_ struct writer *wr, + _In_ int64_t seq, + _In_ serdata_t payload) +{ + memset(sampleinfo, 0, sizeof(*sampleinfo)); + sampleinfo->bswap = 0; + sampleinfo->complex_qos = 0; + sampleinfo->hashash = 0; + sampleinfo->seq = seq; + sampleinfo->reception_timestamp = payload->v.msginfo.timestamp; + sampleinfo->statusinfo = payload->v.msginfo.statusinfo; + sampleinfo->pwr_info.iid = 1; + sampleinfo->pwr_info.auto_dispose = 0; + sampleinfo->pwr_info.guid = wr->e.guid; + sampleinfo->pwr_info.ownership_strength = 0; +} + +static void writer_add_local_connection (struct writer *wr, struct reader *rd) +{ + struct wr_rd_match *m = os_malloc (sizeof (*m)); + ut_avlIPath_t path; + + os_mutexLock (&wr->e.lock); + if (ut_avlLookupIPath (&wr_local_readers_treedef, &wr->local_readers, &rd->e.guid, &path)) + { + nn_log(LC_DISCOVERY, " writer_add_local_connection(wr %x:%x:%x:%x rd %x:%x:%x:%x) - already connected\n", PGUID (wr->e.guid), PGUID (rd->e.guid)); + os_mutexUnlock (&wr->e.lock); + os_free (m); + return; + } + + nn_log(LC_DISCOVERY, " writer_add_local_connection(wr %x:%x:%x:%x rd %x:%x:%x:%x)", PGUID (wr->e.guid), PGUID (rd->e.guid)); + m->rd_guid = rd->e.guid; + ut_avlInsertIPath (&wr_local_readers_treedef, &wr->local_readers, m, &path); + local_reader_ary_insert (&wr->rdary, rd); + + /* Store available data into the late joining reader when it is reliable. */ + if ((rd->xqos->reliability.kind > NN_BEST_EFFORT_RELIABILITY_QOS) /* transient reader */ && + (!whc_empty(wr->whc)) /* data available */ ) + { + seqno_t seq = -1; + struct whc_node *n; + while ((n = whc_next_node(wr->whc, seq)) != NULL) + { + struct nn_rsample_info sampleinfo; + serdata_t payload = n->serdata; + struct tkmap_instance *tk = (ddsi_plugin.rhc_lookup_fn) (payload); + init_sampleinfo(&sampleinfo, wr, n->seq, payload); + (void)(ddsi_plugin.rhc_store_fn) (rd->rhc, &sampleinfo, payload, tk); + seq = n->seq; + } + } + + os_mutexUnlock (&wr->e.lock); + + nn_log(LC_DISCOVERY, "\n"); + + if (wr->status_cb) + { + status_cb_data_t data; + data.status = DDS_PUBLICATION_MATCHED_STATUS; + data.add = true; + data.handle = rd->e.iid; + (wr->status_cb) (wr->status_cb_entity, &data); + } + +} + +static void reader_add_connection (struct reader *rd, struct proxy_writer *pwr, nn_count_t *init_count) +{ + struct rd_pwr_match *m = os_malloc (sizeof (*m)); + ut_avlIPath_t path; + + m->pwr_guid = pwr->e.guid; + + os_mutexLock (&rd->e.lock); + + /* Initial sequence number of acknacks is the highest stored (+ 1, + done when generating the acknack) -- existing connections may be + beyond that already, but this guarantees that one particular + writer will always see monotonically increasing sequence numbers + from one particular reader. This is then used for the + pwr_rd_match initialization */ + nn_log (LC_DISCOVERY, " reader %x:%x:%x:%x init_acknack_count = %d\n", PGUID (rd->e.guid), rd->init_acknack_count); + *init_count = rd->init_acknack_count; + + if (ut_avlLookupIPath (&rd_writers_treedef, &rd->writers, &pwr->e.guid, &path)) + { + nn_log (LC_DISCOVERY, " reader_add_connection(pwr %x:%x:%x:%x rd %x:%x:%x:%x) - already connected\n", + PGUID (pwr->e.guid), PGUID (rd->e.guid)); + os_mutexUnlock (&rd->e.lock); + os_free (m); + } + else + { + nn_log (LC_DISCOVERY, " reader_add_connection(pwr %x:%x:%x:%x rd %x:%x:%x:%x)\n", + PGUID (pwr->e.guid), PGUID (rd->e.guid)); + ut_avlInsertIPath (&rd_writers_treedef, &rd->writers, m, &path); + os_mutexUnlock (&rd->e.lock); + +#ifdef DDSI_INCLUDE_SSM + if (rd->favours_ssm && pwr->supports_ssm) + { + /* pwr->supports_ssm is set if addrset_contains_ssm(pwr->ssm), so + any_ssm must succeed. */ + if (!addrset_any_uc (pwr->c.as, &m->ssm_src_loc)) + assert (0); + if (!addrset_any_ssm (pwr->c.as, &m->ssm_mc_loc)) + assert (0); + /* FIXME: for now, assume that the ports match for datasock_mc -- + 't would be better to dynamically create and destroy sockets on + an as needed basis. */ + ddsi_conn_join_mc (gv.data_conn_mc, &m->ssm_src_loc, &m->ssm_mc_loc); + } + else + { + set_unspec_locator (&m->ssm_src_loc); + set_unspec_locator (&m->ssm_mc_loc); + } +#endif + + if (rd->status_cb) + { + status_cb_data_t data; + data.status = DDS_SUBSCRIPTION_MATCHED_STATUS; + data.add = true; + data.handle = pwr->e.iid; + (rd->status_cb) (rd->status_cb_entity, &data); + } + } +} + +static void reader_add_local_connection (struct reader *rd, struct writer *wr) +{ + struct rd_wr_match *m = os_malloc (sizeof (*m)); + ut_avlIPath_t path; + + m->wr_guid = wr->e.guid; + + os_mutexLock (&rd->e.lock); + + if (ut_avlLookupIPath (&rd_local_writers_treedef, &rd->local_writers, &wr->e.guid, &path)) + { + nn_log(LC_DISCOVERY, " reader_add_local_connection(wr %x:%x:%x:%x rd %x:%x:%x:%x) - already connected\n", PGUID (wr->e.guid), PGUID (rd->e.guid)); + os_mutexUnlock (&rd->e.lock); + os_free (m); + } + else + { + nn_log(LC_DISCOVERY, " reader_add_local_connection(wr %x:%x:%x:%x rd %x:%x:%x:%x)\n", PGUID (wr->e.guid), PGUID (rd->e.guid)); + ut_avlInsertIPath (&rd_local_writers_treedef, &rd->local_writers, m, &path); + os_mutexUnlock (&rd->e.lock); + + if (rd->status_cb) + { + status_cb_data_t data; + data.add = true; + data.handle = wr->e.iid; + + data.status = DDS_LIVELINESS_CHANGED_STATUS; + (rd->status_cb) (rd->status_cb_entity, &data); + + data.status = DDS_SUBSCRIPTION_MATCHED_STATUS; + (rd->status_cb) (rd->status_cb_entity, &data); + } + } +} + +static void proxy_writer_add_connection (struct proxy_writer *pwr, struct reader *rd, nn_mtime_t tnow, nn_count_t init_count) +{ + struct pwr_rd_match *m = os_malloc (sizeof (*m)); + ut_avlIPath_t path; + seqno_t last_deliv_seq; + + os_mutexLock (&pwr->e.lock); + if (ut_avlLookupIPath (&pwr_readers_treedef, &pwr->readers, &rd->e.guid, &path)) + goto already_matched; + + if (pwr->c.topic == NULL && rd->topic) + pwr->c.topic = rd->topic; + if (pwr->ddsi2direct_cb == 0 && rd->ddsi2direct_cb != 0) + { + pwr->ddsi2direct_cb = rd->ddsi2direct_cb; + pwr->ddsi2direct_cbarg = rd->ddsi2direct_cbarg; + } + + nn_log (LC_DISCOVERY, " proxy_writer_add_connection(pwr %x:%x:%x:%x rd %x:%x:%x:%x)", + PGUID (pwr->e.guid), PGUID (rd->e.guid)); + m->rd_guid = rd->e.guid; + m->tcreate = now_mt (); + + + /* We track the last heartbeat count value per reader--proxy-writer + pair, so that we can correctly handle directed heartbeats. The + only reason to bother is to prevent a directed heartbeat (with + the FINAL flag clear) from causing AckNacks from all readers + instead of just the addressed ones. + + If we don't mind those extra AckNacks, we could track the count + at the proxy-writer and simply treat all incoming heartbeats as + undirected. */ + m->next_heartbeat = DDSI_COUNT_MIN; + m->hb_timestamp.v = 0; + m->t_heartbeat_accepted.v = 0; + m->t_last_nack.v = 0; + m->seq_last_nack = 0; + + /* These can change as a consequence of handling data and/or + discovery activities. The safe way of dealing with them is to + lock the proxy writer */ + last_deliv_seq = nn_reorder_next_seq (pwr->reorder) - 1; + if (!rd->handle_as_transient_local) + { + m->in_sync = PRMSS_SYNC; + } + else if (last_deliv_seq == 0) + { + /* proxy-writer hasn't seen any data yet, in which case this reader is in sync with the proxy writer (i.e., no reader-specific reorder buffer needed), but still should generate a notification when all historical data has been received, except for the built-in ones (for now anyway, it may turn out to be useful for determining discovery status). */ + m->in_sync = PRMSS_TLCATCHUP; + m->u.not_in_sync.end_of_tl_seq = MAX_SEQ_NUMBER; + if (m->in_sync != PRMSS_SYNC) + nn_log (LC_DISCOVERY, " - tlcatchup"); + } + else if (!config.conservative_builtin_reader_startup && is_builtin_entityid (rd->e.guid.entityid, ownvendorid) && !ut_avlIsEmpty (&pwr->readers)) + { + /* builtins really don't care about multiple copies */ + m->in_sync = PRMSS_SYNC; + } + else + { + /* normal transient-local, reader is behind proxy writer */ + m->in_sync = PRMSS_OUT_OF_SYNC; + m->u.not_in_sync.end_of_tl_seq = pwr->last_seq; + m->u.not_in_sync.end_of_out_of_sync_seq = last_deliv_seq; + nn_log(LC_DISCOVERY, " - out-of-sync %"PRId64, m->u.not_in_sync.end_of_out_of_sync_seq); + } + if (m->in_sync != PRMSS_SYNC) + pwr->n_readers_out_of_sync++; + m->count = init_count; + /* Spec says we may send a pre-emptive AckNack (8.4.2.3.4), hence we + schedule it for the configured delay * T_MILLISECOND. From then + on it it'll keep sending pre-emptive ones until the proxy writer + receives a heartbeat. (We really only need a pre-emptive AckNack + per proxy writer, but hopefully it won't make that much of a + difference in practice.) */ + if (rd->reliable) + { + m->acknack_xevent = qxev_acknack (pwr->evq, add_duration_to_mtime (tnow, config.preemptive_ack_delay), &pwr->e.guid, &rd->e.guid); + m->u.not_in_sync.reorder = + nn_reorder_new (NN_REORDER_MODE_NORMAL, config.secondary_reorder_maxsamples); + pwr->n_reliable_readers++; + } + else + { + m->acknack_xevent = NULL; + m->u.not_in_sync.reorder = + nn_reorder_new (NN_REORDER_MODE_MONOTONICALLY_INCREASING, config.secondary_reorder_maxsamples); + } + + ut_avlInsertIPath (&pwr_readers_treedef, &pwr->readers, m, &path); + local_reader_ary_insert(&pwr->rdary, rd); + os_mutexUnlock (&pwr->e.lock); + qxev_pwr_entityid (pwr, &rd->e.guid.prefix); + + nn_log (LC_DISCOVERY, "\n"); + + if (rd->status_cb) + { + status_cb_data_t data; + data.status = DDS_LIVELINESS_CHANGED_STATUS; + data.add = true; + data.handle = pwr->e.iid; + (rd->status_cb) (rd->status_cb_entity, &data); + } + + return; + +already_matched: + assert (is_builtin_entityid (pwr->e.guid.entityid, pwr->c.vendor) ? (pwr->c.topic == NULL) : (pwr->c.topic != NULL)); + nn_log (LC_DISCOVERY, " proxy_writer_add_connection(pwr %x:%x:%x:%x rd %x:%x:%x:%x) - already connected\n", + PGUID (pwr->e.guid), PGUID (rd->e.guid)); + os_mutexUnlock (&pwr->e.lock); + os_free (m); + return; +} + +static void proxy_reader_add_connection (struct proxy_reader *prd, struct writer *wr) +{ + struct prd_wr_match *m = os_malloc (sizeof (*m)); + ut_avlIPath_t path; + + m->wr_guid = wr->e.guid; + os_mutexLock (&prd->e.lock); + if (prd->c.topic == NULL) + prd->c.topic = wr->topic; + if (ut_avlLookupIPath (&prd_writers_treedef, &prd->writers, &wr->e.guid, &path)) + { + nn_log (LC_DISCOVERY, " proxy_reader_add_connection(wr %x:%x:%x:%x prd %x:%x:%x:%x) - already connected\n", + PGUID (wr->e.guid), PGUID (prd->e.guid)); + os_mutexUnlock (&prd->e.lock); + os_free (m); + } + else + { + nn_log (LC_DISCOVERY, " proxy_reader_add_connection(wr %x:%x:%x:%x prd %x:%x:%x:%x)\n", + PGUID (wr->e.guid), PGUID (prd->e.guid)); + ut_avlInsertIPath (&prd_writers_treedef, &prd->writers, m, &path); + os_mutexUnlock (&prd->e.lock); + qxev_prd_entityid (prd, &wr->e.guid.prefix); + } +} + +static nn_entityid_t builtin_entityid_match (nn_entityid_t x) +{ + nn_entityid_t res; + res.u = 0; + switch (x.u) + { + case NN_ENTITYID_SEDP_BUILTIN_TOPIC_WRITER: + res.u = NN_ENTITYID_SEDP_BUILTIN_TOPIC_READER; + break; + case NN_ENTITYID_SEDP_BUILTIN_TOPIC_READER: + res.u = NN_ENTITYID_SEDP_BUILTIN_TOPIC_WRITER; + break; + case NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_WRITER: + res.u = NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_READER; + break; + case NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_READER: + res.u = NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_WRITER; + break; + case NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_WRITER: + res.u = NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_READER; + break; + case NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_READER: + res.u = NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_WRITER; + break; + case NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_WRITER: + res.u = NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_READER; + break; + case NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_READER: + res.u = NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_WRITER; + break; + + case NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER: + case NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_READER: + /* SPDP is special cased because we don't -- indeed can't -- + match readers with writers, only send to matched readers and + only accept from matched writers. That way discovery wouldn't + work at all. No entity with NN_ENTITYID_UNKNOWN exists, + ever, so this guarantees no connection will be made. */ + res.u = NN_ENTITYID_UNKNOWN; + break; + + case NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_READER: + res.u = NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_WRITER; + break; + case NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_WRITER: + res.u = NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_READER; + break; + case NN_ENTITYID_SEDP_BUILTIN_CM_PUBLISHER_READER: + res.u = NN_ENTITYID_SEDP_BUILTIN_CM_PUBLISHER_WRITER; + break; + case NN_ENTITYID_SEDP_BUILTIN_CM_PUBLISHER_WRITER: + res.u = NN_ENTITYID_SEDP_BUILTIN_CM_PUBLISHER_READER; + break; + case NN_ENTITYID_SEDP_BUILTIN_CM_SUBSCRIBER_READER: + res.u = NN_ENTITYID_SEDP_BUILTIN_CM_SUBSCRIBER_WRITER; + break; + case NN_ENTITYID_SEDP_BUILTIN_CM_SUBSCRIBER_WRITER: + res.u = NN_ENTITYID_SEDP_BUILTIN_CM_SUBSCRIBER_READER; + break; + + default: + assert (0); + } + return res; +} + +static void writer_qos_mismatch (struct writer * wr, int32_t reason) +{ + /* When the reason is DDS_INVALID_QOS_POLICY_ID, it means that we compared + * readers/writers from different topics: ignore that. */ + if (reason != DDS_INVALID_QOS_POLICY_ID) + { + if (wr->topic->status_cb) { + /* Handle INCONSISTENT_TOPIC on topic */ + (wr->topic->status_cb) (wr->topic->status_cb_entity); + } + if (wr->status_cb) + { + status_cb_data_t data; + data.status = DDS_OFFERED_INCOMPATIBLE_QOS_STATUS; + data.extra = reason; + (wr->status_cb) (wr->status_cb_entity, &data); + } + } +} + +static void reader_qos_mismatch (struct reader * rd, int32_t reason) +{ + /* When the reason is DDS_INVALID_QOS_POLICY_ID, it means that we compared + * readers/writers from different topics: ignore that. */ + if (reason != DDS_INVALID_QOS_POLICY_ID) + { + if (rd->topic->status_cb) + { + /* Handle INCONSISTENT_TOPIC on topic */ + (rd->topic->status_cb) (rd->topic->status_cb_entity); + } + if (rd->status_cb) + { + status_cb_data_t data; + data.status = DDS_REQUESTED_INCOMPATIBLE_QOS_STATUS; + data.extra = reason; + (rd->status_cb) (rd->status_cb_entity, &data); + } + } +} + +static void connect_writer_with_proxy_reader (struct writer *wr, struct proxy_reader *prd, nn_mtime_t tnow) +{ + const int isb0 = (is_builtin_entityid (wr->e.guid.entityid, ownvendorid) != 0); + const int isb1 = (is_builtin_entityid (prd->e.guid.entityid, prd->c.vendor) != 0); + int32_t reason; + OS_UNUSED_ARG(tnow); + if (isb0 != isb1) + return; + if (wr->e.onlylocal) + return; + if (!isb0 && (reason = qos_match_p (prd->c.xqos, wr->xqos)) >= 0) + { + writer_qos_mismatch (wr, reason); + return; + } + proxy_reader_add_connection (prd, wr); + writer_add_connection (wr, prd); +} + +static void connect_proxy_writer_with_reader (struct proxy_writer *pwr, struct reader *rd, nn_mtime_t tnow) +{ + const int isb0 = (is_builtin_entityid (pwr->e.guid.entityid, pwr->c.vendor) != 0); + const int isb1 = (is_builtin_entityid (rd->e.guid.entityid, ownvendorid) != 0); + int32_t reason; + nn_count_t init_count; + if (isb0 != isb1) + return; + if (rd->e.onlylocal) + return; + if (!isb0 && (reason = qos_match_p (rd->xqos, pwr->c.xqos)) >= 0) + { + reader_qos_mismatch (rd, reason); + return; + } + reader_add_connection (rd, pwr, &init_count); + proxy_writer_add_connection (pwr, rd, tnow, init_count); +} + +static void connect_writer_with_reader (struct writer *wr, struct reader *rd, nn_mtime_t tnow) +{ + int32_t reason; + if (is_builtin_entityid (wr->e.guid.entityid, ownvendorid) || is_builtin_entityid (rd->e.guid.entityid, ownvendorid)) + return; + if ((reason = qos_match_p (rd->xqos, wr->xqos)) >= 0) + { + writer_qos_mismatch (wr, reason); + reader_qos_mismatch (rd, reason); + return; + } + reader_add_local_connection (rd, wr); + writer_add_local_connection (wr, rd); +} + +static void connect_writer_with_proxy_reader_wrapper (struct entity_common *vwr, struct entity_common *vprd, nn_mtime_t tnow) +{ + struct writer *wr = (struct writer *) vwr; + struct proxy_reader *prd = (struct proxy_reader *) vprd; + assert (wr->e.kind == EK_WRITER); + assert (prd->e.kind == EK_PROXY_READER); + connect_writer_with_proxy_reader(wr, prd, tnow); +} + +static void connect_proxy_writer_with_reader_wrapper (struct entity_common *vpwr, struct entity_common *vrd, nn_mtime_t tnow) +{ + struct proxy_writer *pwr = (struct proxy_writer *) vpwr; + struct reader *rd = (struct reader *) vrd; + assert (pwr->e.kind == EK_PROXY_WRITER); + assert (rd->e.kind == EK_READER); + connect_proxy_writer_with_reader(pwr, rd, tnow); +} + +static void connect_writer_with_reader_wrapper (struct entity_common *vwr, struct entity_common *vrd, nn_mtime_t tnow) +{ + struct writer *wr = (struct writer *) vwr; + struct reader *rd = (struct reader *) vrd; + assert (wr->e.kind == EK_WRITER); + assert (rd->e.kind == EK_READER); + connect_writer_with_reader(wr, rd, tnow); +} + +static enum entity_kind generic_do_match_mkind (enum entity_kind kind) +{ + switch (kind) + { + case EK_WRITER: return EK_PROXY_READER; + case EK_READER: return EK_PROXY_WRITER; + case EK_PROXY_WRITER: return EK_READER; + case EK_PROXY_READER: return EK_WRITER; + case EK_PARTICIPANT: + case EK_PROXY_PARTICIPANT: + assert(0); + return EK_WRITER; + } + assert(0); + return EK_WRITER; +} + +static enum entity_kind generic_do_local_match_mkind (enum entity_kind kind) +{ + switch (kind) + { + case EK_WRITER: return EK_READER; + case EK_READER: return EK_WRITER; + case EK_PROXY_WRITER: + case EK_PROXY_READER: + case EK_PARTICIPANT: + case EK_PROXY_PARTICIPANT: + assert(0); + return EK_WRITER; + } + assert(0); + return EK_WRITER; +} + +static const char *generic_do_match_kindstr_us (enum entity_kind kind) +{ + switch (kind) + { + case EK_WRITER: return "writer"; + case EK_READER: return "reader"; + case EK_PROXY_WRITER: return "proxy_writer"; + case EK_PROXY_READER: return "proxy_reader"; + case EK_PARTICIPANT: return "participant"; + case EK_PROXY_PARTICIPANT: return "proxy_participant"; + } + assert(0); + return "?"; +} + +static const char *generic_do_match_kindstr (enum entity_kind kind) +{ + switch (kind) + { + case EK_WRITER: return "writer"; + case EK_READER: return "reader"; + case EK_PROXY_WRITER: return "proxy writer"; + case EK_PROXY_READER: return "proxy reader"; + case EK_PARTICIPANT: return "participant"; + case EK_PROXY_PARTICIPANT: return "proxy participant"; + } + assert(0); + return "?"; +} + +static const char *generic_do_match_kindabbrev (enum entity_kind kind) +{ + switch (kind) + { + case EK_WRITER: return "wr"; + case EK_READER: return "rd"; + case EK_PROXY_WRITER: return "pwr"; + case EK_PROXY_READER: return "prd"; + case EK_PARTICIPANT: return "pp"; + case EK_PROXY_PARTICIPANT: return "proxypp"; + } + assert(0); + return "?"; +} + +static int generic_do_match_isproxy (const struct entity_common *e) +{ + return e->kind == EK_PROXY_WRITER || e->kind == EK_PROXY_READER || e->kind == EK_PROXY_PARTICIPANT; +} + +static void generic_do_match_connect (struct entity_common *e, struct entity_common *em, nn_mtime_t tnow) +{ + switch (e->kind) + { + case EK_WRITER: + connect_writer_with_proxy_reader_wrapper(e, em, tnow); + break; + case EK_READER: + connect_proxy_writer_with_reader_wrapper(em, e, tnow); + break; + case EK_PROXY_WRITER: + connect_proxy_writer_with_reader_wrapper(e, em, tnow); + break; + case EK_PROXY_READER: + connect_writer_with_proxy_reader_wrapper(em, e, tnow); + break; + case EK_PARTICIPANT: + case EK_PROXY_PARTICIPANT: + assert(0); + } +} + +static void generic_do_local_match_connect (struct entity_common *e, struct entity_common *em, nn_mtime_t tnow) +{ + switch (e->kind) + { + case EK_WRITER: + connect_writer_with_reader_wrapper(e, em, tnow); + break; + case EK_READER: + connect_writer_with_reader_wrapper(em, e, tnow); + break; + case EK_PROXY_WRITER: + case EK_PROXY_READER: + case EK_PARTICIPANT: + case EK_PROXY_PARTICIPANT: + assert(0); + } +} + +static void generic_do_match (struct entity_common *e, nn_mtime_t tnow) +{ + struct ephash_enum est; + struct entity_common *em; + enum entity_kind mkind = generic_do_match_mkind(e->kind); + if (!is_builtin_entityid (e->guid.entityid, ownvendorid)) + { + nn_log(LC_DISCOVERY, "match_%s_with_%ss(%s %x:%x:%x:%x) scanning all %ss\n", + generic_do_match_kindstr_us (e->kind), generic_do_match_kindstr_us (mkind), + generic_do_match_kindabbrev (e->kind), PGUID (e->guid), + generic_do_match_kindstr(mkind)); + /* Note: we visit at least all proxies that existed when we called + init (with the -- possible -- exception of ones that were + deleted between our calling init and our reaching it while + enumerating), but we may visit a single proxy reader multiple + times. */ + ephash_enum_init (&est, mkind); + os_rwlockRead (&gv.qoslock); + while ((em = ephash_enum_next (&est)) != NULL) + generic_do_match_connect(e, em, tnow); + os_rwlockUnlock (&gv.qoslock); + ephash_enum_fini (&est); + } + else + { + /* Built-ins have fixed QoS */ + nn_entityid_t tgt_ent = builtin_entityid_match (e->guid.entityid); + enum entity_kind pkind = generic_do_match_isproxy (e) ? EK_PARTICIPANT : EK_PROXY_PARTICIPANT; + nn_log(LC_DISCOVERY, "match_%s_with_%ss(%s %x:%x:%x:%x) scanning %sparticipants tgt=%x\n", + generic_do_match_kindstr_us (e->kind), generic_do_match_kindstr_us (mkind), + generic_do_match_kindabbrev (e->kind), PGUID (e->guid), + generic_do_match_isproxy (e) ? "" : "proxy ", + tgt_ent.u); + if (tgt_ent.u != NN_ENTITYID_UNKNOWN) + { + struct entity_common *ep; + ephash_enum_init (&est, pkind); + while ((ep = ephash_enum_next (&est)) != NULL) + { + nn_guid_t tgt_guid; + tgt_guid.prefix = ep->guid.prefix; + tgt_guid.entityid = tgt_ent; + if ((em = ephash_lookup_guid (&tgt_guid, mkind)) != NULL) + generic_do_match_connect(e, em, tnow); + } + ephash_enum_fini (&est); + } + } +} + +static void generic_do_local_match (struct entity_common *e, nn_mtime_t tnow) +{ + struct ephash_enum est; + struct entity_common *em; + enum entity_kind mkind; + if (is_builtin_entityid (e->guid.entityid, ownvendorid)) + /* never a need for local matches on discovery endpoints */ + return; + mkind = generic_do_local_match_mkind(e->kind); + nn_log(LC_DISCOVERY, "match_%s_with_%ss(%s %x:%x:%x:%x) scanning all %ss\n", + generic_do_match_kindstr_us (e->kind), generic_do_match_kindstr_us (mkind), + generic_do_match_kindabbrev (e->kind), PGUID (e->guid), + generic_do_match_kindstr(mkind)); + /* Note: we visit at least all proxies that existed when we called + init (with the -- possible -- exception of ones that were + deleted between our calling init and our reaching it while + enumerating), but we may visit a single proxy reader multiple + times. */ + ephash_enum_init (&est, mkind); + os_rwlockRead (&gv.qoslock); + while ((em = ephash_enum_next (&est)) != NULL) + generic_do_local_match_connect(e, em, tnow); + os_rwlockUnlock (&gv.qoslock); + ephash_enum_fini (&est); +} + +static void match_writer_with_proxy_readers (struct writer *wr, nn_mtime_t tnow) +{ + generic_do_match (&wr->e, tnow); +} + +static void match_writer_with_local_readers (struct writer *wr, nn_mtime_t tnow) +{ + generic_do_local_match (&wr->e, tnow); +} + +static void match_reader_with_proxy_writers (struct reader *rd, nn_mtime_t tnow) +{ + generic_do_match (&rd->e, tnow); +} + +static void match_reader_with_local_writers (struct reader *rd, nn_mtime_t tnow) +{ + generic_do_local_match (&rd->e, tnow); +} + +static void match_proxy_writer_with_readers (struct proxy_writer *pwr, nn_mtime_t tnow) +{ + generic_do_match (&pwr->e, tnow); +} + +static void match_proxy_reader_with_writers (struct proxy_reader *prd, nn_mtime_t tnow) +{ + generic_do_match(&prd->e, tnow); +} + +/* ENDPOINT --------------------------------------------------------- */ + +static void new_reader_writer_common (const struct nn_guid *guid, const struct sertopic * topic, const struct nn_xqos *xqos) +{ + const char *partition = "(default)"; + const char *partition_suffix = ""; + assert (is_builtin_entityid (guid->entityid, ownvendorid) ? (topic == NULL) : (topic != NULL)); + if (is_builtin_entityid (guid->entityid, ownvendorid)) + { + /* continue printing it as not being in a partition, the actual + value doesn't matter because it is never matched based on QoS + settings */ + partition = "(null)"; + } + else if ((xqos->present & QP_PARTITION) && xqos->partition.n > 0 && strcmp (xqos->partition.strs[0], "") != 0) + { + partition = xqos->partition.strs[0]; + if (xqos->partition.n > 1) + partition_suffix = "+"; + } + nn_log (LC_DISCOVERY, "new_%s(guid %x:%x:%x:%x, %s%s.%s/%s)\n", + is_writer_entityid (guid->entityid) ? "writer" : "reader", + PGUID (*guid), + partition, partition_suffix, + topic ? topic->name : "(null)", + topic ? topic->typename : "(null)"); +} + +static void endpoint_common_init +( + struct entity_common *e, + struct endpoint_common *c, + enum entity_kind kind, + const struct nn_guid *guid, + const struct nn_guid *group_guid, + struct participant *pp +) +{ + entity_common_init (e, guid, NULL, kind, pp->e.onlylocal); + c->pp = ref_participant (pp, &e->guid); + if (group_guid) + { + c->group_guid = *group_guid; + } + else + { + memset (&c->group_guid, 0, sizeof (c->group_guid)); + } +} + +static void endpoint_common_fini (struct entity_common *e, struct endpoint_common *c) +{ + unref_participant (c->pp, &e->guid); + entity_common_fini (e); +} + +static int set_topic_type_name (nn_xqos_t *xqos, const struct sertopic * topic) +{ + if (!(xqos->present & QP_TYPE_NAME) && topic) + { + xqos->present |= QP_TYPE_NAME; + xqos->type_name = os_strdup (topic->typename); + } + if (!(xqos->present & QP_TOPIC_NAME) && topic) + { + xqos->present |= QP_TOPIC_NAME; + xqos->topic_name = os_strdup (topic->name); + } + return 0; +} + +/* WRITER ----------------------------------------------------------- */ + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS +static uint32_t get_partitionid_from_mapping (const char *partition, const char *topic) +{ + struct config_partitionmapping_listelem *pm; + if ((pm = find_partitionmapping (partition, topic)) == NULL) + return 0; + else + { + nn_log (LC_DISCOVERY, "matched writer for topic \"%s\" in partition \"%s\" to networkPartition \"%s\"\n", topic, partition, pm->networkPartition); + return pm->partition->partitionId; + } +} +#endif /* DDSI_INCLUDE_NETWORK_PARTITIONS */ + +static void augment_wr_prd_match (void *vnode, const void *vleft, const void *vright) +{ + struct wr_prd_match *n = vnode; + const struct wr_prd_match *left = vleft; + const struct wr_prd_match *right = vright; + seqno_t min_seq, max_seq; + int have_replied = n->has_replied_to_hb; + + /* note: this means min <= seq, but not min <= max nor seq <= max! + note: this guarantees max < MAX_SEQ_NUMBER, which by induction + guarantees {left,right}.max < MAX_SEQ_NUMBER note: this treats a + reader that has not yet replied to a heartbeat as a demoted + one */ + min_seq = n->seq; + max_seq = (n->seq < MAX_SEQ_NUMBER) ? n->seq : 0; + + /* 1. Compute {min,max} & have_replied. */ + if (left) + { + if (left->min_seq < min_seq) + min_seq = left->min_seq; + if (left->max_seq > max_seq) + max_seq = left->max_seq; + have_replied = have_replied && left->all_have_replied_to_hb; + } + if (right) + { + if (right->min_seq < min_seq) + min_seq = right->min_seq; + if (right->max_seq > max_seq) + max_seq = right->max_seq; + have_replied = have_replied && right->all_have_replied_to_hb; + } + n->min_seq = min_seq; + n->max_seq = max_seq; + n->all_have_replied_to_hb = have_replied ? 1 : 0; + + /* 2. Compute num_reliable_readers_where_seq_equals_max */ + if (max_seq == 0) + { + /* excludes demoted & best-effort readers; note that max == 0 + cannot happen if {left,right}.max > 0 */ + n->num_reliable_readers_where_seq_equals_max = 0; + } + else + { + /* if demoted or best-effort, seq != max */ + n->num_reliable_readers_where_seq_equals_max = + (n->seq == max_seq && n->has_replied_to_hb); + if (left && left->max_seq == max_seq) + n->num_reliable_readers_where_seq_equals_max += + left->num_reliable_readers_where_seq_equals_max; + if (right && right->max_seq == max_seq) + n->num_reliable_readers_where_seq_equals_max += + right->num_reliable_readers_where_seq_equals_max; + } + + /* 3. Compute arbitrary unacked reader */ + /* 3a: maybe this reader is itself a candidate */ + if (n->seq < max_seq) + { + /* seq < max cannot be true for a best-effort reader or a demoted */ + n->arbitrary_unacked_reader = n->prd_guid; + } + else if (n->is_reliable && (n->seq == MAX_SEQ_NUMBER || !n->has_replied_to_hb)) + { + /* demoted readers and reliable readers that have not yet replied to a heartbeat are candidates */ + n->arbitrary_unacked_reader = n->prd_guid; + } + /* 3b: maybe we can inherit from the children */ + else if (left && left->arbitrary_unacked_reader.entityid.u != NN_ENTITYID_UNKNOWN) + { + n->arbitrary_unacked_reader = left->arbitrary_unacked_reader; + } + else if (right && right->arbitrary_unacked_reader.entityid.u != NN_ENTITYID_UNKNOWN) + { + n->arbitrary_unacked_reader = right->arbitrary_unacked_reader; + } + /* 3c: else it may be that we can now determine one of our children + is actually a candidate */ + else if (left && left->max_seq != 0 && left->max_seq < max_seq) + { + n->arbitrary_unacked_reader = left->prd_guid; + } + else if (right && right->max_seq != 0 && right->max_seq < max_seq) + { + n->arbitrary_unacked_reader = right->prd_guid; + } + /* 3d: else no candidate in entire subtree */ + else + { + n->arbitrary_unacked_reader.entityid.u = NN_ENTITYID_UNKNOWN; + } +} + +seqno_t writer_max_drop_seq (const struct writer *wr) +{ + const struct wr_prd_match *n; + if (ut_avlIsEmpty (&wr->readers)) + return wr->seq; + n = ut_avlRootNonEmpty (&wr_readers_treedef, &wr->readers); + return (n->min_seq == MAX_SEQ_NUMBER) ? wr->seq : n->min_seq; +} + +int writer_must_have_hb_scheduled (const struct writer *wr) +{ + if (ut_avlIsEmpty (&wr->readers) || whc_empty (wr->whc)) + { + /* Can't transmit a valid heartbeat if there is no data; and it + wouldn't actually be sent anywhere if there are no readers, so + there is little point in processing the xevent all the time. + + Note that add_msg_to_whc and add_proxy_reader_to_writer will + perform a reschedule. 8.4.2.2.3: need not (can't, really!) send + a heartbeat if no data is available. */ + return 0; + } + else if (!((const struct wr_prd_match *) ut_avlRootNonEmpty (&wr_readers_treedef, &wr->readers))->all_have_replied_to_hb) + { + /* Labouring under the belief that heartbeats must be sent + regardless of ack state */ + return 1; + } + else + { + /* DDSI 2.1, section 8.4.2.2.3: need not send heartbeats when all + messages have been acknowledged. Slightly different from + requiring a non-empty whc_seq: if it is transient_local, + whc_seq usually won't be empty even when all msgs have been + ack'd. */ + return writer_max_drop_seq (wr) < whc_max_seq (wr->whc); + } +} + +void writer_set_retransmitting (struct writer *wr) +{ + assert (!wr->retransmitting); + wr->retransmitting = 1; + if (config.whc_adaptive && wr->whc_high > wr->whc_low) + { + uint32_t m = 8 * wr->whc_high / 10; + wr->whc_high = (m > wr->whc_low) ? m : wr->whc_low; + } +} + +void writer_clear_retransmitting (struct writer *wr) +{ + wr->retransmitting = 0; + wr->t_whc_high_upd = wr->t_rexmit_end = now_et(); + os_condBroadcast (&wr->throttle_cond); +} + +unsigned remove_acked_messages (struct writer *wr, struct whc_node **deferred_free_list) +{ + unsigned n; + size_t n_unacked; + assert (wr->e.guid.entityid.u != NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER); + ASSERT_MUTEX_HELD (&wr->e.lock); + n = whc_remove_acked_messages (wr->whc, writer_max_drop_seq (wr), deferred_free_list); + /* when transitioning from >= low-water to < low-water, signal + anyone waiting in throttle_writer() */ + n_unacked = whc_unacked_bytes (wr->whc); + if (wr->throttling && n_unacked <= wr->whc_low) + os_condBroadcast (&wr->throttle_cond); + if (wr->retransmitting && whc_unacked_bytes (wr->whc) == 0) + writer_clear_retransmitting (wr); + if (wr->state == WRST_LINGERING && n_unacked == 0) + { + nn_log (LC_DISCOVERY, "remove_acked_messages: deleting lingering writer %x:%x:%x:%x\n", PGUID (wr->e.guid)); + delete_writer_nolinger_locked (wr); + } + return n; +} + +unsigned remove_acked_messages_and_free (struct writer *wr) +{ + struct whc_node *deferred_free_list; + unsigned n = remove_acked_messages (wr, &deferred_free_list); + whc_free_deferred_free_list (wr->whc, deferred_free_list); + return n; +} + +static struct writer * new_writer_guid +( + const struct nn_guid *guid, + const struct nn_guid *group_guid, + struct participant *pp, + const struct sertopic *topic, + const struct nn_xqos *xqos, + status_cb_t status_cb, + void * status_entity +) +{ + const size_t sample_overhead = 80; /* INFO_TS + DATA (approximate figure) + inline QoS */ + struct writer *wr; + nn_mtime_t tnow = now_mt (); + + assert (is_writer_entityid (guid->entityid)); + assert (ephash_lookup_writer_guid (guid) == NULL); + assert (memcmp (&guid->prefix, &pp->e.guid.prefix, sizeof (guid->prefix)) == 0); + + new_reader_writer_common (guid, topic, xqos); + wr = os_malloc (sizeof (*wr)); + + /* want a pointer to the participant so that a parallel call to + delete_participant won't interfere with our ability to address + the participant */ + + endpoint_common_init (&wr->e, &wr->c, EK_WRITER, guid, group_guid, pp); + + os_condInit (&wr->throttle_cond, &wr->e.lock); + wr->seq = 0; + wr->cs_seq = 0; + INIT_SEQ_XMIT(wr, 0); + wr->hbcount = 0; + wr->state = WRST_OPERATIONAL; + wr->hbfragcount = 0; + writer_hbcontrol_init (&wr->hbcontrol); + wr->throttling = 0; + wr->retransmitting = 0; + wr->t_rexmit_end.v = 0; + wr->t_whc_high_upd.v = 0; + wr->num_reliable_readers = 0; + wr->num_acks_received = 0; + wr->num_nacks_received = 0; + wr->throttle_count = 0; + wr->throttle_tracing = 0; + wr->rexmit_count = 0; + wr->rexmit_lost_count = 0; + + wr->status_cb = status_cb; + wr->status_cb_entity = status_entity; + + /* Copy QoS, merging in defaults */ + + wr->xqos = os_malloc (sizeof (*wr->xqos)); + nn_xqos_copy (wr->xqos, xqos); + nn_xqos_mergein_missing (wr->xqos, &gv.default_xqos_wr); + assert (wr->xqos->aliased == 0); + set_topic_type_name (wr->xqos, topic); + + if (config.enabled_logcats & LC_DISCOVERY) + { + nn_log (LC_DISCOVERY, "WRITER %x:%x:%x:%x QOS={", PGUID (wr->e.guid)); + nn_log_xqos (LC_DISCOVERY, wr->xqos); + nn_log (LC_DISCOVERY, "}\n"); + } + assert (wr->xqos->present & QP_RELIABILITY); + wr->reliable = (wr->xqos->reliability.kind != NN_BEST_EFFORT_RELIABILITY_QOS); + assert (wr->xqos->present & QP_DURABILITY); + if (is_builtin_entityid (wr->e.guid.entityid, ownvendorid)) + { + assert (wr->xqos->history.kind == NN_KEEP_LAST_HISTORY_QOS); + assert (wr->xqos->durability.kind == NN_TRANSIENT_LOCAL_DURABILITY_QOS); + wr->aggressive_keep_last = 1; + } + else + { + wr->aggressive_keep_last = (config.aggressive_keep_last_whc && wr->xqos->history.kind == NN_KEEP_LAST_HISTORY_QOS); + } + wr->handle_as_transient_local = (wr->xqos->durability.kind == NN_TRANSIENT_LOCAL_DURABILITY_QOS); + wr->include_keyhash = + config.generate_keyhash && + ((wr->e.guid.entityid.u & NN_ENTITYID_KIND_MASK) == NN_ENTITYID_KIND_WRITER_WITH_KEY); + /* Startup mode causes the writer to treat data in its WHC as if + transient-local, for the first few seconds after startup of the + DDSI service. It is done for volatile reliable writers only + (which automatically excludes all builtin writers) or for all + writers except volatile best-effort & transient-local ones. + + Which one to use depends on whether merge policies are in effect + in durability. If yes, then durability will take care of all + transient & persistent data; if no, DDSI discovery usually takes + too long and this'll save you. + + Note: may still be cleared, if it turns out we are not maintaining + an index at all (e.g., volatile KEEP_ALL) */ + if (config.startup_mode_full) { + wr->startup_mode = gv.startup_mode && + (wr->xqos->durability.kind >= NN_TRANSIENT_DURABILITY_QOS || + (wr->xqos->durability.kind == NN_VOLATILE_DURABILITY_QOS && + wr->xqos->reliability.kind != NN_BEST_EFFORT_RELIABILITY_QOS)); + } else { + wr->startup_mode = gv.startup_mode && + (wr->xqos->durability.kind == NN_VOLATILE_DURABILITY_QOS && + wr->xqos->reliability.kind != NN_BEST_EFFORT_RELIABILITY_QOS); + } + if (topic) + { + os_atomic_inc32 (&((struct sertopic *)topic)->refcount); + } + wr->topic = topic; + wr->as = new_addrset (); + wr->as_group = NULL; + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + { + unsigned i; + /* This is an open issue how to encrypt mesages send for various + partitions that match multiple network partitions. From a safety + point of view a wierd configuration. Here we chose the first one + that we find */ + wr->partition_id = 0; + for (i = 0; i < wr->xqos->partition.n && wr->partition_id == 0; i++) + wr->partition_id = get_partitionid_from_mapping (wr->xqos->partition.strs[i], wr->xqos->topic_name); + } +#endif /* DDSI_INCLUDE_NETWORK_PARTITIONS */ + +#ifdef DDSI_INCLUDE_SSM + /* Writer supports SSM if it is mapped to a network partition for + which the address set includes an SSM address. If it supports + SSM, it arbitrarily selects one SSM address from the address set + to advertise. */ + wr->supports_ssm = 0; + wr->ssm_as = NULL; + if (config.allowMulticast & AMC_SSM) + { + nn_locator_t loc; + int have_loc = 0; + if (wr->partition_id == 0) + { + if (is_ssm_mcaddr (&gv.loc_default_mc)) + { + loc = gv.loc_default_mc; + have_loc = 1; + } + } + else + { + const struct config_networkpartition_listelem *np = find_networkpartition_by_id (wr->partition_id); + assert (np); + if (addrset_any_ssm (np->as, &loc)) + have_loc = 1; + } + if (have_loc) + { + wr->supports_ssm = 1; + wr->ssm_as = new_addrset (); + add_to_addrset (wr->ssm_as, &loc); + nn_log (LC_DISCOVERY, "writer %x:%x:%x:%x: ssm=%d", PGUID (wr->e.guid), wr->supports_ssm); + nn_log_addrset (LC_DISCOVERY, "", wr->ssm_as); + nn_log (LC_DISCOVERY, "\n"); + } + } +#endif + + /* for non-builtin writers, select the eventqueue based on the channel it is mapped to */ + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + if (!is_builtin_entityid (wr->e.guid.entityid, ownvendorid)) + { + struct config_channel_listelem *channel = find_channel (wr->xqos->transport_priority); + nn_log (LC_DISCOVERY, "writer %x:%x:%x:%x: transport priority %d => channel '%s' priority %d\n", + PGUID (wr->e.guid), wr->xqos->transport_priority.value, channel->name, channel->priority); + wr->evq = channel->evq ? channel->evq : gv.xevents; + } + else +#endif + { + wr->evq = gv.xevents; + } + + /* heartbeat event will be deleted when the handler can't find a + writer for it in the hash table. T_NEVER => won't ever be + scheduled, and this can only change by writing data, which won't + happen until after it becomes visible. */ + if (wr->reliable) + { + nn_mtime_t tsched; + tsched.v = T_NEVER; + wr->heartbeat_xevent = qxev_heartbeat (wr->evq, tsched, &wr->e.guid); + } + else + { + wr->heartbeat_xevent = NULL; + } + assert (wr->xqos->present & QP_LIVELINESS); + if (wr->xqos->liveliness.kind != NN_AUTOMATIC_LIVELINESS_QOS || + nn_from_ddsi_duration (wr->xqos->liveliness.lease_duration) != T_NEVER) + { + nn_log (LC_INFO | LC_DISCOVERY, "writer %x:%x:%x:%x: incorrectly treating it as of automatic liveliness kind with lease duration = inf (%d, %"PRId64")\n", PGUID (wr->e.guid), (int) wr->xqos->liveliness.kind, nn_from_ddsi_duration (wr->xqos->liveliness.lease_duration)); + } + wr->lease_duration = T_NEVER; /* FIXME */ + + /* Construct WHC -- if aggressive_keep_last1 is set, the WHC will + drop all samples for which a later update is available. This + forces it to maintain a tlidx. */ + { + unsigned hdepth, tldepth; + if (!wr->aggressive_keep_last || wr->xqos->history.kind == NN_KEEP_ALL_HISTORY_QOS) + hdepth = 0; + else + hdepth = (unsigned)wr->xqos->history.depth; + if (wr->handle_as_transient_local) { + if (wr->xqos->durability_service.history.kind == NN_KEEP_ALL_HISTORY_QOS) + tldepth = 0; + else + tldepth = (unsigned)wr->xqos->durability_service.history.depth; + } else if (wr->startup_mode) { + tldepth = hdepth; + } else { + tldepth = 0; + } + if (hdepth == 0 && tldepth == 0) + { + /* no index at all - so no need to bother with startup mode */ + wr->startup_mode = 0; + } + wr->whc = whc_new (wr->handle_as_transient_local, hdepth, tldepth, sample_overhead); + if (hdepth > 0) + { + /* hdepth > 0 => "aggressive keep last", and in that case: why + bother blocking for a slow receiver when the entire point of + KEEP_LAST is to keep going (at least in a typical interpretation + of the spec. */ + wr->whc_low = wr->whc_high = INT32_MAX; + } + else + { + wr->whc_low = config.whc_lowwater_mark; + wr->whc_high = config.whc_init_highwater_mark.value; + } + assert (!is_builtin_entityid(wr->e.guid.entityid, ownvendorid) || (wr->whc_low == wr->whc_high && wr->whc_low == INT32_MAX)); + } + + /* Connection admin */ + ut_avlInit (&wr_readers_treedef, &wr->readers); + ut_avlInit (&wr_local_readers_treedef, &wr->local_readers); + + local_reader_ary_init (&wr->rdary); + + /* guid_hash needed for protocol handling, so add it before we send + out our first message. Also: needed for matching, and swapping + the order if hash insert & matching creates a window during which + neither of two endpoints being created in parallel can discover + the other. */ + ephash_insert_writer_guid (wr); + + /* once it exists, match it with proxy writers and broadcast + existence (I don't think it matters much what the order of these + two is, but it seems likely that match-then-broadcast has a + slightly lower likelihood that a response from a proxy reader + gets dropped) -- but note that without adding a lock it might be + deleted while we do so */ + match_writer_with_proxy_readers (wr, tnow); + match_writer_with_local_readers (wr, tnow); + sedp_write_writer (wr); + + if (wr->lease_duration != T_NEVER) + { + nn_mtime_t tsched = { 0 }; + resched_xevent_if_earlier (pp->pmd_update_xevent, tsched); + } + + return wr; +} + +struct writer * new_writer +( + struct nn_guid *wrguid, + const struct nn_guid *group_guid, + const struct nn_guid *ppguid, + const struct sertopic *topic, + const struct nn_xqos *xqos, + status_cb_t status_cb, + void * status_cb_arg +) +{ + struct participant *pp; + struct writer * wr; + unsigned entity_kind; + + if ((pp = ephash_lookup_participant_guid (ppguid)) == NULL) + { + nn_log (LC_DISCOVERY, "new_writer - participant %x:%x:%x:%x not found\n", PGUID (*ppguid)); + return NULL; + } + /* participant can't be freed while we're mucking around cos we are + awake and do not touch the thread's vtime (ephash_lookup already + verifies we're awake) */ + entity_kind = (topic->nkeys ? NN_ENTITYID_KIND_WRITER_WITH_KEY : NN_ENTITYID_KIND_WRITER_NO_KEY); + wrguid->prefix = pp->e.guid.prefix; + if (pp_allocate_entityid (&wrguid->entityid, entity_kind, pp) < 0) + return NULL; + wr = new_writer_guid (wrguid, group_guid, pp, topic, xqos, status_cb, status_cb_arg); + return wr; +} + +static void gc_delete_writer (struct gcreq *gcreq) +{ + struct writer *wr = gcreq->arg; + nn_log (LC_DISCOVERY, "gc_delete_writer(%p, %x:%x:%x:%x)\n", (void *) gcreq, PGUID (wr->e.guid)); + gcreq_free (gcreq); + + /* We now allow GC while blocked on a full WHC, but we still don't allow deleting a writer while blocked on it. The writer's state must be DELETING by the time we get here, and that means the transmit path is no longer blocked. It doesn't imply that the write thread is no longer in throttle_writer(), just that if it is, it will soon return from there. Therefore, block until it isn't throttling anymore. We can safely lock the writer, as we're on the separate GC thread. */ + assert (wr->state == WRST_DELETING); + assert (!wr->throttling); + + if (wr->heartbeat_xevent) + { + wr->hbcontrol.tsched.v = T_NEVER; + delete_xevent (wr->heartbeat_xevent); + } + + /* Tear down connections -- no proxy reader can be adding/removing + us now, because we can't be found via guid_hash anymore. We + therefore need not take lock. */ + + while (!ut_avlIsEmpty (&wr->readers)) + { + struct wr_prd_match *m = ut_avlRootNonEmpty (&wr_readers_treedef, &wr->readers); + ut_avlDelete (&wr_readers_treedef, &wr->readers, m); + proxy_reader_drop_connection (&m->prd_guid, wr); + free_wr_prd_match (m); + } + while (!ut_avlIsEmpty (&wr->local_readers)) + { + struct wr_rd_match *m = ut_avlRootNonEmpty (&wr_local_readers_treedef, &wr->local_readers); + ut_avlDelete (&wr_local_readers_treedef, &wr->local_readers, m); + reader_drop_local_connection (&m->rd_guid, wr); + free_wr_rd_match (m); + } + + /* Do last gasp on SEDP and free writer. */ + if (!is_builtin_entityid (wr->e.guid.entityid, ownvendorid)) + sedp_dispose_unregister_writer (wr); + if (wr->status_cb) + { + (wr->status_cb) (wr->status_cb_entity, NULL); + } + + whc_free (wr->whc); +#ifdef DDSI_INCLUDE_SSM + if (wr->ssm_as) + unref_addrset (wr->ssm_as); +#endif + unref_addrset (wr->as); /* must remain until readers gone (rebuilding of addrset) */ + nn_xqos_fini (wr->xqos); + os_free (wr->xqos); + local_reader_ary_fini (&wr->rdary); + os_condDestroy (&wr->throttle_cond); + + sertopic_free ((struct sertopic *) wr->topic); + endpoint_common_fini (&wr->e, &wr->c); + os_free (wr); +} + +static void gc_delete_writer_throttlewait (struct gcreq *gcreq) +{ + struct writer *wr = gcreq->arg; + nn_log (LC_DISCOVERY, "gc_delete_writer_throttlewait(%p, %x:%x:%x:%x)\n", (void *) gcreq, PGUID (wr->e.guid)); + /* We now allow GC while blocked on a full WHC, but we still don't allow deleting a writer while blocked on it. The writer's state must be DELETING by the time we get here, and that means the transmit path is no longer blocked. It doesn't imply that the write thread is no longer in throttle_writer(), just that if it is, it will soon return from there. Therefore, block until it isn't throttling anymore. We can safely lock the writer, as we're on the separate GC thread. */ + assert (wr->state == WRST_DELETING); + os_mutexLock (&wr->e.lock); + while (wr->throttling) + os_condWait (&wr->throttle_cond, &wr->e.lock); + os_mutexUnlock (&wr->e.lock); + gcreq_requeue (gcreq, gc_delete_writer); +} + +static void writer_set_state (struct writer *wr, enum writer_state newstate) +{ + ASSERT_MUTEX_HELD (&wr->e.lock); + nn_log (LC_DISCOVERY, "writer_set_state(%x:%x:%x:%x) state transition %d -> %d\n", PGUID (wr->e.guid), wr->state, newstate); + assert (newstate > wr->state); + if (wr->state == WRST_OPERATIONAL) + { + /* Unblock all throttled writers (alternative method: clear WHC -- + but with parallel writes and very small limits on the WHC size, + that doesn't guarantee no-one will block). A truly blocked + write() is a problem because it prevents the gc thread from + cleaning up the writer. (Note: late assignment to wr->state is + ok, 'tis all protected by the writer lock.) */ + os_condBroadcast (&wr->throttle_cond); + } + wr->state = newstate; +} + +int delete_writer_nolinger_locked (struct writer *wr) +{ + nn_log (LC_DISCOVERY, "delete_writer_nolinger(guid %x:%x:%x:%x) ...\n", PGUID (wr->e.guid)); + ASSERT_MUTEX_HELD (&wr->e.lock); + local_reader_ary_setinvalid (&wr->rdary); + ephash_remove_writer_guid (wr); + writer_set_state (wr, WRST_DELETING); + gcreq_writer (wr); + return 0; +} + +int delete_writer_nolinger (const struct nn_guid *guid) +{ + struct writer *wr; + /* We take no care to ensure application writers are not deleted + while they still have unacknowledged data (unless it takes too + long), but we don't care about the DDSI built-in writers: we deal + with that anyway because of the potential for crashes of remote + DDSI participants. But it would be somewhat more elegant to do it + differently. */ + assert (is_writer_entityid (guid->entityid)); + if ((wr = ephash_lookup_writer_guid (guid)) == NULL) + { + nn_log (LC_DISCOVERY, "delete_writer_nolinger(guid %x:%x:%x:%x) - unknown guid\n", PGUID (*guid)); + return ERR_UNKNOWN_ENTITY; + } + nn_log (LC_DISCOVERY, "delete_writer_nolinger(guid %x:%x:%x:%x) ...\n", PGUID (*guid)); + os_mutexLock (&wr->e.lock); + delete_writer_nolinger_locked (wr); + os_mutexUnlock (&wr->e.lock); + return 0; +} + +int delete_writer (const struct nn_guid *guid) +{ + struct writer *wr; + if ((wr = ephash_lookup_writer_guid (guid)) == NULL) + { + nn_log (LC_DISCOVERY, "delete_writer(guid %x:%x:%x:%x) - unknown guid\n", PGUID (*guid)); + return ERR_UNKNOWN_ENTITY; + } + nn_log (LC_DISCOVERY, "delete_writer(guid %x:%x:%x:%x) ...\n", PGUID (*guid)); + os_mutexLock (&wr->e.lock); + + /* If no unack'ed data, don't waste time or resources (expected to + be the usual case), do it immediately. If more data is still + coming in (which can't really happen at the moment, but might + again in the future) it'll potentially be discarded. */ + if (whc_unacked_bytes (wr->whc) == 0) + { + nn_log (LC_DISCOVERY, "delete_writer(guid %x:%x:%x:%x) - no unack'ed samples\n", PGUID (*guid)); + delete_writer_nolinger_locked (wr); + os_mutexUnlock (&wr->e.lock); + } + else + { + nn_mtime_t tsched; + int tsec, tusec; + writer_set_state (wr, WRST_LINGERING); + os_mutexUnlock (&wr->e.lock); + tsched = add_duration_to_mtime (now_mt (), config.writer_linger_duration); + mtime_to_sec_usec (&tsec, &tusec, tsched); + nn_log (LC_DISCOVERY, "delete_writer(guid %x:%x:%x:%x) - unack'ed samples, will delete when ack'd or at t = %d.%06d\n", + PGUID (*guid), tsec, tusec); + qxev_delete_writer (tsched, &wr->e.guid); + } + return 0; +} + +void writer_exit_startup_mode (struct writer *wr) +{ + os_mutexLock (&wr->e.lock); + /* startup mode and handle_as_transient_local may not both be set */ + assert (!(wr->startup_mode && wr->handle_as_transient_local)); + if (!wr->startup_mode) + nn_log (LC_DISCOVERY, " wr %x:%x:%x:%x skipped\n", PGUID (wr->e.guid)); + else + { + unsigned n; + assert (wr->e.guid.entityid.u != NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER); + wr->startup_mode = 0; + whc_downgrade_to_volatile (wr->whc); + n = remove_acked_messages_and_free (wr); + writer_clear_retransmitting (wr); + nn_log (LC_DISCOVERY, " wr %x:%x:%x:%x dropped %u entr%s\n", PGUID (wr->e.guid), n, n == 1 ? "y" : "ies"); + } + os_mutexUnlock (&wr->e.lock); +} + +uint64_t writer_instance_id (const struct nn_guid *guid) +{ + struct entity_common *e; + e = (struct entity_common*)ephash_lookup_writer_guid(guid); + if (e) { + return e->iid; + } + e = (struct entity_common*)ephash_lookup_proxy_writer_guid(guid); + if (e) { + return e->iid; + } + return 0; +} + +/* READER ----------------------------------------------------------- */ + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS +static struct addrset * get_as_from_mapping (const char *partition, const char *topic) +{ + struct config_partitionmapping_listelem *pm; + struct addrset *as = new_addrset (); + if ((pm = find_partitionmapping (partition, topic)) != NULL) + { + nn_log (LC_DISCOVERY, "matched reader for topic \"%s\" in partition \"%s\" to networkPartition \"%s\"\n", topic, partition, pm->networkPartition); + assert (pm->partition->as); + copy_addrset_into_addrset (as, pm->partition->as); + } + return as; +} + +static void join_mcast_helper (const nn_locator_t *n, void * varg) +{ + ddsi_tran_conn_t conn = (ddsi_tran_conn_t) varg; + if (is_mcaddr (n)) + { + if (n->kind != NN_LOCATOR_KIND_UDPv4MCGEN) + { + if (ddsi_conn_join_mc (conn, NULL, n) < 0) + { + nn_log (LC_WARNING, "failed to join network partition multicast group\n"); + } + } + else /* join all addresses that include this node */ + { + { + nn_locator_t l = *n; + nn_udpv4mcgen_address_t l1; + uint32_t iph; + unsigned i; + memcpy(&l1, l.address, sizeof(l1)); + l.kind = NN_LOCATOR_KIND_UDPv4; + memset(l.address, 0, 12); + iph = ntohl(l1.ipv4.s_addr); + for (i = 1; i < (1u << l1.count); i++) + { + uint32_t ipn, iph1 = iph; + if (i & (1u << l1.idx)) + { + iph1 |= (i << l1.base); + ipn = htonl(iph1); + memcpy(l.address + 12, &ipn, 4); + if (ddsi_conn_join_mc (conn, NULL, &l) < 0) + { + nn_log (LC_WARNING, "failed to join network partition multicast group\n"); + } + } + } + } + } + } +} + +static void leave_mcast_helper (const nn_locator_t *n, void * varg) +{ + ddsi_tran_conn_t conn = (ddsi_tran_conn_t) varg; + if (is_mcaddr (n)) + { + if (n->kind != NN_LOCATOR_KIND_UDPv4MCGEN) + { + if (ddsi_conn_leave_mc (conn, NULL, n) < 0) + { + nn_log (LC_WARNING, "failed to leave network partition multicast group\n"); + } + } + else /* join all addresses that include this node */ + { + { + nn_locator_t l = *n; + nn_udpv4mcgen_address_t l1; + uint32_t iph; + unsigned i; + memcpy(&l1, l.address, sizeof(l1)); + l.kind = NN_LOCATOR_KIND_UDPv4; + memset(l.address, 0, 12); + iph = ntohl(l1.ipv4.s_addr); + for (i = 1; i < (1u << l1.count); i++) + { + uint32_t ipn, iph1 = iph; + if (i & (1u << l1.idx)) + { + iph1 |= (i << l1.base); + ipn = htonl(iph1); + memcpy(l.address + 12, &ipn, 4); + if (ddsi_conn_leave_mc (conn, NULL, &l) < 0) + { + nn_log (LC_WARNING, "failed to leave network partition multicast group\n"); + } + } + } + } + } + } +} +#endif /* DDSI_INCLUDE_NETWORK_PARTITIONS */ + +static struct reader * new_reader_guid +( + const struct nn_guid *guid, + const struct nn_guid *group_guid, + struct participant *pp, + const struct sertopic *topic, + const struct nn_xqos *xqos, + struct rhc *rhc, + status_cb_t status_cb, + void * status_entity +) +{ + /* see new_writer_guid for commenets */ + + struct reader * rd; + nn_mtime_t tnow = now_mt (); + + assert (!is_writer_entityid (guid->entityid)); + assert (ephash_lookup_reader_guid (guid) == NULL); + assert (memcmp (&guid->prefix, &pp->e.guid.prefix, sizeof (guid->prefix)) == 0); + + new_reader_writer_common (guid, topic, xqos); + rd = os_malloc (sizeof (*rd)); + + endpoint_common_init (&rd->e, &rd->c, EK_READER, guid, group_guid, pp); + + /* Copy QoS, merging in defaults */ + rd->xqos = os_malloc (sizeof (*rd->xqos)); + nn_xqos_copy (rd->xqos, xqos); + nn_xqos_mergein_missing (rd->xqos, &gv.default_xqos_rd); + assert (rd->xqos->aliased == 0); + set_topic_type_name (rd->xqos, topic); + + if (config.enabled_logcats & LC_DISCOVERY) + { + nn_log (LC_DISCOVERY, "READER %x:%x:%x:%x QOS={", PGUID (rd->e.guid)); + nn_log_xqos (LC_DISCOVERY, rd->xqos); + nn_log (LC_DISCOVERY, "}\n"); + } + assert (rd->xqos->present & QP_RELIABILITY); + rd->reliable = (rd->xqos->reliability.kind != NN_BEST_EFFORT_RELIABILITY_QOS); + assert (rd->xqos->present & QP_DURABILITY); + rd->handle_as_transient_local = (rd->xqos->durability.kind == NN_TRANSIENT_LOCAL_DURABILITY_QOS); + if (topic) + { + os_atomic_inc32 (&((struct sertopic *)topic)->refcount); + } + rd->topic = topic; + rd->ddsi2direct_cb = 0; + rd->ddsi2direct_cbarg = 0; + rd->init_acknack_count = 0; +#ifdef DDSI_INCLUDE_SSM + rd->favours_ssm = 0; +#endif + if (topic == NULL) + { + assert (is_builtin_entityid (rd->e.guid.entityid, ownvendorid)); + } + rd->status_cb = status_cb; + rd->status_cb_entity = status_entity; + rd->rhc = rhc; + /* set rhc qos for reader */ + if (rhc) + { + (ddsi_plugin.rhc_set_qos_fn) (rd->rhc, rd->xqos); + } + assert (rd->xqos->present & QP_LIVELINESS); + if (rd->xqos->liveliness.kind != NN_AUTOMATIC_LIVELINESS_QOS || + nn_from_ddsi_duration (rd->xqos->liveliness.lease_duration) != T_NEVER) + { + nn_log (LC_INFO | LC_DISCOVERY, "reader %x:%x:%x:%x: incorrectly treating it as of automatic liveliness kind with lease duration = inf (%d, %"PRId64")\n", PGUID (rd->e.guid), (int) rd->xqos->liveliness.kind, nn_from_ddsi_duration (rd->xqos->liveliness.lease_duration)); + } + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + rd->as = new_addrset (); + if (config.allowMulticast & ~AMC_SPDP) + { + unsigned i; + + /* compile address set from the mapped network partitions */ + for (i = 0; i < rd->xqos->partition.n; i++) + { + struct addrset *pas = get_as_from_mapping (rd->xqos->partition.strs[i], rd->xqos->topic_name); + if (pas) + { +#ifdef DDSI_INCLUDE_SSM + copy_addrset_into_addrset_no_ssm (rd->as, pas); + if (addrset_contains_ssm (pas) && config.allowMulticast & AMC_SSM) + rd->favours_ssm = 1; +#else + copy_addrset_into_addrset (rd->as, pas); +#endif + unref_addrset (pas); + } + } + if (!addrset_empty (rd->as)) + { + /* Iterate over all udp addresses: + * - Set the correct portnumbers + * - Join the socket if a multicast address + */ + addrset_forall (rd->as, join_mcast_helper, gv.data_conn_mc); + if (config.enabled_logcats & LC_DISCOVERY) + { + nn_log (LC_DISCOVERY, "READER %x:%x:%x:%x locators={", PGUID (rd->e.guid)); + nn_log_addrset (LC_DISCOVERY, "", rd->as); + nn_log (LC_DISCOVERY, "}\n"); + } + } +#ifdef DDSI_INCLUDE_SSM + else + { + /* Note: SSM requires NETWORK_PARTITIONS; if network partitions + do not override the default, we should check whether the + default is an SSM address. */ + if (is_ssm_mcaddr (&gv.loc_default_mc) && config.allowMulticast & AMC_SSM) + rd->favours_ssm = 1; + } +#endif + } +#ifdef DDSI_INCLUDE_SSM + if (rd->favours_ssm) + nn_log (LC_DISCOVERY, "READER %x:%x:%x:%x ssm=%d\n", PGUID (rd->e.guid), rd->favours_ssm); +#endif +#endif + + ut_avlInit (&rd_writers_treedef, &rd->writers); + ut_avlInit (&rd_local_writers_treedef, &rd->local_writers); + + ephash_insert_reader_guid (rd); + match_reader_with_proxy_writers (rd, tnow); + match_reader_with_local_writers (rd, tnow); + sedp_write_reader (rd); + return rd; +} + +struct reader * new_reader +( + struct nn_guid *rdguid, + const struct nn_guid *group_guid, + const struct nn_guid *ppguid, + const struct sertopic *topic, + const struct nn_xqos *xqos, + struct rhc * rhc, + status_cb_t status_cb, + void * status_cbarg +) +{ + struct participant * pp; + struct reader * rd; + unsigned entity_kind; + + if ((pp = ephash_lookup_participant_guid (ppguid)) == NULL) + { + nn_log (LC_DISCOVERY, "new_reader - participant %x:%x:%x:%x not found\n", PGUID (*ppguid)); + return NULL; + } + entity_kind = (topic->nkeys ? NN_ENTITYID_KIND_READER_WITH_KEY : NN_ENTITYID_KIND_READER_NO_KEY); + rdguid->prefix = pp->e.guid.prefix; + if (pp_allocate_entityid (&rdguid->entityid, entity_kind, pp) < 0) + return NULL; + rd = new_reader_guid (rdguid, group_guid, pp, topic, xqos, rhc, status_cb, status_cbarg); + return rd; +} + +static void gc_delete_reader (struct gcreq *gcreq) +{ + /* see gc_delete_writer for comments */ + struct reader *rd = gcreq->arg; + nn_log (LC_DISCOVERY, "gc_delete_reader(%p, %x:%x:%x:%x)\n", (void *) gcreq, PGUID (rd->e.guid)); + gcreq_free (gcreq); + + while (!ut_avlIsEmpty (&rd->writers)) + { + struct rd_pwr_match *m = ut_avlRootNonEmpty (&rd_writers_treedef, &rd->writers); + ut_avlDelete (&rd_writers_treedef, &rd->writers, m); + proxy_writer_drop_connection (&m->pwr_guid, rd); + free_rd_pwr_match (m); + } + while (!ut_avlIsEmpty (&rd->local_writers)) + { + struct rd_wr_match *m = ut_avlRootNonEmpty (&rd_local_writers_treedef, &rd->local_writers); + ut_avlDelete (&rd_local_writers_treedef, &rd->local_writers, m); + writer_drop_local_connection (&m->wr_guid, rd); + free_rd_wr_match (m); + } + + if (!is_builtin_entityid (rd->e.guid.entityid, ownvendorid)) + sedp_dispose_unregister_reader (rd); +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + addrset_forall (rd->as, leave_mcast_helper, gv.data_conn_mc); +#endif + if (rd->rhc) + { + (ddsi_plugin.rhc_free_fn) (rd->rhc); + } + if (rd->status_cb) + { + (rd->status_cb) (rd->status_cb_entity, NULL); + } + sertopic_free ((struct sertopic *) rd->topic); + + nn_xqos_fini (rd->xqos); + os_free (rd->xqos); +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + unref_addrset (rd->as); +#endif + + endpoint_common_fini (&rd->e, &rd->c); + os_free (rd); +} + +int delete_reader (const struct nn_guid *guid) +{ + struct reader *rd; + assert (!is_writer_entityid (guid->entityid)); + if ((rd = ephash_lookup_reader_guid (guid)) == NULL) + { + nn_log (LC_DISCOVERY, "delete_reader_guid(guid %x:%x:%x:%x) - unknown guid\n", PGUID (*guid)); + return ERR_UNKNOWN_ENTITY; + } + if (rd->rhc) + { + (ddsi_plugin.rhc_fini_fn) (rd->rhc); + } + nn_log (LC_DISCOVERY, "delete_reader_guid(guid %x:%x:%x:%x) ...\n", PGUID (*guid)); + ephash_remove_reader_guid (rd); + gcreq_reader (rd); + return 0; +} + +uint64_t reader_instance_id (const struct nn_guid *guid) +{ + struct entity_common *e; + e = (struct entity_common*)ephash_lookup_reader_guid(guid); + if (e) { + return e->iid; + } + e = (struct entity_common*)ephash_lookup_proxy_reader_guid(guid); + if (e) { + return e->iid; + } + return 0; +} + + +/* PROXY-PARTICIPANT ------------------------------------------------ */ +static void gc_proxy_participant_lease (struct gcreq *gcreq) +{ + lease_free (gcreq->arg); + gcreq_free (gcreq); +} + +void proxy_participant_reassign_lease (struct proxy_participant *proxypp, struct lease *newlease) +{ + /* Lease renewal is done by the receive thread without locking the + proxy participant (and I'd like to keep it that way), but that + means we must guarantee that the lease pointer remains valid once + loaded. + + By loading/storing the pointer atomically, we ensure we always + read a valid (or once valid) value, by delaying the freeing + through the garbage collector, we ensure whatever lease update + occurs in parallel completes before the memory is released. + + The lease_renew(never) call ensures the lease will never expire + while we are messing with it. */ + os_mutexLock (&proxypp->e.lock); + if (proxypp->owns_lease) + { + const nn_etime_t never = { T_NEVER }; + struct gcreq *gcreq = gcreq_new (gv.gcreq_queue, gc_proxy_participant_lease); + struct lease *oldlease = os_atomic_ldvoidp (&proxypp->lease); + lease_renew (oldlease, never); + gcreq->arg = oldlease; + gcreq_enqueue (gcreq); + proxypp->owns_lease = 0; + } + os_atomic_stvoidp (&proxypp->lease, newlease); + os_mutexUnlock (&proxypp->e.lock); +} + +void new_proxy_participant +( + const struct nn_guid *ppguid, + unsigned bes, + unsigned prismtech_bes, + const struct nn_guid *privileged_pp_guid, + struct addrset *as_default, + struct addrset *as_meta, + const nn_plist_t *plist, + int64_t tlease_dur, + nn_vendorid_t vendor, + unsigned custom_flags, + nn_wctime_t timestamp +) +{ + /* No locking => iff all participants use unique guids, and sedp + runs on a single thread, it can't go wrong. FIXME, maybe? The + same holds for the other functions for creating entities. */ + struct proxy_participant *proxypp; + + assert (ppguid->entityid.u == NN_ENTITYID_PARTICIPANT); + assert (ephash_lookup_proxy_participant_guid (ppguid) == NULL); + assert (privileged_pp_guid == NULL || privileged_pp_guid->entityid.u == NN_ENTITYID_PARTICIPANT); + + prune_deleted_participant_guids (now_mt ()); + + proxypp = os_malloc (sizeof (*proxypp)); + + entity_common_init (&proxypp->e, ppguid, "", EK_PROXY_PARTICIPANT, false); + proxypp->refc = 1; + proxypp->lease_expired = 0; + proxypp->vendor = vendor; + proxypp->bes = bes; + proxypp->prismtech_bes = prismtech_bes; + if (privileged_pp_guid) { + proxypp->privileged_pp_guid = *privileged_pp_guid; + } else { + memset (&proxypp->privileged_pp_guid.prefix, 0, sizeof (proxypp->privileged_pp_guid.prefix)); + proxypp->privileged_pp_guid.entityid.u = NN_ENTITYID_PARTICIPANT; + } + if ((plist->present & PP_PRISMTECH_PARTICIPANT_VERSION_INFO) && + (plist->prismtech_participant_version_info.flags & NN_PRISMTECH_FL_DDSI2_PARTICIPANT_FLAG) && + (plist->prismtech_participant_version_info.flags & NN_PRISMTECH_FL_PARTICIPANT_IS_DDSI2)) + proxypp->is_ddsi2_pp = 1; + else + proxypp->is_ddsi2_pp = 0; + if ((plist->present & PP_PRISMTECH_PARTICIPANT_VERSION_INFO) && + (plist->prismtech_participant_version_info.flags & NN_PRISMTECH_FL_MINIMAL_BES_MODE)) + proxypp->minimal_bes_mode = 1; + else + proxypp->minimal_bes_mode = 0; + + { + struct proxy_participant *privpp; + privpp = ephash_lookup_proxy_participant_guid (&proxypp->privileged_pp_guid); + if (privpp != NULL && privpp->is_ddsi2_pp) + { + os_atomic_stvoidp (&proxypp->lease, os_atomic_ldvoidp (&privpp->lease)); + proxypp->owns_lease = 0; + } + else + { + /* Lease duration is meaningless when the lease never expires, but when proxy participants are created implicitly because of endpoint discovery from a cloud service, we do want the lease to expire eventually when the cloud discovery service disappears and never reappears. The normal data path renews the lease, so if the lease expiry is changed after the DS disappears but data continues to flow (even if it is only a single sample) the proxy participant would immediately go back to a non-expiring lease with no further triggers for deleting it. Instead, we take tlease_dur == NEVER as a special value meaning a lease that doesn't expire now and that has a "reasonable" lease duration. That way the lease renewal in the data path is fine, and we only need to do something special in SEDP handling. */ + nn_etime_t texp = add_duration_to_etime (now_et(), tlease_dur); + int64_t dur = (tlease_dur == T_NEVER) ? config.lease_duration : tlease_dur; + os_atomic_stvoidp (&proxypp->lease, lease_new (texp, dur, &proxypp->e)); + proxypp->owns_lease = 1; + } + } + + proxypp->as_default = as_default; + proxypp->as_meta = as_meta; + proxypp->endpoints = NULL; + proxypp->plist = nn_plist_dup (plist); + ut_avlInit (&proxypp_groups_treedef, &proxypp->groups); + + + if (custom_flags & CF_INC_KERNEL_SEQUENCE_NUMBERS) + proxypp->kernel_sequence_numbers = 1; + else + proxypp->kernel_sequence_numbers = 0; + if (custom_flags & CF_IMPLICITLY_CREATED_PROXYPP) + proxypp->implicitly_created = 1; + else + proxypp->implicitly_created = 0; + + if (custom_flags & CF_PROXYPP_NO_SPDP) + proxypp->proxypp_have_spdp = 0; + else + proxypp->proxypp_have_spdp = 1; + /* Non-PrismTech doesn't implement the PT extensions and therefore won't generate + a CMParticipant; if a PT peer does not implement a CMParticipant writer, then it + presumably also is a handicapped implementation (perhaps simply an old one) */ + if (!vendor_is_prismtech(proxypp->vendor) || + (proxypp->bes != 0 && !(proxypp->prismtech_bes & NN_DISC_BUILTIN_ENDPOINT_CM_PARTICIPANT_WRITER))) + proxypp->proxypp_have_cm = 1; + else + proxypp->proxypp_have_cm = 0; + + /* Proxy participant must be in the hash tables for + new_proxy_{writer,reader} to work */ + ephash_insert_proxy_participant_guid (proxypp); + + /* Add proxy endpoints based on the advertised (& possibly augmented + ...) built-in endpoint set. */ + { +#define PT_TE(ap_, a_, bp_, b_) { 0, NN_##ap_##BUILTIN_ENDPOINT_##a_, NN_ENTITYID_##bp_##_BUILTIN_##b_ } +#define TE(ap_, a_, bp_, b_) { NN_##ap_##BUILTIN_ENDPOINT_##a_, 0, NN_ENTITYID_##bp_##_BUILTIN_##b_ } +#define LTE(a_, bp_, b_) { NN_##BUILTIN_ENDPOINT_##a_, 0, NN_ENTITYID_##bp_##_BUILTIN_##b_ } + static const struct bestab { + unsigned besflag; + unsigned prismtech_besflag; + unsigned entityid; + } bestab[] = { +#if 0 + /* SPDP gets special treatment => no need for proxy + writers/readers */ + TE (DISC_, PARTICIPANT_ANNOUNCER, SPDP, PARTICIPANT_WRITER), +#endif + TE (DISC_, PARTICIPANT_DETECTOR, SPDP, PARTICIPANT_READER), + TE (DISC_, PUBLICATION_ANNOUNCER, SEDP, PUBLICATIONS_WRITER), + TE (DISC_, PUBLICATION_DETECTOR, SEDP, PUBLICATIONS_READER), + TE (DISC_, SUBSCRIPTION_ANNOUNCER, SEDP, SUBSCRIPTIONS_WRITER), + TE (DISC_, SUBSCRIPTION_DETECTOR, SEDP, SUBSCRIPTIONS_READER), + LTE (PARTICIPANT_MESSAGE_DATA_WRITER, P2P, PARTICIPANT_MESSAGE_WRITER), + LTE (PARTICIPANT_MESSAGE_DATA_READER, P2P, PARTICIPANT_MESSAGE_READER), + TE (DISC_, TOPIC_ANNOUNCER, SEDP, TOPIC_WRITER), + TE (DISC_, TOPIC_DETECTOR, SEDP, TOPIC_READER), + PT_TE (DISC_, CM_PARTICIPANT_READER, SEDP, CM_PARTICIPANT_READER), + PT_TE (DISC_, CM_PARTICIPANT_WRITER, SEDP, CM_PARTICIPANT_WRITER), + PT_TE (DISC_, CM_PUBLISHER_READER, SEDP, CM_PUBLISHER_READER), + PT_TE (DISC_, CM_PUBLISHER_WRITER, SEDP, CM_PUBLISHER_WRITER), + PT_TE (DISC_, CM_SUBSCRIBER_READER, SEDP, CM_SUBSCRIBER_READER), + PT_TE (DISC_, CM_SUBSCRIBER_WRITER, SEDP, CM_SUBSCRIBER_WRITER) + }; +#undef PT_TE +#undef TE +#undef LTE + nn_plist_t plist_rd, plist_wr; + int i; + /* Note: no entity name or group GUID supplied, but that shouldn't + matter, as these are internal to DDSI and don't use group + coherency */ + nn_plist_init_empty (&plist_wr); + nn_plist_init_empty (&plist_rd); + nn_xqos_copy (&plist_wr.qos, &gv.builtin_endpoint_xqos_wr); + nn_xqos_copy (&plist_rd.qos, &gv.builtin_endpoint_xqos_rd); + for (i = 0; i < (int) (sizeof (bestab) / sizeof (*bestab)); i++) + { + const struct bestab *te = &bestab[i]; + if ((proxypp->bes & te->besflag) || (proxypp->prismtech_bes & te->prismtech_besflag)) + { + nn_guid_t guid1; + guid1.prefix = proxypp->e.guid.prefix; + guid1.entityid.u = te->entityid; + assert (is_builtin_entityid (guid1.entityid, proxypp->vendor)); + if (is_writer_entityid (guid1.entityid)) + { + new_proxy_writer (ppguid, &guid1, proxypp->as_meta, &plist_wr, gv.builtins_dqueue, gv.xevents, timestamp); + } + else + { +#ifdef DDSI_INCLUDE_SSM + const int ssm = addrset_contains_ssm (proxypp->as_meta); + new_proxy_reader (ppguid, &guid1, proxypp->as_meta, &plist_rd, timestamp, ssm); +#else + new_proxy_reader (ppguid, &guid1, proxypp->as_meta, &plist_rd, timestamp); +#endif + } + } + } + nn_plist_fini (&plist_wr); + nn_plist_fini (&plist_rd); + } + + /* Register lease, but be careful not to accidentally re-register + DDSI2's lease, as we may have become dependent on DDSI2 any time + after ephash_insert_proxy_participant_guid even if + privileged_pp_guid was NULL originally */ + os_mutexLock (&proxypp->e.lock); + + if (proxypp->owns_lease) + lease_register (os_atomic_ldvoidp (&proxypp->lease)); + + if (proxypp->proxypp_have_spdp) + { + propagate_builtin_topic_participant(&(proxypp->e), proxypp->plist, timestamp, true); + if (proxypp->proxypp_have_cm) + { + propagate_builtin_topic_cmparticipant(&(proxypp->e), proxypp->plist, timestamp, true); + } + } + + os_mutexUnlock (&proxypp->e.lock); +} + +int update_proxy_participant_plist_locked (struct proxy_participant *proxypp, const struct nn_plist *datap, enum update_proxy_participant_source source, nn_wctime_t timestamp) +{ + /* Currently, built-in processing is single-threaded, and it is only through this function and the proxy participant deletion (which necessarily happens when no-one else potentially references the proxy participant anymore). So at the moment, the lock is superfluous. */ + nn_plist_t *new_plist; + + new_plist = nn_plist_dup (datap); + nn_plist_mergein_missing (new_plist, proxypp->plist); + nn_plist_fini (proxypp->plist); + os_free (proxypp->plist); + proxypp->plist = new_plist; + + switch (source) + { + case UPD_PROXYPP_SPDP: + propagate_builtin_topic_participant(&(proxypp->e), proxypp->plist, timestamp, true); + if (!proxypp->proxypp_have_spdp && proxypp->proxypp_have_cm) + propagate_builtin_topic_cmparticipant(&(proxypp->e), proxypp->plist, timestamp, true); + proxypp->proxypp_have_spdp = 1; + break; + case UPD_PROXYPP_CM: + if (proxypp->proxypp_have_spdp) + propagate_builtin_topic_cmparticipant(&(proxypp->e), proxypp->plist, timestamp, true); + proxypp->proxypp_have_cm = 1; + break; + } + + return 0; +} + +int update_proxy_participant_plist (struct proxy_participant *proxypp, const struct nn_plist *datap, enum update_proxy_participant_source source, nn_wctime_t timestamp) +{ + nn_plist_t tmp; + + /* FIXME: find a better way of restricting which bits can get updated */ + os_mutexLock (&proxypp->e.lock); + switch (source) + { + case UPD_PROXYPP_SPDP: + update_proxy_participant_plist_locked (proxypp, datap, source, timestamp); + break; + case UPD_PROXYPP_CM: + tmp = *datap; + tmp.present &= + PP_PRISMTECH_NODE_NAME | PP_PRISMTECH_EXEC_NAME | PP_PRISMTECH_PROCESS_ID | + PP_PRISMTECH_WATCHDOG_SCHEDULING | PP_PRISMTECH_LISTENER_SCHEDULING | + PP_PRISMTECH_SERVICE_TYPE | PP_ENTITY_NAME; + tmp.qos.present &= QP_PRISMTECH_ENTITY_FACTORY; + update_proxy_participant_plist_locked (proxypp, &tmp, source, timestamp); + break; + } + os_mutexUnlock (&proxypp->e.lock); + return 0; +} + +static void ref_proxy_participant (struct proxy_participant *proxypp, struct proxy_endpoint_common *c) +{ + os_mutexLock (&proxypp->e.lock); + c->proxypp = proxypp; + proxypp->refc++; + + c->next_ep = proxypp->endpoints; + c->prev_ep = NULL; + if (c->next_ep) + { + c->next_ep->prev_ep = c; + } + proxypp->endpoints = c; + os_mutexUnlock (&proxypp->e.lock); +} + +static void unref_proxy_participant (struct proxy_participant *proxypp, struct proxy_endpoint_common *c) +{ + uint32_t refc; + const nn_wctime_t tnow = now(); + + os_mutexLock (&proxypp->e.lock); + refc = --proxypp->refc; + + if (c != NULL) + { + if (c->next_ep) + c->next_ep->prev_ep = c->prev_ep; + if (c->prev_ep) + c->prev_ep->next_ep = c->next_ep; + else + proxypp->endpoints = c->next_ep; + } + + if (refc == 0) + { + assert (proxypp->endpoints == NULL); + os_mutexUnlock (&proxypp->e.lock); + nn_log (LC_DISCOVERY, "unref_proxy_participant(%x:%x:%x:%x): refc=0, freeing\n", PGUID (proxypp->e.guid)); + + + unref_addrset (proxypp->as_default); + unref_addrset (proxypp->as_meta); + nn_plist_fini (proxypp->plist); + os_free (proxypp->plist); + if (proxypp->owns_lease) + lease_free (os_atomic_ldvoidp (&proxypp->lease)); + entity_common_fini (&proxypp->e); + remove_deleted_participant_guid (&proxypp->e.guid, DPG_LOCAL | DPG_REMOTE); + os_free (proxypp); + } + else if (proxypp->endpoints == NULL && proxypp->implicitly_created) + { + assert (refc == 1); + os_mutexUnlock (&proxypp->e.lock); + nn_log (LC_DISCOVERY, "unref_proxy_participant(%x:%x:%x:%x): refc=%u, no endpoints, implicitly created, deleting\n", PGUID (proxypp->e.guid), (unsigned) refc); + delete_proxy_participant_by_guid(&proxypp->e.guid, tnow, 1); + /* Deletion is still (and has to be) asynchronous. A parallel endpoint creation may or may not + succeed, and if it succeeds it will be deleted along with the proxy participant. So "your + mileage may vary". Also, the proxy participant may be blacklisted for a little ... */ + } + else + { + os_mutexUnlock (&proxypp->e.lock); + nn_log (LC_DISCOVERY, "unref_proxy_participant(%x:%x:%x:%x): refc=%u\n", PGUID (proxypp->e.guid), (unsigned) refc); + } +} + +static void gc_delete_proxy_participant (struct gcreq *gcreq) +{ + struct proxy_participant *proxypp = gcreq->arg; + nn_log (LC_DISCOVERY, "gc_delete_proxy_participant(%p, %x:%x:%x:%x)\n", (void *) gcreq, PGUID (proxypp->e.guid)); + gcreq_free (gcreq); + unref_proxy_participant (proxypp, NULL); +} + +static struct entity_common *entity_common_from_proxy_endpoint_common (const struct proxy_endpoint_common *c) +{ + assert (offsetof (struct proxy_writer, e) == 0); + assert (offsetof (struct proxy_reader, e) == offsetof (struct proxy_writer, e)); + assert (offsetof (struct proxy_reader, c) == offsetof (struct proxy_writer, c)); + assert (c != NULL); + return (struct entity_common *) ((char *) c - offsetof (struct proxy_writer, c)); +} + +static void delete_or_detach_dependent_pp (struct proxy_participant *p, struct proxy_participant *proxypp, nn_wctime_t timestamp, int isimplicit) +{ + os_mutexLock (&p->e.lock); + if (memcmp (&p->privileged_pp_guid, &proxypp->e.guid, sizeof (proxypp->e.guid)) != 0) + { + /* p not dependent on proxypp */ + os_mutexUnlock (&p->e.lock); + return; + } + else if (!(vendor_is_cloud(p->vendor) && p->implicitly_created)) + { + /* DDSI2 minimal participant mode -- but really, anything not discovered via Cloud gets deleted */ + os_mutexUnlock (&p->e.lock); + (void) delete_proxy_participant_by_guid (&p->e.guid, timestamp, isimplicit); + } + else + { + nn_etime_t texp = add_duration_to_etime (now_et(), config.ds_grace_period); + /* Clear dependency (but don't touch entity id, which must be 0x1c1) and set the lease ticking */ + nn_log (LC_DISCOVERY, "%x:%x:%x:%x detach-from-DS %x:%x:%x:%x\n", PGUID(p->e.guid), PGUID(proxypp->e.guid)); + memset (&p->privileged_pp_guid.prefix, 0, sizeof (p->privileged_pp_guid.prefix)); + lease_set_expiry (os_atomic_ldvoidp (&p->lease), texp); + os_mutexUnlock (&p->e.lock); + } +} + +static void delete_ppt (struct proxy_participant * proxypp, nn_wctime_t timestamp, int isimplicit) +{ + struct proxy_endpoint_common * c; + int ret; + + /* if any proxy participants depend on this participant, delete them */ + nn_log (LC_DISCOVERY, "delete_ppt(%x:%x:%x:%x) - deleting dependent proxy participants\n", PGUID (proxypp->e.guid)); + { + struct ephash_enum_proxy_participant est; + struct proxy_participant *p; + ephash_enum_proxy_participant_init (&est); + while ((p = ephash_enum_proxy_participant_next (&est)) != NULL) + delete_or_detach_dependent_pp(p, proxypp, timestamp, isimplicit); + ephash_enum_proxy_participant_fini (&est); + } + + /* delete_proxy_{reader,writer} merely schedules the actual delete + operation, so we can hold the lock -- at least, for now. */ + + os_mutexLock (&proxypp->e.lock); + if (isimplicit) + proxypp->lease_expired = 1; + + nn_log (LC_DISCOVERY, "delete_ppt(%x:%x:%x:%x) - deleting groups\n", PGUID (proxypp->e.guid)); + while (!ut_avlIsEmpty (&proxypp->groups)) + delete_proxy_group_locked (ut_avlRoot (&proxypp_groups_treedef, &proxypp->groups), timestamp, isimplicit); + + nn_log (LC_DISCOVERY, "delete_ppt(%x:%x:%x:%x) - deleting endpoints\n", PGUID (proxypp->e.guid)); + c = proxypp->endpoints; + while (c) + { + struct entity_common *e = entity_common_from_proxy_endpoint_common (c); + if (is_writer_entityid (e->guid.entityid)) + { + ret = delete_proxy_writer (&e->guid, timestamp, isimplicit); + } + else + { + ret = delete_proxy_reader (&e->guid, timestamp, isimplicit); + } + (void) ret; + c = c->next_ep; + } + os_mutexUnlock (&proxypp->e.lock); + + gcreq_proxy_participant (proxypp); +} + +typedef struct proxy_purge_data { + struct proxy_participant *proxypp; + const nn_locator_t *loc; + nn_wctime_t timestamp; +} *proxy_purge_data_t; + +static void purge_helper (const nn_locator_t *n, void * varg) +{ + proxy_purge_data_t data = (proxy_purge_data_t) varg; + if (compare_locators (n, data->loc) == 0) + delete_proxy_participant_by_guid (&data->proxypp->e.guid, data->timestamp, 1); +} + +void purge_proxy_participants (const nn_locator_t *loc, bool delete_from_as_disc) +{ + /* FIXME: check whether addr:port can't be reused for a new connection by the time we get here. */ + /* NOTE: This function exists for the sole purpose of cleaning up after closing a TCP connection in ddsi_tcp_close_conn and the state of the calling thread could be anything at this point. Because of that we do the unspeakable and toggle the thread state conditionally. We can't afford to have it in "asleep", as that causes a race with the garbage collector. */ + struct thread_state1 * const self = lookup_thread_state(); + const int self_is_awake = vtime_awake_p (self->vtime); + struct ephash_enum_proxy_participant est; + struct proxy_purge_data data; + + if (!self_is_awake) + thread_state_awake(self); + + data.loc = loc; + data.timestamp = now(); + ephash_enum_proxy_participant_init (&est); + while ((data.proxypp = ephash_enum_proxy_participant_next (&est)) != NULL) + addrset_forall (data.proxypp->as_meta, purge_helper, &data); + ephash_enum_proxy_participant_fini (&est); + + /* Shouldn't try to keep pinging clients once they're gone */ + if (delete_from_as_disc) + remove_from_addrset (gv.as_disc, loc); + + if (!self_is_awake) + thread_state_asleep(self); +} + +int delete_proxy_participant_by_guid (const struct nn_guid * guid, nn_wctime_t timestamp, int isimplicit) +{ + struct proxy_participant * ppt; + + nn_log (LC_DISCOVERY, "delete_proxy_participant_by_guid(%x:%x:%x:%x) ", PGUID (*guid)); + os_mutexLock (&gv.lock); + ppt = ephash_lookup_proxy_participant_guid (guid); + if (ppt == NULL) + { + os_mutexUnlock (&gv.lock); + nn_log (LC_DISCOVERY, "- unknown\n"); + return ERR_UNKNOWN_ENTITY; + } + nn_log (LC_DISCOVERY, "- deleting\n"); + propagate_builtin_topic_cmparticipant(&(ppt->e), ppt->plist, timestamp, false); + propagate_builtin_topic_participant(&(ppt->e), ppt->plist, timestamp, false); + remember_deleted_participant_guid (&ppt->e.guid); + ephash_remove_proxy_participant_guid (ppt); + os_mutexUnlock (&gv.lock); + delete_ppt (ppt, timestamp, isimplicit); + + return 0; +} + +uint64_t participant_instance_id (const struct nn_guid *guid) +{ + struct entity_common *e; + e = (struct entity_common*)ephash_lookup_participant_guid(guid); + if (e) { + return e->iid; + } + e = (struct entity_common*)ephash_lookup_proxy_participant_guid(guid); + if (e) { + return e->iid; + } + return 0; +} + +/* PROXY-GROUP --------------------------------------------------- */ + +int new_proxy_group (const struct nn_guid *guid, const struct v_gid_s *gid, const char *name, const struct nn_xqos *xqos, nn_wctime_t timestamp) +{ + struct proxy_participant *proxypp; + nn_guid_t ppguid; + ppguid.prefix = guid->prefix; + ppguid.entityid.u = NN_ENTITYID_PARTICIPANT; + if ((proxypp = ephash_lookup_proxy_participant_guid (&ppguid)) == NULL) + { + nn_log (LC_DISCOVERY, "new_proxy_group(%x:%x:%x:%x) - unknown participant\n", PGUID (*guid)); + return 0; + } + else + { + struct proxy_group *pgroup; + ut_avlIPath_t ipath; + int is_sub; + switch (guid->entityid.u & (NN_ENTITYID_SOURCE_MASK | NN_ENTITYID_KIND_MASK)) + { + case NN_ENTITYID_SOURCE_VENDOR | NN_ENTITYID_KIND_PRISMTECH_PUBLISHER: + is_sub = 0; + break; + case NN_ENTITYID_SOURCE_VENDOR | NN_ENTITYID_KIND_PRISMTECH_SUBSCRIBER: + is_sub = 1; + break; + default: + NN_WARNING ("new_proxy_group: unrecognised entityid: %x\n", guid->entityid.u); + return ERR_INVALID_DATA; + } + os_mutexLock (&proxypp->e.lock); + if ((pgroup = ut_avlLookupIPath (&proxypp_groups_treedef, &proxypp->groups, guid, &ipath)) != NULL) + { + /* Complete proxy group definition if it was a partial + definition made by creating a proxy reader or writer, + otherwise ignore this call */ + if (pgroup->name != NULL) + goto out; + } + else + { + /* Always have a guid, may not have a gid */ + nn_log (LC_DISCOVERY, "new_proxy_group(%x:%x:%x:%x): new\n", PGUID (*guid)); + pgroup = os_malloc (sizeof (*pgroup)); + pgroup->guid = *guid; + pgroup->proxypp = proxypp; + pgroup->name = NULL; + pgroup->xqos = NULL; + ut_avlInsertIPath (&proxypp_groups_treedef, &proxypp->groups, pgroup, &ipath); + } + if (name) + { + assert (xqos != NULL); + nn_log (LC_DISCOVERY, "new_proxy_group(%x:%x:%x:%x): setting name (%s) and qos\n", PGUID (*guid), name); + pgroup->name = os_strdup (name); + pgroup->xqos = nn_xqos_dup (xqos); + nn_xqos_mergein_missing (pgroup->xqos, is_sub ? &gv.default_xqos_sub : &gv.default_xqos_pub); + } + out: + os_mutexUnlock (&proxypp->e.lock); + nn_log (LC_DISCOVERY, "\n"); + return 0; + } +} + +static void delete_proxy_group_locked (struct proxy_group *pgroup, nn_wctime_t timestamp, int isimplicit) +{ + struct proxy_participant *proxypp = pgroup->proxypp; + assert ((pgroup->xqos != NULL) == (pgroup->name != NULL)); + nn_log (LC_DISCOVERY, "delete_proxy_group_locked %x:%x:%x:%x\n", PGUID (pgroup->guid)); + ut_avlDelete (&proxypp_groups_treedef, &proxypp->groups, pgroup); + /* Publish corresponding built-in topic only if it is not a place + holder: in that case we haven't announced its presence and + therefore don't need to dispose it, and this saves us from having + to handle null pointers for name and QoS in the built-in topic + generation */ + if (pgroup->name) + { + nn_xqos_fini (pgroup->xqos); + os_free (pgroup->xqos); + os_free (pgroup->name); + } + os_free (pgroup); +} + +void delete_proxy_group (const nn_guid_t *guid, nn_wctime_t timestamp, int isimplicit) +{ + struct proxy_participant *proxypp; + nn_guid_t ppguid; + ppguid.prefix = guid->prefix; + ppguid.entityid.u = NN_ENTITYID_PARTICIPANT; + if ((proxypp = ephash_lookup_proxy_participant_guid (&ppguid)) != NULL) + { + struct proxy_group *pgroup; + os_mutexLock (&proxypp->e.lock); + if ((pgroup = ut_avlLookup (&proxypp_groups_treedef, &proxypp->groups, guid)) != NULL) + delete_proxy_group_locked (pgroup, timestamp, isimplicit); + os_mutexUnlock (&proxypp->e.lock); + } +} + +/* PROXY-ENDPOINT --------------------------------------------------- */ + +static void proxy_endpoint_common_init +( + struct entity_common *e, struct proxy_endpoint_common *c, + enum entity_kind kind, const struct nn_guid *guid, struct proxy_participant *proxypp, + struct addrset *as, const nn_plist_t *plist +) +{ + const char *name; + + if (is_builtin_entityid (guid->entityid, proxypp->vendor)) + assert ((plist->qos.present & (QP_TOPIC_NAME | QP_TYPE_NAME)) == 0); + else + assert ((plist->qos.present & (QP_TOPIC_NAME | QP_TYPE_NAME)) == (QP_TOPIC_NAME | QP_TYPE_NAME)); + + name = (plist->present & PP_ENTITY_NAME) ? plist->entity_name : ""; + entity_common_init (e, guid, name, kind, false); + c->xqos = nn_xqos_dup (&plist->qos); + c->as = ref_addrset (as); + c->topic = NULL; /* set from first matching reader/writer */ + c->vendor = proxypp->vendor; + + if (plist->present & PP_GROUP_GUID) + c->group_guid = plist->group_guid; + else + memset (&c->group_guid, 0, sizeof (c->group_guid)); + + + ref_proxy_participant (proxypp, c); +} + +static void proxy_endpoint_common_fini (struct entity_common *e, struct proxy_endpoint_common *c) +{ + unref_proxy_participant (c->proxypp, c); + + nn_xqos_fini (c->xqos); + os_free (c->xqos); + unref_addrset (c->as); + + entity_common_fini (e); +} + +/* PROXY-WRITER ----------------------------------------------------- */ + +int new_proxy_writer (const struct nn_guid *ppguid, const struct nn_guid *guid, struct addrset *as, const nn_plist_t *plist, struct nn_dqueue *dqueue, struct xeventq *evq, nn_wctime_t timestamp) +{ + struct proxy_participant *proxypp; + struct proxy_writer *pwr; + int isreliable; + nn_mtime_t tnow = now_mt (); + + assert (is_writer_entityid (guid->entityid)); + assert (ephash_lookup_proxy_writer_guid (guid) == NULL); + + if ((proxypp = ephash_lookup_proxy_participant_guid (ppguid)) == NULL) + { + NN_WARNING ("new_proxy_writer(%x:%x:%x:%x): proxy participant unknown\n", PGUID (*guid)); + return ERR_UNKNOWN_ENTITY; + } + + pwr = os_malloc (sizeof (*pwr)); + proxy_endpoint_common_init (&pwr->e, &pwr->c, EK_PROXY_WRITER, guid, proxypp, as, plist); + + ut_avlInit (&pwr_readers_treedef, &pwr->readers); + pwr->n_reliable_readers = 0; + pwr->n_readers_out_of_sync = 0; + pwr->last_seq = 0; + pwr->last_fragnum = ~0u; + pwr->nackfragcount = 0; + pwr->last_fragnum_reset = 0; + os_atomic_st32 (&pwr->next_deliv_seq_lowword, 1); + if (is_builtin_entityid (pwr->e.guid.entityid, pwr->c.vendor)) { + /* The DDSI built-in proxy writers always deliver + asynchronously */ + pwr->deliver_synchronously = 0; + } else if (nn_from_ddsi_duration (pwr->c.xqos->latency_budget.duration) <= config.synchronous_delivery_latency_bound && + pwr->c.xqos->transport_priority.value >= config.synchronous_delivery_priority_threshold) { + /* Regular proxy-writers with a sufficiently low latency_budget + and a sufficiently high transport_priority deliver + synchronously */ + pwr->deliver_synchronously = 1; + } else { + pwr->deliver_synchronously = 0; + } + pwr->have_seen_heartbeat = 0; + pwr->local_matching_inprogress = 1; +#ifdef DDSI_INCLUDE_SSM + pwr->supports_ssm = (addrset_contains_ssm (as) && config.allowMulticast & AMC_SSM) ? 1 : 0; +#endif + isreliable = (pwr->c.xqos->reliability.kind != NN_BEST_EFFORT_RELIABILITY_QOS); + + /* Only assert PP lease on receipt of data if enabled (duh) and the proxy participant is a + "real" participant, rather than the thing we use for endpoints discovered via the DS */ + pwr->assert_pp_lease = + (unsigned) !!config.arrival_of_data_asserts_pp_and_ep_liveliness; + + assert (pwr->c.xqos->present & QP_LIVELINESS); + if (pwr->c.xqos->liveliness.kind != NN_AUTOMATIC_LIVELINESS_QOS) + nn_log (LC_DISCOVERY, " FIXME: only AUTOMATIC liveliness supported"); +#if 0 + pwr->tlease_dur = nn_from_ddsi_duration (pwr->c.xqos->liveliness.lease_duration); + if (pwr->tlease_dur == 0) + { + nn_log (LC_DISCOVERY, " FIXME: treating lease_duration=0 as inf"); + pwr->tlease_dur = T_NEVER; + } + pwr->tlease_end = add_duration_to_wctime (tnow, pwr->tlease_dur); +#endif + + if (isreliable) + { + pwr->defrag = nn_defrag_new (NN_DEFRAG_DROP_LATEST, config.defrag_reliable_maxsamples); + pwr->reorder = nn_reorder_new (NN_REORDER_MODE_NORMAL, config.primary_reorder_maxsamples); + } + else + { + pwr->defrag = nn_defrag_new (NN_DEFRAG_DROP_OLDEST, config.defrag_unreliable_maxsamples); + pwr->reorder = nn_reorder_new (NN_REORDER_MODE_MONOTONICALLY_INCREASING, config.primary_reorder_maxsamples); + } + pwr->dqueue = dqueue; + pwr->evq = evq; + pwr->ddsi2direct_cb = 0; + pwr->ddsi2direct_cbarg = 0; + + local_reader_ary_init (&pwr->rdary); + ephash_insert_proxy_writer_guid (pwr); + match_proxy_writer_with_readers (pwr, tnow); + + os_mutexLock (&pwr->e.lock); + pwr->local_matching_inprogress = 0; + os_mutexUnlock (&pwr->e.lock); + + return 0; +} + +void update_proxy_writer (struct proxy_writer * pwr, struct addrset * as) +{ + struct reader * rd; + struct pwr_rd_match * m; + ut_avlIter_t iter; + + /* Update proxy writer endpoints (from SEDP alive) */ + + os_mutexLock (&pwr->e.lock); + if (! addrset_eq_onesidederr (pwr->c.as, as)) + { +#ifdef DDSI_INCLUDE_SSM + pwr->supports_ssm = (addrset_contains_ssm (as) && config.allowMulticast & AMC_SSM) ? 1 : 0; +#endif + unref_addrset (pwr->c.as); + ref_addrset (as); + pwr->c.as = as; + m = ut_avlIterFirst (&pwr_readers_treedef, &pwr->readers, &iter); + while (m) + { + rd = ephash_lookup_reader_guid (&m->rd_guid); + if (rd) + { + qxev_pwr_entityid (pwr, &rd->e.guid.prefix); + } + m = ut_avlIterNext (&iter); + } + } + os_mutexUnlock (&pwr->e.lock); +} + +void update_proxy_reader (struct proxy_reader * prd, struct addrset * as) +{ + struct prd_wr_match * m; + nn_guid_t wrguid; + + memset (&wrguid, 0, sizeof (wrguid)); + + os_mutexLock (&prd->e.lock); + if (! addrset_eq_onesidederr (prd->c.as, as)) + { + /* Update proxy reader endpoints (from SEDP alive) */ + + unref_addrset (prd->c.as); + ref_addrset (as); + prd->c.as = as; + + /* Rebuild writer endpoints */ + + while ((m = ut_avlLookupSuccEq (&prd_writers_treedef, &prd->writers, &wrguid)) != NULL) + { + struct prd_wr_match *next; + nn_guid_t guid_next; + struct writer * wr; + + wrguid = m->wr_guid; + next = ut_avlFindSucc (&prd_writers_treedef, &prd->writers, m); + if (next) + { + guid_next = next->wr_guid; + } + else + { + memset (&guid_next, 0xff, sizeof (guid_next)); + guid_next.entityid.u = (guid_next.entityid.u & ~(unsigned)0xff) | NN_ENTITYID_KIND_WRITER_NO_KEY; + } + + os_mutexUnlock (&prd->e.lock); + wr = ephash_lookup_writer_guid (&wrguid); + if (wr) + { + os_mutexLock (&wr->e.lock); + rebuild_writer_addrset (wr); + os_mutexUnlock (&wr->e.lock); + qxev_prd_entityid (prd, &wr->e.guid.prefix); + } + wrguid = guid_next; + os_mutexLock (&prd->e.lock); + } + } + os_mutexUnlock (&prd->e.lock); +} + +static void gc_delete_proxy_writer (struct gcreq *gcreq) +{ + struct proxy_writer *pwr = gcreq->arg; + nn_log (LC_DISCOVERY, "gc_delete_proxy_writer(%p, %x:%x:%x:%x)\n", (void *) gcreq, PGUID (pwr->e.guid)); + gcreq_free (gcreq); + + while (!ut_avlIsEmpty (&pwr->readers)) + { + struct pwr_rd_match *m = ut_avlRootNonEmpty (&pwr_readers_treedef, &pwr->readers); + ut_avlDelete (&pwr_readers_treedef, &pwr->readers, m); + reader_drop_connection (&m->rd_guid, pwr); + update_reader_init_acknack_count (&m->rd_guid, m->count); + free_pwr_rd_match (m); + } + local_reader_ary_fini (&pwr->rdary); + proxy_endpoint_common_fini (&pwr->e, &pwr->c); + nn_defrag_free (pwr->defrag); + nn_reorder_free (pwr->reorder); + os_free (pwr); +} + +int delete_proxy_writer (const struct nn_guid *guid, nn_wctime_t timestamp, int isimplicit) +{ + struct proxy_writer *pwr; + nn_log (LC_DISCOVERY, "delete_proxy_writer (%x:%x:%x:%x) ", PGUID (*guid)); + os_mutexLock (&gv.lock); + if ((pwr = ephash_lookup_proxy_writer_guid (guid)) == NULL) + { + os_mutexUnlock (&gv.lock); + nn_log (LC_DISCOVERY, "- unknown\n"); + return ERR_UNKNOWN_ENTITY; + } + /* Set "deleting" flag in particular for Lite, to signal to the receive path it can't + trust rdary[] anymore, which is because removing the proxy writer from the hash + table will prevent the readers from looking up the proxy writer, and consequently + from removing themselves from the proxy writer's rdary[]. */ + local_reader_ary_setinvalid (&pwr->rdary); + nn_log(LC_DISCOVERY, "- deleting\n"); + ephash_remove_proxy_writer_guid (pwr); + os_mutexUnlock (&gv.lock); + gcreq_proxy_writer (pwr); + return 0; +} + +/* PROXY-READER ----------------------------------------------------- */ + +int new_proxy_reader (const struct nn_guid *ppguid, const struct nn_guid *guid, struct addrset *as, const nn_plist_t *plist, nn_wctime_t timestamp +#ifdef DDSI_INCLUDE_SSM + , int favours_ssm +#endif + ) +{ + struct proxy_participant *proxypp; + struct proxy_reader *prd; + nn_mtime_t tnow = now_mt (); + + assert (!is_writer_entityid (guid->entityid)); + assert (ephash_lookup_proxy_reader_guid (guid) == NULL); + + if ((proxypp = ephash_lookup_proxy_participant_guid (ppguid)) == NULL) + { + NN_WARNING ("new_proxy_reader(%x:%x:%x:%x): proxy participant unknown\n", PGUID (*guid)); + return ERR_UNKNOWN_ENTITY; + } + + prd = os_malloc (sizeof (*prd)); + proxy_endpoint_common_init (&prd->e, &prd->c, EK_PROXY_READER, guid, proxypp, as, plist); + + prd->deleting = 0; +#ifdef DDSI_INCLUDE_SSM + prd->favours_ssm = (favours_ssm && config.allowMulticast & AMC_SSM) ? 1 : 0; +#endif + prd->is_fict_trans_reader = 0; + /* Only assert PP lease on receipt of data if enabled (duh) and the proxy participant is a + "real" participant, rather than the thing we use for endpoints discovered via the DS */ + prd->assert_pp_lease = (unsigned) !!config.arrival_of_data_asserts_pp_and_ep_liveliness; + + ut_avlInit (&prd_writers_treedef, &prd->writers); + ephash_insert_proxy_reader_guid (prd); + match_proxy_reader_with_writers (prd, tnow); + return 0; +} + +static void proxy_reader_set_delete_and_ack_all_messages (struct proxy_reader *prd) +{ + nn_guid_t wrguid; + struct writer *wr; + struct prd_wr_match *m; + + memset (&wrguid, 0, sizeof (wrguid)); + os_mutexLock (&prd->e.lock); + prd->deleting = 1; + while ((m = ut_avlLookupSuccEq (&prd_writers_treedef, &prd->writers, &wrguid)) != NULL) + { + /* have to be careful walking the tree -- pretty is different, but + I want to check this before I write a lookup_succ function. */ + struct prd_wr_match *m_a_next; + nn_guid_t wrguid_next; + wrguid = m->wr_guid; + if ((m_a_next = ut_avlFindSucc (&prd_writers_treedef, &prd->writers, m)) != NULL) + wrguid_next = m_a_next->wr_guid; + else + { + memset (&wrguid_next, 0xff, sizeof (wrguid_next)); + wrguid_next.entityid.u = (wrguid_next.entityid.u & ~(unsigned)0xff) | NN_ENTITYID_KIND_WRITER_NO_KEY; + } + + os_mutexUnlock (&prd->e.lock); + if ((wr = ephash_lookup_writer_guid (&wrguid)) != NULL) + { + struct wr_prd_match *m_wr; + os_mutexLock (&wr->e.lock); + if ((m_wr = ut_avlLookup (&wr_readers_treedef, &wr->readers, &prd->e.guid)) != NULL) + { + m_wr->seq = MAX_SEQ_NUMBER; + ut_avlAugmentUpdate (&wr_readers_treedef, m_wr); + remove_acked_messages_and_free (wr); + writer_clear_retransmitting (wr); + } + os_mutexUnlock (&wr->e.lock); + } + + wrguid = wrguid_next; + os_mutexLock (&prd->e.lock); + } + os_mutexUnlock (&prd->e.lock); +} + +static void gc_delete_proxy_reader (struct gcreq *gcreq) +{ + struct proxy_reader *prd = gcreq->arg; + nn_log (LC_DISCOVERY, "gc_delete_proxy_reader(%p, %x:%x:%x:%x)\n", (void *) gcreq, PGUID (prd->e.guid)); + gcreq_free (gcreq); + + while (!ut_avlIsEmpty (&prd->writers)) + { + struct prd_wr_match *m = ut_avlRootNonEmpty (&prd_writers_treedef, &prd->writers); + ut_avlDelete (&prd_writers_treedef, &prd->writers, m); + writer_drop_connection (&m->wr_guid, prd); + free_prd_wr_match (m); + } + + proxy_endpoint_common_fini (&prd->e, &prd->c); + os_free (prd); +} + +int delete_proxy_reader (const struct nn_guid *guid, nn_wctime_t timestamp, int isimplicit) +{ + struct proxy_reader *prd; + nn_log (LC_DISCOVERY, "delete_proxy_reader (%x:%x:%x:%x) ", PGUID (*guid)); + os_mutexLock (&gv.lock); + if ((prd = ephash_lookup_proxy_reader_guid (guid)) == NULL) + { + os_mutexUnlock (&gv.lock); + nn_log (LC_DISCOVERY, "- unknown\n"); + return ERR_UNKNOWN_ENTITY; + } + ephash_remove_proxy_reader_guid (prd); + os_mutexUnlock (&gv.lock); + nn_log (LC_DISCOVERY, "- deleting\n"); + + /* If the proxy reader is reliable, pretend it has just acked all + messages: this allows a throttled writer to once again make + progress, which in turn is necessary for the garbage collector to + do its work. */ + proxy_reader_set_delete_and_ack_all_messages (prd); + + gcreq_proxy_reader (prd); + return 0; +} + +/* CONVENIENCE FUNCTIONS FOR SCHEDULING GC WORK --------------------- */ + +static int gcreq_participant (struct participant *pp) +{ + struct gcreq *gcreq = gcreq_new (gv.gcreq_queue, gc_delete_participant); + gcreq->arg = pp; + gcreq_enqueue (gcreq); + return 0; +} + +static int gcreq_writer (struct writer *wr) +{ + struct gcreq *gcreq = gcreq_new (gv.gcreq_queue, wr->throttling ? gc_delete_writer_throttlewait : gc_delete_writer); + gcreq->arg = wr; + gcreq_enqueue (gcreq); + return 0; +} + +static int gcreq_reader (struct reader *rd) +{ + struct gcreq *gcreq = gcreq_new (gv.gcreq_queue, gc_delete_reader); + gcreq->arg = rd; + gcreq_enqueue (gcreq); + return 0; +} + +static int gcreq_proxy_participant (struct proxy_participant *proxypp) +{ + struct gcreq *gcreq = gcreq_new (gv.gcreq_queue, gc_delete_proxy_participant); + gcreq->arg = proxypp; + gcreq_enqueue (gcreq); + return 0; +} + +static void gc_delete_proxy_writer_dqueue_bubble_cb (struct gcreq *gcreq) +{ + /* delete proxy_writer, phase 3 */ + struct proxy_writer *pwr = gcreq->arg; + nn_log (LC_DISCOVERY, "gc_delete_proxy_writer_dqueue_bubble(%p, %x:%x:%x:%x)\n", (void *) gcreq, PGUID (pwr->e.guid)); + gcreq_requeue (gcreq, gc_delete_proxy_writer); +} + +static void gc_delete_proxy_writer_dqueue (struct gcreq *gcreq) +{ + /* delete proxy_writer, phase 2 */ + struct proxy_writer *pwr = gcreq->arg; + struct nn_dqueue *dqueue = pwr->dqueue; + nn_log (LC_DISCOVERY, "gc_delete_proxy_writer_dqueue(%p, %x:%x:%x:%x)\n", (void *) gcreq, PGUID (pwr->e.guid)); + nn_dqueue_enqueue_callback (dqueue, (void (*) (void *)) gc_delete_proxy_writer_dqueue_bubble_cb, gcreq); +} + +static int gcreq_proxy_writer (struct proxy_writer *pwr) +{ + struct gcreq *gcreq = gcreq_new (gv.gcreq_queue, gc_delete_proxy_writer_dqueue); + gcreq->arg = pwr; + gcreq_enqueue (gcreq); + return 0; +} + +static int gcreq_proxy_reader (struct proxy_reader *prd) +{ + struct gcreq *gcreq = gcreq_new (gv.gcreq_queue, gc_delete_proxy_reader); + gcreq->arg = prd; + gcreq_enqueue (gcreq); + return 0; +} diff --git a/src/core/ddsi/src/q_ephash.c b/src/core/ddsi/src/q_ephash.c new file mode 100644 index 0000000..20386f3 --- /dev/null +++ b/src/core/ddsi/src/q_ephash.c @@ -0,0 +1,365 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include + +#include "os/os.h" +#include "ddsi/sysdeps.h" + +#include "util/ut_hopscotch.h" +#include "ddsi/q_ephash.h" +#include "ddsi/q_config.h" +#include "ddsi/q_globals.h" +#include "ddsi/q_entity.h" +#include "ddsi/q_gc.h" +#include "ddsi/q_rtps.h" /* guid_t */ +#include "ddsi/q_thread.h" /* for assert(thread is awake) */ + +struct ephash { + struct ut_chh *hash; +}; + +static const uint64_t unihashconsts[] = { + UINT64_C (16292676669999574021), + UINT64_C (10242350189706880077), + UINT64_C (12844332200329132887), + UINT64_C (16728792139623414127) +}; + +static uint32_t hash_entity_guid (const struct entity_common *c) +{ + return + (int) (((((uint32_t) c->guid.prefix.u[0] + unihashconsts[0]) * + ((uint32_t) c->guid.prefix.u[1] + unihashconsts[1])) + + (((uint32_t) c->guid.prefix.u[2] + unihashconsts[2]) * + ((uint32_t) c->guid.entityid.u + unihashconsts[3]))) + >> 32); +} + +static uint32_t hash_entity_guid_wrapper (const void *c) +{ + return hash_entity_guid (c); +} + +static int entity_guid_eq (const struct entity_common *a, const struct entity_common *b) +{ + return + a->guid.prefix.u[0] == b->guid.prefix.u[0] && a->guid.prefix.u[1] == b->guid.prefix.u[1] && + a->guid.prefix.u[2] == b->guid.prefix.u[2] && a->guid.entityid.u == b->guid.entityid.u; +} + +static int entity_guid_eq_wrapper (const void *a, const void *b) +{ + return entity_guid_eq (a, b); +} + +static void gc_buckets_cb (struct gcreq *gcreq) +{ + void *bs = gcreq->arg; + gcreq_free (gcreq); + os_free (bs); +} + +static void gc_buckets (void *bs) +{ + struct gcreq *gcreq = gcreq_new (gv.gcreq_queue, gc_buckets_cb); + gcreq->arg = bs; + gcreq_enqueue (gcreq); +} + +struct ephash *ephash_new (void) +{ + struct ephash *ephash; + ephash = os_malloc (sizeof (*ephash)); + ephash->hash = ut_chhNew (32, hash_entity_guid_wrapper, entity_guid_eq_wrapper, gc_buckets); + if (ephash->hash == NULL) { + os_free (ephash); + return NULL; + } else { + return ephash; + } +} + +void ephash_free (struct ephash *ephash) +{ + ut_chhFree (ephash->hash); + ephash->hash = NULL; + os_free (ephash); +} + +static void ephash_guid_insert (struct entity_common *e) +{ + int x; + assert(gv.guid_hash); + assert(gv.guid_hash->hash); + x = ut_chhAdd (gv.guid_hash->hash, e); + (void)x; + assert (x); +} + +static void ephash_guid_remove (struct entity_common *e) +{ + int x; + assert(gv.guid_hash); + assert(gv.guid_hash->hash); + x = ut_chhRemove (gv.guid_hash->hash, e); + (void)x; + assert (x); +} + +static void *ephash_lookup_guid_int (const struct ephash *ephash, const struct nn_guid *guid, enum entity_kind kind) +{ + /* FIXME: could (now) require guid to be first in entity_common; entity_common already is first in entity */ + struct entity_common e; + struct entity_common *res; + e.guid = *guid; + res = ut_chhLookup (gv.guid_hash->hash, &e); + if (res && res->kind == kind) + return res; + else + return NULL; +} + +void *ephash_lookup_guid (const struct nn_guid *guid, enum entity_kind kind) +{ + return ephash_lookup_guid_int (gv.guid_hash, guid, kind); +} + +void ephash_insert_participant_guid (struct participant *pp) +{ + ephash_guid_insert (&pp->e); +} + +void ephash_insert_proxy_participant_guid (struct proxy_participant *proxypp) +{ + ephash_guid_insert (&proxypp->e); +} + +void ephash_insert_writer_guid (struct writer *wr) +{ + ephash_guid_insert (&wr->e); +} + +void ephash_insert_reader_guid (struct reader *rd) +{ + ephash_guid_insert (&rd->e); +} + +void ephash_insert_proxy_writer_guid (struct proxy_writer *pwr) +{ + ephash_guid_insert (&pwr->e); +} + +void ephash_insert_proxy_reader_guid (struct proxy_reader *prd) +{ + ephash_guid_insert (&prd->e); +} + +void ephash_remove_participant_guid (struct participant *pp) +{ + ephash_guid_remove (&pp->e); +} + +void ephash_remove_proxy_participant_guid (struct proxy_participant *proxypp) +{ + ephash_guid_remove (&proxypp->e); +} + +void ephash_remove_writer_guid (struct writer *wr) +{ + ephash_guid_remove (&wr->e); +} + +void ephash_remove_reader_guid (struct reader *rd) +{ + ephash_guid_remove (&rd->e); +} + +void ephash_remove_proxy_writer_guid (struct proxy_writer *pwr) +{ + ephash_guid_remove (&pwr->e); +} + +void ephash_remove_proxy_reader_guid (struct proxy_reader *prd) +{ + ephash_guid_remove (&prd->e); +} + +struct participant *ephash_lookup_participant_guid (const struct nn_guid *guid) +{ + assert (guid->entityid.u == NN_ENTITYID_PARTICIPANT); + assert (offsetof (struct participant, e) == 0); + return ephash_lookup_guid_int (gv.guid_hash, guid, EK_PARTICIPANT); +} + +struct proxy_participant *ephash_lookup_proxy_participant_guid (const struct nn_guid *guid) +{ + assert (guid->entityid.u == NN_ENTITYID_PARTICIPANT); + assert (offsetof (struct proxy_participant, e) == 0); + return ephash_lookup_guid_int (gv.guid_hash, guid, EK_PROXY_PARTICIPANT); +} + +struct writer *ephash_lookup_writer_guid (const struct nn_guid *guid) +{ + assert (is_writer_entityid (guid->entityid)); + assert (offsetof (struct writer, e) == 0); + return ephash_lookup_guid_int (gv.guid_hash, guid, EK_WRITER); +} + +struct reader *ephash_lookup_reader_guid (const struct nn_guid *guid) +{ + assert (is_reader_entityid (guid->entityid)); + assert (offsetof (struct reader, e) == 0); + return ephash_lookup_guid_int (gv.guid_hash, guid, EK_READER); +} + +struct proxy_writer *ephash_lookup_proxy_writer_guid (const struct nn_guid *guid) +{ + assert (is_writer_entityid (guid->entityid)); + assert (offsetof (struct proxy_writer, e) == 0); + return ephash_lookup_guid_int (gv.guid_hash, guid, EK_PROXY_WRITER); +} + +struct proxy_reader *ephash_lookup_proxy_reader_guid (const struct nn_guid *guid) +{ + assert (is_reader_entityid (guid->entityid)); + assert (offsetof (struct proxy_reader, e) == 0); + return ephash_lookup_guid_int (gv.guid_hash, guid, EK_PROXY_READER); +} + +/* Enumeration */ + +static void ephash_enum_init_int (struct ephash_enum *st, struct ephash *ephash, enum entity_kind kind) +{ + st->kind = kind; + st->cur = ut_chhIterFirst (ephash->hash, &st->it); + while (st->cur && st->cur->kind != st->kind) + st->cur = ut_chhIterNext (&st->it); +} + +void ephash_enum_init (struct ephash_enum *st, enum entity_kind kind) +{ + ephash_enum_init_int(st, gv.guid_hash, kind); +} + +void ephash_enum_writer_init (struct ephash_enum_writer *st) +{ + ephash_enum_init (&st->st, EK_WRITER); +} + +void ephash_enum_reader_init (struct ephash_enum_reader *st) +{ + ephash_enum_init (&st->st, EK_READER); +} + +void ephash_enum_proxy_writer_init (struct ephash_enum_proxy_writer *st) +{ + ephash_enum_init (&st->st, EK_PROXY_WRITER); +} + +void ephash_enum_proxy_reader_init (struct ephash_enum_proxy_reader *st) +{ + ephash_enum_init (&st->st, EK_PROXY_READER); +} + +void ephash_enum_participant_init (struct ephash_enum_participant *st) +{ + ephash_enum_init (&st->st, EK_PARTICIPANT); +} + +void ephash_enum_proxy_participant_init (struct ephash_enum_proxy_participant *st) +{ + ephash_enum_init (&st->st, EK_PROXY_PARTICIPANT); +} + +void *ephash_enum_next (struct ephash_enum *st) +{ + void *res = st->cur; + if (st->cur) + { + st->cur = ut_chhIterNext (&st->it); + while (st->cur && (int)st->cur->kind != st->kind) + st->cur = ut_chhIterNext (&st->it); + } + return res; +} + +struct writer *ephash_enum_writer_next (struct ephash_enum_writer *st) +{ + assert (offsetof (struct writer, e) == 0); + return ephash_enum_next (&st->st); +} + +struct reader *ephash_enum_reader_next (struct ephash_enum_reader *st) +{ + assert (offsetof (struct reader, e) == 0); + return ephash_enum_next (&st->st); +} + +struct proxy_writer *ephash_enum_proxy_writer_next (struct ephash_enum_proxy_writer *st) +{ + assert (offsetof (struct proxy_writer, e) == 0); + return ephash_enum_next (&st->st); +} + +struct proxy_reader *ephash_enum_proxy_reader_next (struct ephash_enum_proxy_reader *st) +{ + assert (offsetof (struct proxy_reader, e) == 0); + return ephash_enum_next (&st->st); +} + +struct participant *ephash_enum_participant_next (struct ephash_enum_participant *st) +{ + assert (offsetof (struct participant, e) == 0); + return ephash_enum_next (&st->st); +} + +struct proxy_participant *ephash_enum_proxy_participant_next (struct ephash_enum_proxy_participant *st) +{ + assert (offsetof (struct proxy_participant, e) == 0); + return ephash_enum_next (&st->st); +} + +void ephash_enum_fini (struct ephash_enum *st) +{ + OS_UNUSED_ARG(st); +} + +void ephash_enum_writer_fini (struct ephash_enum_writer *st) +{ + ephash_enum_fini (&st->st); +} + +void ephash_enum_reader_fini (struct ephash_enum_reader *st) +{ + ephash_enum_fini (&st->st); +} + +void ephash_enum_proxy_writer_fini (struct ephash_enum_proxy_writer *st) +{ + ephash_enum_fini (&st->st); +} + +void ephash_enum_proxy_reader_fini (struct ephash_enum_proxy_reader *st) +{ + ephash_enum_fini (&st->st); +} + +void ephash_enum_participant_fini (struct ephash_enum_participant *st) +{ + ephash_enum_fini (&st->st); +} + +void ephash_enum_proxy_participant_fini (struct ephash_enum_proxy_participant *st) +{ + ephash_enum_fini (&st->st); +} diff --git a/src/core/ddsi/src/q_freelist.c b/src/core/ddsi/src/q_freelist.c new file mode 100644 index 0000000..3c55b2e --- /dev/null +++ b/src/core/ddsi/src/q_freelist.c @@ -0,0 +1,248 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include + +#include "os/os.h" +#include "ddsi/sysdeps.h" +#include "ddsi/q_freelist.h" + +#if FREELIST_TYPE == FREELIST_ATOMIC_LIFO + +void nn_freelist_init (_Out_ struct nn_freelist *fl, uint32_t max, off_t linkoff) +{ + os_atomic_lifo_init (&fl->x); + os_atomic_st32(&fl->count, 0); + fl->max = (max == UINT32_MAX) ? max-1 : max; + fl->linkoff = linkoff; +} + +void nn_freelist_fini (_Inout_ _Post_invalid_ struct nn_freelist *fl, _In_ void (*free) (void *elem)) +{ + void *e; + while ((e = os_atomic_lifo_pop (&fl->x, fl->linkoff)) != NULL) + free (e); +} + +_Check_return_ bool nn_freelist_push (_Inout_ struct nn_freelist *fl, _Inout_ _When_ (return != 0, _Post_invalid_) void *elem) +{ + if (os_atomic_inc32_nv (&fl->count) <= fl->max) + { + os_atomic_lifo_push (&fl->x, elem, fl->linkoff); + return true; + } + else + { + os_atomic_dec32 (&fl->count); + return false; + } +} + +_Check_return_ _Ret_maybenull_ void *nn_freelist_pushmany (_Inout_ struct nn_freelist *fl, _Inout_opt_ _When_ (return != 0, _Post_invalid_) void *first, _Inout_opt_ _When_ (return != 0, _Post_invalid_) void *last, uint32_t n) +{ + os_atomic_add32 (&fl->count, n); + os_atomic_lifo_pushmany (&fl->x, first, last, fl->linkoff); + return NULL; +} + +_Check_return_ _Ret_maybenull_ void *nn_freelist_pop (_Inout_ struct nn_freelist *fl) +{ + void *e; + if ((e = os_atomic_lifo_pop (&fl->x, fl->linkoff)) != NULL) + { + os_atomic_dec32(&fl->count); + return e; + } + else + { + return NULL; + } +} + +#elif FREELIST_TYPE == FREELIST_DOUBLE + +static os_threadLocal int freelist_inner_idx = -1; + +void nn_freelist_init (_Out_ struct nn_freelist *fl, uint32_t max, off_t linkoff) +{ + int i; + os_mutexInit (&fl->lock); + for (i = 0; i < NN_FREELIST_NPAR; i++) + { + os_mutexInit (&fl->inner[i].lock); + fl->inner[i].count = 0; + fl->inner[i].m = os_malloc (sizeof (*fl->inner[i].m)); + } + os_atomic_st32 (&fl->cc, 0); + fl->mlist = NULL; + fl->emlist = NULL; + fl->count = 0; + fl->max = (max == UINT32_MAX) ? max-1 : max; + fl->linkoff = linkoff; +} + +static void *get_next (const struct nn_freelist *fl, const void *e) +{ + return *((void **) ((char *)e + fl->linkoff)); +} + +void nn_freelist_fini (_Inout_ _Post_invalid_ struct nn_freelist *fl, _In_ void (*xfree) (void *)) +{ + int i; + uint32_t j; + struct nn_freelistM *m; + os_mutexDestroy (&fl->lock); + for (i = 0; i < NN_FREELIST_NPAR; i++) + { + os_mutexDestroy (&fl->inner[i].lock); + for (j = 0; j < fl->inner[i].count; j++) + xfree (fl->inner[i].m->x[j]); + os_free(fl->inner[i].m); + } +/* The compiler can't make sense of all these linked lists and doesn't + * realize that the next pointers are always initialized here. */ +OS_WARNING_MSVC_OFF(6001); + while ((m = fl->mlist) != NULL) + { + fl->mlist = m->next; + for (j = 0; j < NN_FREELIST_MAGSIZE; j++) + xfree (m->x[j]); + os_free (m); + } + while ((m = fl->emlist) != NULL) + { + fl->emlist = m->next; + os_free (m); + } +OS_WARNING_MSVC_ON(6001); +} + +static os_atomic_uint32_t freelist_inner_idx_off = OS_ATOMIC_UINT32_INIT(0); + +static int get_freelist_inner_idx (void) +{ + if (freelist_inner_idx == -1) + { + static const uint64_t unihashconsts[] = { + UINT64_C (16292676669999574021), + UINT64_C (10242350189706880077), + }; + uintptr_t addr; + uint64_t t = (uint64_t) ((uintptr_t) &addr) + os_atomic_ld32 (&freelist_inner_idx_off); + freelist_inner_idx = (int) (((((uint32_t) t + unihashconsts[0]) * ((uint32_t) (t >> 32) + unihashconsts[1]))) >> (64 - NN_FREELIST_NPAR_LG2)); + } + return freelist_inner_idx; +} + +static int lock_inner (struct nn_freelist *fl) +{ + int k = get_freelist_inner_idx(); + if (os_mutexTryLock (&fl->inner[k].lock) != os_resultSuccess) + { + os_mutexLock (&fl->inner[k].lock); + if (os_atomic_inc32_nv (&fl->cc) == 100) + { + os_atomic_st32(&fl->cc, 0); + os_atomic_inc32(&freelist_inner_idx_off); + freelist_inner_idx = -1; + } + } + return k; +} + +_Check_return_ bool nn_freelist_push (_Inout_ struct nn_freelist *fl, _Inout_ _When_ (return != 0, _Post_invalid_) void *elem) +{ + int k = lock_inner (fl); + if (fl->inner[k].count < NN_FREELIST_MAGSIZE) + { + fl->inner[k].m->x[fl->inner[k].count++] = elem; + os_mutexUnlock (&fl->inner[k].lock); + return true; + } + else + { + struct nn_freelistM *m; + os_mutexLock (&fl->lock); + if (fl->count + NN_FREELIST_MAGSIZE >= fl->max) + { + os_mutexUnlock (&fl->lock); + os_mutexUnlock (&fl->inner[k].lock); + return false; + } + m = fl->inner[k].m; + m->next = fl->mlist; + fl->mlist = m; + fl->count += NN_FREELIST_MAGSIZE; + fl->inner[k].count = 0; + if (fl->emlist == NULL) + fl->inner[k].m = os_malloc (sizeof (*fl->inner[k].m)); + else + { + fl->inner[k].m = fl->emlist; + fl->emlist = fl->emlist->next; + } + os_mutexUnlock (&fl->lock); + fl->inner[k].m->x[fl->inner[k].count++] = elem; + os_mutexUnlock (&fl->inner[k].lock); + return true; + } +} + +_Check_return_ _Ret_maybenull_ void *nn_freelist_pushmany (_Inout_ struct nn_freelist *fl, _Inout_opt_ _When_ (return != 0, _Post_invalid_) void *first, _Inout_opt_ _When_ (return != 0, _Post_invalid_) void *last, uint32_t n) +{ + void *m = first; + while (m) + { + void *mnext = get_next (fl, m); + if (!nn_freelist_push (fl, m)) { + return m; + } + m = mnext; + } + return NULL; +} + +_Check_return_ _Ret_maybenull_ void *nn_freelist_pop (_Inout_ struct nn_freelist *fl) +{ + int k = lock_inner (fl); + if (fl->inner[k].count > 0) + { + void *e = fl->inner[k].m->x[--fl->inner[k].count]; + os_mutexUnlock (&fl->inner[k].lock); + return e; + } + else + { + os_mutexLock (&fl->lock); + if (fl->mlist == NULL) + { + os_mutexUnlock (&fl->lock); + os_mutexUnlock (&fl->inner[k].lock); + return NULL; + } + else + { + void *e; + fl->inner[k].m->next = fl->emlist; + fl->emlist = fl->inner[k].m; + fl->inner[k].m = fl->mlist; + fl->mlist = fl->mlist->next; + fl->count -= NN_FREELIST_MAGSIZE; + os_mutexUnlock (&fl->lock); + fl->inner[k].count = NN_FREELIST_MAGSIZE; + e = fl->inner[k].m->x[--fl->inner[k].count]; + os_mutexUnlock (&fl->inner[k].lock); + return e; + } + } +} + +#endif diff --git a/src/core/ddsi/src/q_gc.c b/src/core/ddsi/src/q_gc.c new file mode 100644 index 0000000..f29bc98 --- /dev/null +++ b/src/core/ddsi/src/q_gc.c @@ -0,0 +1,254 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include + +#include "os/os.h" + +#include "ddsi/q_gc.h" +#include "ddsi/q_log.h" +#include "ddsi/q_config.h" +#include "ddsi/q_time.h" +#include "ddsi/q_thread.h" +#include "ddsi/q_ephash.h" +#include "ddsi/q_unused.h" +#include "ddsi/q_lease.h" +#include "ddsi/q_globals.h" /* for mattr, cattr */ + +#include "ddsi/q_rtps.h" /* for guid_hash */ + +struct gcreq_queue { + struct gcreq *first; + struct gcreq *last; + os_mutex lock; + os_cond cond; + int terminate; + int32_t count; + struct thread_state1 *ts; +}; + +static void threads_vtime_gather_for_wait (unsigned *nivs, struct idx_vtime *ivs) +{ + /* copy vtimes of threads, skipping those that are sleeping */ + unsigned i, j; + for (i = j = 0; i < thread_states.nthreads; i++) + { + vtime_t vtime = thread_states.ts[i].vtime; + if (vtime_awake_p (vtime)) + { + ivs[j].idx = i; + ivs[j].vtime = vtime; + ++j; + } + } + *nivs = j; +} + +static int threads_vtime_check (unsigned *nivs, struct idx_vtime *ivs) +{ + /* check all threads in ts have made progress those that have are + removed from the set */ + unsigned i = 0; + while (i < *nivs) + { + unsigned thridx = ivs[i].idx; + vtime_t vtime = thread_states.ts[thridx].vtime; + assert (vtime_awake_p (ivs[i].vtime)); + if (!vtime_gt (vtime, ivs[i].vtime)) + ++i; + else + { + if (i + 1 < *nivs) + ivs[i] = ivs[*nivs - 1]; + --(*nivs); + } + } + return *nivs == 0; +} + +static uint32_t gcreq_queue_thread (struct gcreq_queue *q) +{ + struct thread_state1 *self = lookup_thread_state (); + nn_mtime_t next_thread_cputime = { 0 }; + struct os_time to = { 0, 100 * T_MILLISECOND }; + struct os_time shortsleep = { 0, 1 * T_MILLISECOND }; + struct gcreq *gcreq = NULL; + int trace_shortsleep = 1; + os_mutexLock (&q->lock); + while (!(q->terminate && q->count == 0)) + { + LOG_THREAD_CPUTIME (next_thread_cputime); + /* If we are waiting for a gcreq to become ready, don't bother + looking at the queue; if we aren't, wait for a request to come + in. We can't really wait until something came in because we're + also checking lease expirations. */ + if (gcreq == NULL) + { + if (q->first == NULL) + os_condTimedWait (&q->cond, &q->lock, &to); + if (q->first) + { + gcreq = q->first; + q->first = q->first->next; + } + } + os_mutexUnlock (&q->lock); + + /* Cleanup dead proxy entities. One can argue this should be an + independent thread, but one can also easily argue that an + expired lease is just another form of a request for + deletion. In any event, letting this thread do this should have + very little impact on its primary purpose and be less of a + burden on the system than having a separate thread or adding it + to the workload of the data handling threads. */ + thread_state_awake (self); + check_and_handle_lease_expiration (self, now_et ()); + thread_state_asleep (self); + + if (gcreq) + { + if (!threads_vtime_check (&gcreq->nvtimes, gcreq->vtimes)) + { + /* Not all threads made enough progress => gcreq is not ready + yet => sleep for a bit and rety. Note that we can't even + terminate while this gcreq is waiting and that there is no + condition on which to wait, so a plain sleep is quite + reasonable. */ + if (trace_shortsleep) + { + TRACE (("gc %p: not yet, shortsleep\n", (void*)gcreq)); + trace_shortsleep = 0; + } + os_nanoSleep (shortsleep); + } + else + { + /* Sufficent progress has been made: may now continue deleting + it; the callback is responsible for requeueing (if complex + multi-phase delete) or freeing the delete request. Reset + the current gcreq as this one obviously is no more. */ + TRACE (("gc %p: deleting\n", (void*)gcreq)); + thread_state_awake (self); + gcreq->cb (gcreq); + thread_state_asleep (self); + gcreq = NULL; + trace_shortsleep = 1; + } + } + + os_mutexLock (&q->lock); + } + os_mutexUnlock (&q->lock); + return 0; +} + +struct gcreq_queue *gcreq_queue_new (void) +{ + struct gcreq_queue *q = os_malloc (sizeof (*q)); + + q->first = q->last = NULL; + q->terminate = 0; + q->count = 0; + os_mutexInit (&q->lock); + os_condInit (&q->cond, &q->lock); + q->ts = create_thread ("gc", (uint32_t (*) (void *)) gcreq_queue_thread, q); + assert (q->ts); + return q; +} + +void gcreq_queue_free (struct gcreq_queue *q) +{ + struct gcreq *gcreq; + + /* Create a no-op not dependent on any thread */ + gcreq = gcreq_new (q, gcreq_free); + gcreq->nvtimes = 0; + + os_mutexLock (&q->lock); + q->terminate = 1; + /* Wait until there is only request in existence, the one we just + allocated. Then we know the gc system is quiet. */ + while (q->count != 1) + os_condWait (&q->cond, &q->lock); + os_mutexUnlock (&q->lock); + + /* Force the gc thread to wake up by enqueueing our no-op. The + callback, gcreq_free, will be called immediately, which causes + q->count to 0 before the loop condition is evaluated again, at + which point the thread terminates. */ + gcreq_enqueue (gcreq); + + join_thread (q->ts); + assert (q->first == NULL); + os_condDestroy (&q->cond); + os_mutexDestroy (&q->lock); + os_free (q); +} + +struct gcreq *gcreq_new (struct gcreq_queue *q, gcreq_cb_t cb) +{ + struct gcreq *gcreq; + gcreq = os_malloc (offsetof (struct gcreq, vtimes) + thread_states.nthreads * sizeof (*gcreq->vtimes)); + gcreq->cb = cb; + gcreq->queue = q; + threads_vtime_gather_for_wait (&gcreq->nvtimes, gcreq->vtimes); + os_mutexLock (&q->lock); + q->count++; + os_mutexUnlock (&q->lock); + return gcreq; +} + +void gcreq_free (struct gcreq *gcreq) +{ + struct gcreq_queue *gcreq_queue = gcreq->queue; + os_mutexLock (&gcreq_queue->lock); + --gcreq_queue->count; + if (gcreq_queue->terminate && gcreq_queue->count <= 1) + os_condBroadcast (&gcreq_queue->cond); + os_mutexUnlock (&gcreq_queue->lock); + os_free (gcreq); +} + +static int gcreq_enqueue_common (struct gcreq *gcreq) +{ + struct gcreq_queue *gcreq_queue = gcreq->queue; + int isfirst; + os_mutexLock (&gcreq_queue->lock); + gcreq->next = NULL; + if (gcreq_queue->first) + { + gcreq_queue->last->next = gcreq; + isfirst = 0; + } + else + { + gcreq_queue->first = gcreq; + isfirst = 1; + } + gcreq_queue->last = gcreq; + if (isfirst) + os_condBroadcast (&gcreq_queue->cond); + os_mutexUnlock (&gcreq_queue->lock); + return isfirst; +} + +void gcreq_enqueue (struct gcreq *gcreq) +{ + gcreq_enqueue_common (gcreq); +} + +int gcreq_requeue (struct gcreq *gcreq, gcreq_cb_t cb) +{ + gcreq->cb = cb; + return gcreq_enqueue_common (gcreq); +} diff --git a/src/core/ddsi/src/q_init.c b/src/core/ddsi/src/q_init.c new file mode 100644 index 0000000..c87131a --- /dev/null +++ b/src/core/ddsi/src/q_init.c @@ -0,0 +1,1465 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include + +#include "os/os.h" + +#include "util/ut_avl.h" +#include "util/ut_thread_pool.h" + +#include "ddsi/q_md5.h" +#include "ddsi/q_protocol.h" +#include "ddsi/q_rtps.h" +#include "ddsi/q_misc.h" +#include "ddsi/q_config.h" +#include "ddsi/q_log.h" +#include "ddsi/q_plist.h" +#include "ddsi/q_unused.h" +#include "ddsi/q_bswap.h" +#include "ddsi/q_lat_estim.h" +#include "ddsi/q_bitset.h" +#include "ddsi/q_xevent.h" +#include "ddsi/q_align.h" +#include "ddsi/q_addrset.h" +#include "ddsi/q_ddsi_discovery.h" +#include "ddsi/q_radmin.h" +#include "ddsi/q_error.h" +#include "ddsi/q_thread.h" +#include "ddsi/q_ephash.h" +#include "ddsi/q_lease.h" +#include "ddsi/q_gc.h" +#include "ddsi/q_whc.h" +#include "ddsi/q_entity.h" +#include "ddsi/q_nwif.h" +#include "ddsi/q_globals.h" +#include "ddsi/q_xmsg.h" +#include "ddsi/q_receive.h" +#include "ddsi/q_pcap.h" +#include "ddsi/q_feature_check.h" +#include "ddsi/q_debmon.h" + +#include "ddsi/sysdeps.h" + +#include "ddsi/ddsi_ser.h" +#include "ddsi/ddsi_tran.h" +#include "ddsi/ddsi_udp.h" +#include "ddsi/ddsi_tcp.h" + +#include "dds__tkmap.h" + +static void add_peer_addresses (struct addrset *as, const struct config_peer_listelem *list) +{ + while (list) + { + add_addresses_to_addrset (as, list->peer, -1, "add_peer_addresses", 0); + list = list->next; + } +} + +static int make_uc_sockets (uint32_t * pdisc, uint32_t * pdata, int ppid) +{ + if (ppid >= 0) + { + /* FIXME: verify port numbers are in range instead of truncating them like this */ + int base = config.port_base + (config.port_dg * config.domainId) + (ppid * config.port_pg); + *pdisc = (uint32_t) (base + config.port_d1); + *pdata = (uint32_t) (base + config.port_d3); + } + else if (ppid == PARTICIPANT_INDEX_NONE) + { + *pdata = 0; + *pdisc = 0; + } + else + { + NN_FATAL ("make_uc_sockets: invalid participant index %d\n", ppid); + return -1; + } + + gv.disc_conn_uc = ddsi_factory_create_conn (gv.m_factory, *pdisc, NULL); + if (gv.disc_conn_uc) + { + /* Check not configured to use same unicast port for data and discovery */ + + if (*pdata != 0 && (*pdata != *pdisc)) + { + gv.data_conn_uc = ddsi_factory_create_conn (gv.m_factory, *pdata, NULL); + } + else + { + gv.data_conn_uc = gv.disc_conn_uc; + } + if (gv.data_conn_uc == NULL) + { + ddsi_conn_free (gv.disc_conn_uc); + gv.disc_conn_uc = NULL; + } + else + { + /* Set unicast locators */ + + ddsi_conn_locator (gv.disc_conn_uc, &gv.loc_meta_uc); + ddsi_conn_locator (gv.data_conn_uc, &gv.loc_default_uc); + } + } + + return gv.data_conn_uc ? 0 : -1; +} + +static void make_builtin_endpoint_xqos (nn_xqos_t *q, const nn_xqos_t *template) +{ + nn_xqos_copy (q, template); + q->reliability.kind = NN_RELIABLE_RELIABILITY_QOS; + q->reliability.max_blocking_time = nn_to_ddsi_duration (100 * T_MILLISECOND); + q->durability.kind = NN_TRANSIENT_LOCAL_DURABILITY_QOS; +} + +static int set_recvips (void) +{ + gv.recvips = NULL; + + if (config.networkRecvAddressStrings) + { + if (os_strcasecmp (config.networkRecvAddressStrings[0], "all") == 0) + { +#if OS_SOCKET_HAS_IPV6 + if (gv.ipv6_link_local) + { + NN_WARNING ("DDSI2EService/General/MulticastRecvNetworkInterfaceAddresses: using 'preferred' instead of 'all' because of IPv6 link-local address\n"); + gv.recvips_mode = RECVIPS_MODE_PREFERRED; + } + else +#endif + { + gv.recvips_mode = RECVIPS_MODE_ALL; + } + } + else if (os_strcasecmp (config.networkRecvAddressStrings[0], "any") == 0) + { +#if OS_SOCKET_HAS_IPV6 + if (gv.ipv6_link_local) + { + NN_ERROR ("DDSI2EService/General/MulticastRecvNetworkInterfaceAddresses: 'any' is unsupported in combination with an IPv6 link-local address\n"); + return -1; + } +#endif + gv.recvips_mode = RECVIPS_MODE_ANY; + } + else if (os_strcasecmp (config.networkRecvAddressStrings[0], "preferred") == 0) + { + gv.recvips_mode = RECVIPS_MODE_PREFERRED; + } + else if (os_strcasecmp (config.networkRecvAddressStrings[0], "none") == 0) + { + gv.recvips_mode = RECVIPS_MODE_NONE; + } +#if OS_SOCKET_HAS_IPV6 + else if (gv.ipv6_link_local) + { + /* If the configuration explicitly includes the selected + interface, treat it as "preferred", else as "none"; warn if + interfaces other than the selected one are included. */ + int i, have_selected = 0, have_others = 0; + for (i = 0; config.networkRecvAddressStrings[i] != NULL; i++) + { + os_sockaddr_storage parsedaddr; + if (!os_sockaddrStringToAddress (config.networkRecvAddressStrings[i], (os_sockaddr *) &parsedaddr, !config.useIpv6)) + { + NN_ERROR ("%s: not a valid IP address\n", config.networkRecvAddressStrings[i]); + return -1; + } + if (os_sockaddrIPAddressEqual ((os_sockaddr *) &gv.interfaces[gv.selected_interface].addr, (os_sockaddr *) &parsedaddr)) + have_selected = 1; + else + have_others = 1; + } + gv.recvips_mode = have_selected ? RECVIPS_MODE_PREFERRED : RECVIPS_MODE_NONE; + if (have_others) + { + NN_WARNING ("DDSI2EService/General/MulticastRecvNetworkInterfaceAddresses: using 'preferred' because of IPv6 local address\n"); + } + } +#endif + else + { + struct ospl_in_addr_node **recvnode = &gv.recvips; + int i, j; + gv.recvips_mode = RECVIPS_MODE_SOME; + for (i = 0; config.networkRecvAddressStrings[i] != NULL; i++) + { + os_sockaddr_storage parsedaddr; + if (!os_sockaddrStringToAddress (config.networkRecvAddressStrings[i], (os_sockaddr *) &parsedaddr, !config.useIpv6)) + { + NN_ERROR ("%s: not a valid IP address\n", config.networkRecvAddressStrings[i]); + return -1; + } + for (j = 0; j < gv.n_interfaces; j++) + { + if (os_sockaddrIPAddressEqual ((os_sockaddr *) &gv.interfaces[j].addr, (os_sockaddr *) &parsedaddr)) + break; + } + if (j == gv.n_interfaces) + { + NN_ERROR ("No interface bound to requested address '%s'\n", + config.networkRecvAddressStrings[i]); + return -1; + } + *recvnode = os_malloc (sizeof (struct ospl_in_addr_node)); + (*recvnode)->addr = parsedaddr; + recvnode = &(*recvnode)->next; + *recvnode = NULL; + } + } + } + return 0; +} + +/* Apart from returning errors with the 'string' content is not valid, this + * function can also return errors when a mismatch in multicast/unicast is + * detected. + * return -1 : ddsi is unicast, but 'mc' indicates it expects multicast + * return 0 : ddsi is multicast, but 'mc' indicates it expects unicast + * The return 0 means that the possible changes in 'loc' can be ignored. */ +static int string_to_default_locator (nn_locator_t *loc, const char *string, uint32_t port, int mc, const char *tag) +{ + os_sockaddr_storage addr; + if (strspn (string, " \t") == strlen (string)) + { + /* string consisting of just spaces and/or tabs (that includes the empty string) is ignored */ + return 0; + } + else if (!os_sockaddrStringToAddress (string, (os_sockaddr *) &addr, !config.useIpv6)) + { + NN_ERROR ("%s: not a valid IP address (%s)\n", string, tag); + return -1; + } + else if (!config.useIpv6 && addr.ss_family != AF_INET) + { + NN_ERROR ("%s: not a valid IPv4 address (%s)\n", string, tag); + return -1; + } +#if OS_SOCKET_HAS_IPV6 + else if (config.useIpv6 && addr.ss_family != AF_INET6) + { + NN_ERROR ("%s: not a valid IPv6 address (%s)\n", string, tag); + return -1; + } +#endif + else + { + nn_address_to_loc (loc, &addr, config.useIpv6 ? NN_LOCATOR_KIND_UDPv6 : NN_LOCATOR_KIND_UDPv4); + if (port != 0 && !is_unspec_locator(loc)) + loc->port = port; + assert (mc == -1 || mc == 0 || mc == 1); + if (mc >= 0) + { + const char *unspecstr = config.useIpv6 ? "the IPv6 unspecified address (::0)" : "IPv4 ANY (0.0.0.0)"; + const char *rel = mc ? "must" : "may not"; + const int ismc = is_unspec_locator (loc) || is_mcaddr (loc); + if (mc != ismc) + { + NN_ERROR ("%s: %s %s be %s or a multicast address\n", string, tag, rel, unspecstr); + return -1; + } + } + + return 1; + } +} + +static int set_spdp_address (void) +{ + const uint32_t port = (uint32_t) (config.port_base + config.port_dg * config.domainId + config.port_d0); + int rc = 0; + if (strcmp (config.spdpMulticastAddressString, "239.255.0.1") != 0) + { + if ((rc = string_to_default_locator (&gv.loc_spdp_mc, config.spdpMulticastAddressString, port, 1, "SPDP address")) < 0) + return rc; + } + if (rc == 0) + { + /* There isn't a standard IPv6 multicast group for DDSI. For + some reason, node-local multicast addresses seem to be + unsupported (ff01::... would be a node-local one I think), so + instead do link-local. I suppose we could use the hop limit + to make it node-local. If other hosts reach us in some way, + we'll of course respond. */ + const char *def = config.useIpv6 ? "ff02::ffff:239.255.0.1" : "239.255.0.1"; + rc = string_to_default_locator (&gv.loc_spdp_mc, def, port, 1, "SPDP address"); + assert (rc > 0); + } +#ifdef DDSI_INCLUDE_SSM + if (is_ssm_mcaddr (&gv.loc_spdp_mc)) + { + NN_ERROR ("%s: SPDP address may not be an SSM address\n", config.spdpMulticastAddressString); + return -1; + } +#endif + if (!(config.allowMulticast & AMC_SPDP) || config.suppress_spdp_multicast) + { + /* Explicitly disabling SPDP multicasting is always possible */ + set_unspec_locator (&gv.loc_spdp_mc); + } + return 0; +} + +static int set_default_mc_address (void) +{ + const uint32_t port = (uint32_t) (config.port_base + config.port_dg * config.domainId + config.port_d2); + int rc; + if (!config.defaultMulticastAddressString) + gv.loc_default_mc = gv.loc_spdp_mc; + else if ((rc = string_to_default_locator (&gv.loc_default_mc, config.defaultMulticastAddressString, port, 1, "default multicast address")) < 0) + return rc; + else if (rc == 0) + gv.loc_default_mc = gv.loc_spdp_mc; + if (!(config.allowMulticast & ~AMC_SPDP)) + { + /* no multicasting beyond SPDP */ + set_unspec_locator (&gv.loc_default_mc); + } + gv.loc_meta_mc = gv.loc_default_mc; + return 0; +} + +static int set_ext_address_and_mask (void) +{ + nn_locator_t loc; + int rc; + + if (!config.externalAddressString) + gv.extip = gv.ownip; + else if ((rc = string_to_default_locator (&loc, config.externalAddressString, 0, 0, "external address")) < 0) + return rc; + else if (rc == 0) { + NN_WARNING ("Ignoring ExternalNetworkAddress %s\n", config.externalAddressString); + gv.extip = gv.ownip; + } else { + nn_loc_to_address (&gv.extip, &loc); + } + + if (!config.externalMaskString || strcmp (config.externalMaskString, "0.0.0.0") == 0) + gv.extmask.s_addr = 0; + else if (config.useIpv6) + { + NN_ERROR ("external network masks only supported in IPv4 mode\n"); + return -1; + } + else + { + os_sockaddr_storage addr; + if ((rc = string_to_default_locator (&loc, config.externalMaskString, 0, -1, "external mask")) < 0) + return rc; + nn_loc_to_address(&addr, &loc); + assert (addr.ss_family == AF_INET); + gv.extmask = ((const os_sockaddr_in *) &addr)->sin_addr; + } + return 0; +} + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS +static int known_channel_p (const char *name) +{ + const struct config_channel_listelem *c; + for (c = config.channels; c; c = c->next) + if (strcmp (name, c->name) == 0) + return 1; + return 0; +} +#endif + +static int check_thread_properties (void) +{ +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + static const char *fixed[] = { "recv", "tev", "gc", "lease", "dq.builtins", "debmon", NULL }; + static const char *chanprefix[] = { "xmit.", "tev.","dq.",NULL }; +#else + static const char *fixed[] = { "recv", "tev", "gc", "lease", "dq.builtins", "xmit.user", "dq.user", "debmon", NULL }; +#endif + const struct config_thread_properties_listelem *e; + int ok = 1, i; + for (e = config.thread_properties; e; e = e->next) + { + for (i = 0; fixed[i]; i++) + if (strcmp (fixed[i], e->name) == 0) + break; + if (fixed[i] == NULL) + { +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + /* Some threads are named after the channel, with names of the form PREFIX.CHAN */ + + for (i = 0; chanprefix[i]; i++) + { + size_t n = strlen (chanprefix[i]); + if (strncmp (chanprefix[i], e->name, n) == 0 && known_channel_p (e->name + n)) + break; + } + if (chanprefix[i] == NULL) + { + NN_ERROR ("config: DDSI2Service/Threads/Thread[@name=\"%s\"]: unknown thread\n", e->name); + ok = 0; + } +#else + NN_ERROR ("config: DDSI2Service/Threads/Thread[@name=\"%s\"]: unknown thread\n", e->name); + ok = 0; +#endif /* DDSI_INCLUDE_NETWORK_CHANNELS */ + } + } + return ok; +} + +int rtps_config_open (void) +{ + int status; + + if (config.tracingOutputFileName == NULL || *config.tracingOutputFileName == 0 || config.enabled_logcats == 0) + { + config.enabled_logcats = 0; + config.tracingOutputFile = NULL; + status = 1; + } + else if (os_strcasecmp (config.tracingOutputFileName, "stdout") == 0) + { + config.tracingOutputFile = stdout; + status = 1; + } + else if (os_strcasecmp (config.tracingOutputFileName, "stderr") == 0) + { + config.tracingOutputFile = stderr; + status = 1; + } + else if ((config.tracingOutputFile = fopen (config.tracingOutputFileName, config.tracingAppendToFile ? "a" : "w")) == NULL) + { + NN_ERROR ("%s: cannot open for writing\n", config.tracingOutputFileName); + status = 0; + } + else + { + status = 1; + } + + return status; +} + +int rtps_config_prep (struct cfgst *cfgst) +{ +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + unsigned num_channels = 0; + unsigned num_channel_threads = 0; +#endif + + /* if the discovery domain id was explicitly set, override the default here */ + if (!config.discoveryDomainId.isdefault) + { + config.domainId = config.discoveryDomainId.value; + } + + /* retry_on_reject_duration default is dependent on late_ack_mode and responsiveness timeout, so fix up */ + + + if (config.whc_init_highwater_mark.isdefault) + config.whc_init_highwater_mark.value = config.whc_lowwater_mark; + if (config.whc_highwater_mark < config.whc_lowwater_mark || + config.whc_init_highwater_mark.value < config.whc_lowwater_mark || + config.whc_init_highwater_mark.value > config.whc_highwater_mark) + { + NN_ERROR ("Invalid watermark settings\n"); + goto err_config_late_error; + } + + if (config.besmode == BESMODE_MINIMAL && config.many_sockets_mode) + { + /* These two are incompatible because minimal bes mode can result + in implicitly creating proxy participants inheriting the + address set from the ddsi2 participant (which is then typically + inherited by readers/writers), but in many sockets mode each + participant has its own socket, and therefore unique address + set */ + NN_ERROR ("Minimal built-in endpoint set mode and ManySocketsMode are incompatible\n"); + goto err_config_late_error; + } + + /* Dependencies between default values is not handled + automatically by the config processing (yet) */ + if (config.many_sockets_mode) + { + if (config.max_participants == 0) + config.max_participants = 100; + } + if (NN_STRICT_P) + { + /* Should not be sending invalid messages when strict */ + config.respond_to_rti_init_zero_ack_with_invalid_heartbeat = 0; + config.acknack_numbits_emptyset = 1; + } + if (config.max_queued_rexmit_bytes == 0) + { +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING + if (config.auxiliary_bandwidth_limit == 0) + config.max_queued_rexmit_bytes = 2147483647u; + else + { + double max = (double) config.auxiliary_bandwidth_limit * ((double) config.nack_delay / 1e9); + if (max < 0) + NN_FATAL ("AuxiliaryBandwidthLimit * NackDelay = %g bytes is insane\n", max); + if (max > 2147483647.0) + config.max_queued_rexmit_bytes = 2147483647u; + else + config.max_queued_rexmit_bytes = (unsigned) max; + } +#else + config.max_queued_rexmit_bytes = 2147483647u; +#endif /* DDSI_INCLUDE_BANDWIDTH_LIMITING */ + } + + /* Verify thread properties refer to defined threads */ + if (!check_thread_properties ()) + { + NN_ERROR ("Could not initialise configuration\n"); + goto err_config_late_error; + } + +#if ! OS_SOCKET_HAS_IPV6 + /* If the platform doesn't support IPv6, guarantee useIpv6 is + false. There are two ways of going about it, one is to do it + silently, the other to let the user fix his config. Clearly, we + have chosen the latter. */ + if (config.useIpv6) + { + NN_ERROR ("IPv6 addressing requested but not supported on this platform\n"); + goto err_config_late_error; + } +#endif + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + { + /* Determine number of configured channels to be able to + determine the correct number of threads. Also fix fields if + at default, and check for some known IPv4/IPv6 + "compatibility" issues */ + struct config_channel_listelem *chptr = config.channels; + int error = 0; + + while (chptr) + { + size_t slen = strlen (chptr->name) + 5; + char *thread_name = os_malloc (slen); + (void) snprintf (thread_name, slen, "tev.%s", chptr->name); + + num_channels++; + num_channel_threads += 2; /* xmit and dqueue */ + + if (config.useIpv6 && chptr->diffserv_field != 0) + { + NN_ERROR ("channel %s specifies IPv4 DiffServ settings which is incompatible with IPv6 use\n", + chptr->name); + error = 1; + } + + if ( +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING + chptr->auxiliary_bandwidth_limit > 0 || +#endif + lookup_thread_properties (thread_name)) + num_channel_threads++; + + os_free (thread_name); + chptr = chptr->next; + } + if (error) + goto err_config_late_error; + } +#endif /* DDSI_INCLUDE_NETWORK_CHANNELS */ + + /* Open tracing file after all possible config errors have been + printed */ + if (! rtps_config_open ()) + { + NN_ERROR ("Could not initialise configuration\n"); + goto err_config_late_error; + } + + /* Thread admin: need max threads, which is currently (2 or 3) for each + configured channel plus 7: main, recv, dqueue.builtin, + lease, gc, debmon; once thread state admin has been inited, upgrade the + main thread one participating in the thread tracking stuff as + if it had been created using create_thread(). */ + + { + /* For Lite - Temporary + Thread states for each application thread is managed using thread_states structure + */ +#define USER_MAX_THREADS 50 + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + const unsigned max_threads = 7 + USER_MAX_THREADS + num_channel_threads + config.ddsi2direct_max_threads; +#else + const unsigned max_threads = 9 + USER_MAX_THREADS + config.ddsi2direct_max_threads; +#endif + thread_states_init (max_threads); + } + + /* Now the per-thread-log-buffers are set up, so print the configuration */ + config_print_cfgst (cfgst); + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + /* Convert address sets in partition mappings from string to address sets */ + { + const int port = config.port_base + config.port_dg * config.domainId + config.port_d2; + struct config_networkpartition_listelem *np; + for (np = config.networkPartitions; np; np = np->next) + { + static const char msgtag_fixed[] = ": partition address"; + size_t slen = strlen (np->name) + sizeof (msgtag_fixed); + char * msgtag = os_malloc (slen); + int rc; + (void) snprintf (msgtag, slen, "%s%s", np->name, msgtag_fixed); + np->as = new_addrset (); + rc = add_addresses_to_addrset (np->as, np->address_string, port, msgtag, 1); + os_free (msgtag); + if (rc < 0) + goto err_config_late_error; + } + } +#endif + + return 0; + +err_config_late_error: + return -1; +} + +struct joinleave_spdp_defmcip_helper_arg { + int errcount; + int dojoin; +}; + +static void joinleave_spdp_defmcip_helper (const nn_locator_t *loc, void *varg) +{ + struct joinleave_spdp_defmcip_helper_arg *arg = varg; + if (!is_mcaddr (loc)) + return; +#ifdef DDSI_INCLUDE_SSM + /* Can't join SSM until we actually have a source */ + if (is_ssm_mcaddr (loc)) + return; +#endif + if (arg->dojoin) { + if (ddsi_conn_join_mc (gv.disc_conn_mc, NULL, loc) < 0 || ddsi_conn_join_mc (gv.data_conn_mc, NULL, loc) < 0) + arg->errcount++; + } else { + if (ddsi_conn_leave_mc (gv.disc_conn_mc, NULL, loc) < 0 || ddsi_conn_leave_mc (gv.data_conn_mc, NULL, loc) < 0) + arg->errcount++; + } +} + +static int joinleave_spdp_defmcip (int dojoin) +{ + /* Addrset provides an easy way to filter out duplicates */ + struct joinleave_spdp_defmcip_helper_arg arg; + struct addrset *as = new_addrset (); + arg.errcount = 0; + arg.dojoin = dojoin; + if (config.allowMulticast & AMC_SPDP) + add_to_addrset (as, &gv.loc_spdp_mc); + if (config.allowMulticast & ~AMC_SPDP) + add_to_addrset (as, &gv.loc_default_mc); + addrset_forall (as, joinleave_spdp_defmcip_helper, &arg); + unref_addrset (as); + if (arg.errcount) + { + NN_ERROR ("rtps_init: failed to join multicast groups for domain %d participant %d\n", config.domainId, config.participantIndex); + return -1; + } + return 0; +} + +int rtps_init (void) +{ + uint32_t port_disc_uc = 0; + uint32_t port_data_uc = 0; + + /* Initialize implementation (Lite or OSPL) */ + + ddsi_plugin_init (); + + gv.tstart = now (); /* wall clock time, used in logs */ + + gv.disc_conn_uc = NULL; + gv.data_conn_uc = NULL; + gv.disc_conn_mc = NULL; + gv.data_conn_mc = NULL; + gv.tev_conn = NULL; + gv.listener = NULL; + gv.thread_pool = NULL; + gv.debmon = NULL; + + /* Print start time for referencing relative times in the remainder + of the nn_log. */ + { + int sec = (int) (gv.tstart.v / 1000000000); + int usec = (int) (gv.tstart.v % 1000000000) / 1000; + os_time tv; + char str[OS_CTIME_R_BUFSIZE]; + tv.tv_sec = sec; + tv.tv_nsec = usec * 1000; + os_ctime_r (&tv, str, sizeof(str)); + nn_log (LC_INFO | LC_CONFIG, "started at %d.06%d -- %s\n", sec, usec, str); + } + + /* Initialize thread pool */ + + if (config.tp_enable) + { + gv.thread_pool = ut_thread_pool_new + (config.tp_threads, config.tp_max_threads, 0, NULL); + } + + /* Initialize UDP or TCP transport and resolve factory */ + + if (!config.tcp_enable) + { + config.publish_uc_locators = true; + if (ddsi_udp_init () < 0) + goto err_udp_tcp_init; + gv.m_factory = ddsi_factory_find ("udp"); + } + else + { + config.publish_uc_locators = (config.tcp_port == -1) ? false : true; + /* TCP affects what features are supported/required */ + config.suppress_spdp_multicast = true; + config.many_sockets_mode = false; + config.allowMulticast = AMC_FALSE; + + if (ddsi_tcp_init () < 0) + goto err_udp_tcp_init; + gv.m_factory = ddsi_factory_find ("tcp"); + } + + if (!find_own_ip (config.networkAddressString)) + { + NN_ERROR ("No network interface selected\n"); + goto err_find_own_ip; + } + if (config.allowMulticast) + { + int i; + for (i = 0; i < gv.n_interfaces; i++) + { + if (gv.interfaces[i].mc_capable) + break; + } + if (i == gv.n_interfaces) + { + NN_WARNING ("No multicast capable interfaces: disabling multicast\n"); + config.suppress_spdp_multicast = 1; + config.allowMulticast = AMC_FALSE; + } + } + if (set_recvips () < 0) + goto err_set_recvips; + if (set_spdp_address () < 0) + goto err_set_ext_address; + if (set_default_mc_address () < 0) + goto err_set_ext_address; + if (set_ext_address_and_mask () < 0) + goto err_set_ext_address; + + { + char buf[INET6_ADDRSTRLEN_EXTENDED]; + nn_log (LC_CONFIG, "ownip: %s\n", sockaddr_to_string_no_port (buf, &gv.ownip)); + nn_log (LC_CONFIG, "extip: %s\n", sockaddr_to_string_no_port (buf, &gv.extip)); + (void)inet_ntop(AF_INET, &gv.extmask, buf, sizeof(buf)); + nn_log (LC_CONFIG, "extmask: %s%s\n", buf, config.useIpv6 ? " (not applicable)" : ""); + nn_log (LC_CONFIG, "networkid: 0x%lx\n", (unsigned long) gv.myNetworkId); + nn_log (LC_CONFIG, "SPDP MC: %s\n", locator_to_string_no_port (buf, &gv.loc_spdp_mc)); + nn_log (LC_CONFIG, "default MC: %s\n", locator_to_string_no_port (buf, &gv.loc_default_mc)); +#ifdef DDSI_INCLUDE_SSM + nn_log (LC_CONFIG, "SSM support included\n"); +#endif + } + + if (gv.ownip.ss_family != gv.extip.ss_family) + NN_FATAL ("mismatch between network address kinds\n"); + + gv.startup_mode = (config.startup_mode_duration > 0) ? 1 : 0; + nn_log (LC_CONFIG, "startup-mode: %s\n", gv.startup_mode ? "enabled" : "disabled"); + + (ddsi_plugin.init_fn) (); + + gv.xmsgpool = nn_xmsgpool_new (); + gv.serpool = ddsi_serstatepool_new (); + +#ifdef DDSI_INCLUDE_ENCRYPTION + if (q_security_plugin.new_decoder) + { + gv.recvSecurityCodec = (q_security_plugin.new_decoder) (); + nn_log (LC_CONFIG, "decoderset created\n"); + } +#endif + + nn_plist_init_default_participant (&gv.default_plist_pp); + nn_xqos_init_default_reader (&gv.default_xqos_rd); + nn_xqos_init_default_writer (&gv.default_xqos_wr); + nn_xqos_init_default_writer_noautodispose (&gv.default_xqos_wr_nad); + nn_xqos_init_default_topic (&gv.default_xqos_tp); + nn_xqos_init_default_subscriber (&gv.default_xqos_sub); + nn_xqos_init_default_publisher (&gv.default_xqos_pub); + nn_xqos_copy (&gv.spdp_endpoint_xqos, &gv.default_xqos_rd); + gv.spdp_endpoint_xqos.durability.kind = NN_TRANSIENT_LOCAL_DURABILITY_QOS; + make_builtin_endpoint_xqos (&gv.builtin_endpoint_xqos_rd, &gv.default_xqos_rd); + make_builtin_endpoint_xqos (&gv.builtin_endpoint_xqos_wr, &gv.default_xqos_wr); + + os_mutexInit (&gv.participant_set_lock); + os_condInit (&gv.participant_set_cond, &gv.participant_set_lock); + lease_management_init (); + deleted_participants_admin_init (); + gv.guid_hash = ephash_new (); + + os_mutexInit (&gv.privileged_pp_lock); + gv.privileged_pp = NULL; + + /* Template PP guid -- protected by privileged_pp_lock for simplicity */ + gv.next_ppguid.prefix.u[0] = sockaddr_to_hopefully_unique_uint32 (&gv.ownip); + gv.next_ppguid.prefix.u[1] = (unsigned) os_procIdSelf (); + gv.next_ppguid.prefix.u[2] = 1; + gv.next_ppguid.entityid.u = NN_ENTITYID_PARTICIPANT; + + os_mutexInit (&gv.lock); + os_mutexInit (&gv.spdp_lock); + gv.spdp_defrag = nn_defrag_new (NN_DEFRAG_DROP_OLDEST, config.defrag_unreliable_maxsamples); + gv.spdp_reorder = nn_reorder_new (NN_REORDER_MODE_ALWAYS_DELIVER, config.primary_reorder_maxsamples); + + gv.m_tkmap = dds_tkmap_new (); + + if (gv.m_factory->m_connless) + { + if (config.participantIndex >= 0 || config.participantIndex == PARTICIPANT_INDEX_NONE) + { + if (make_uc_sockets (&port_disc_uc, &port_data_uc, config.participantIndex) < 0) + { + NN_ERROR ("rtps_init: failed to create unicast sockets for domain %d participant %d\n", config.domainId, config.participantIndex); + goto err_unicast_sockets; + } + } + else if (config.participantIndex == PARTICIPANT_INDEX_AUTO) + { + /* try to find a free one, and update config.participantIndex */ + int ppid; + nn_log (LC_CONFIG, "rtps_init: trying to find a free participant index\n"); + for (ppid = 0; ppid <= config.maxAutoParticipantIndex; ppid++) + { + int r = make_uc_sockets (&port_disc_uc, &port_data_uc, ppid); + if (r == 0) /* Success! */ + break; + else if (r == -1) /* Try next one */ + continue; + else /* Oops! */ + { + NN_ERROR ("rtps_init: failed to create unicast sockets for domain %d participant %d\n", config.domainId, ppid); + goto err_unicast_sockets; + } + } + if (ppid > config.maxAutoParticipantIndex) + { + NN_ERROR ("rtps_init: failed to find a free participant index for domain %d\n", config.domainId); + goto err_unicast_sockets; + } + config.participantIndex = ppid; + } + else + { + assert(0); + } + nn_log (LC_CONFIG, "rtps_init: uc ports: disc %u data %u\n", port_disc_uc, port_data_uc); + } + nn_log (LC_CONFIG, "rtps_init: domainid %d participantid %d\n", config.domainId, config.participantIndex); + + gv.waitset = os_sockWaitsetNew (); + + if (config.pcap_file && *config.pcap_file) + { + gv.pcap_fp = new_pcap_file (config.pcap_file); + if (gv.pcap_fp) + { + os_mutexInit (&gv.pcap_lock); + } + } + else + { + gv.pcap_fp = NULL; + } + + if (gv.m_factory->m_connless) + { + uint32_t port; + + TRACE (("Unicast Ports: discovery %u data %u \n", + ddsi_tran_port (gv.disc_conn_uc), ddsi_tran_port (gv.data_conn_uc))); + + if (config.allowMulticast) + { + ddsi_tran_qos_t qos = ddsi_tran_create_qos (); + qos->m_multicast = true; + + /* FIXME: should check for overflow */ + port = (uint32_t) (config.port_base + config.port_dg * config.domainId + config.port_d0); + gv.disc_conn_mc = ddsi_factory_create_conn (gv.m_factory, port, qos); + + port = (uint32_t) (config.port_base + config.port_dg * config.domainId + config.port_d2); + gv.data_conn_mc = ddsi_factory_create_conn (gv.m_factory, port, qos); + + ddsi_tran_free_qos (qos); + + if (gv.disc_conn_mc == NULL || gv.data_conn_mc == NULL) + goto err_mc_conn; + + TRACE (("Multicast Ports: discovery %u data %u \n", + ddsi_tran_port (gv.disc_conn_mc), ddsi_tran_port (gv.data_conn_mc))); + + /* Set multicast locators */ + if (!is_unspec_locator(&gv.loc_spdp_mc)) + gv.loc_spdp_mc.port = ddsi_tran_port (gv.disc_conn_mc); + if (!is_unspec_locator(&gv.loc_meta_mc)) + gv.loc_meta_mc.port = ddsi_tran_port (gv.disc_conn_mc); + if (!is_unspec_locator(&gv.loc_default_mc)) + gv.loc_default_mc.port = ddsi_tran_port (gv.data_conn_mc); + + if (joinleave_spdp_defmcip (1) < 0) + goto err_mc_conn; + } + } + else + { + /* Must have a data_conn_uc/tev_conn/transmit_conn */ + gv.data_conn_uc = ddsi_factory_create_conn (gv.m_factory, 0, NULL); + + if (config.tcp_port != -1) + { + gv.listener = ddsi_factory_create_listener (gv.m_factory, config.tcp_port, NULL); + if (gv.listener == NULL || ddsi_listener_listen (gv.listener) != 0) + { + NN_ERROR ("Failed to create %s listener\n", gv.m_factory->m_typename); + if (gv.listener) + ddsi_listener_free(gv.listener); + goto err_mc_conn; + } + + /* Set unicast locators from listener */ + set_unspec_locator (&gv.loc_spdp_mc); + set_unspec_locator (&gv.loc_meta_mc); + set_unspec_locator (&gv.loc_default_mc); + + ddsi_listener_locator (gv.listener, &gv.loc_meta_uc); + ddsi_listener_locator (gv.listener, &gv.loc_default_uc); + } + } + + /* Create shared transmit connection */ + + gv.tev_conn = gv.data_conn_uc; + TRACE (("Timed event transmit port: %d\n", (int) ddsi_tran_port (gv.tev_conn))); + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + { + struct config_channel_listelem *chptr = config.channels; + while (chptr) + { + size_t slen = strlen (chptr->name) + 5; + char * tname = os_malloc (slen); + (void) snprintf (tname, slen, "tev.%s", chptr->name); + + /* Only actually create new connection if diffserv set */ + + if (chptr->diffserv_field) + { + ddsi_tran_qos_t qos = ddsi_tran_create_qos (); + qos->m_diffserv = chptr->diffserv_field; + chptr->transmit_conn = ddsi_factory_create_conn (gv.m_factory, 0, qos); + ddsi_tran_free_qos (qos); + if (chptr->transmit_conn == NULL) + { + NN_FATAL ("failed to create transmit connection for channel %s\n", chptr->name); + } + } + else + { + chptr->transmit_conn = gv.data_conn_uc; + } + TRACE (("channel %s: transmit port %d\n", chptr->name, (int) ddsi_tran_port (chptr->transmit_conn))); + +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING + if (chptr->auxiliary_bandwidth_limit > 0 || lookup_thread_properties (tname)) + { + chptr->evq = xeventq_new + ( + chptr->transmit_conn, + config.max_queued_rexmit_bytes, + config.max_queued_rexmit_msgs, + chptr->auxiliary_bandwidth_limit + ); + } +#else + if (lookup_thread_properties (tname)) + { + chptr->evq = xeventq_new + ( + chptr->transmit_conn, + config.max_queued_rexmit_bytes, + config.max_queued_rexmit_msgs, + 0 + ); + } +#endif + os_free (tname); + chptr = chptr->next; + } + } +#endif /* DDSI_INCLUDE_NETWORK_CHANNELS */ + + /* Create event queues */ + + gv.xevents = xeventq_new + ( + gv.tev_conn, + config.max_queued_rexmit_bytes, + config.max_queued_rexmit_msgs, +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING + config.auxiliary_bandwidth_limit +#else + 0 +#endif + ); + + gv.as_disc = new_addrset (); + add_to_addrset (gv.as_disc, &gv.loc_spdp_mc); + if (config.peers) + add_peer_addresses (gv.as_disc, config.peers); + if (config.peers_group) + { + gv.as_disc_group = new_addrset (); + add_peer_addresses (gv.as_disc_group, config.peers_group); + } + else + { + gv.as_disc_group = NULL; + } + + gv.gcreq_queue = gcreq_queue_new (); + + /* We create the rbufpool for the receive thread, and so we'll + become the initial owner thread. The receive thread will change + it before it does anything with it. */ + if ((gv.rbufpool = nn_rbufpool_new (config.rbuf_size, config.rmsg_chunk_size)) == NULL) + { + NN_FATAL ("rtps_init: can't allocate receive buffer pool\n"); + } + + gv.rtps_keepgoing = 1; + os_rwlockInit (&gv.qoslock); + + { + int r; + gv.builtins_dqueue = nn_dqueue_new ("builtins", config.delivery_queue_maxsamples, builtins_dqueue_handler, NULL); + if ((r = xeventq_start (gv.xevents, NULL)) < 0) + { + NN_FATAL ("failed to start global event processing thread (%d)\n", r); + } + } + + nn_xpack_sendq_init(); + nn_xpack_sendq_start(); + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + /* Create a delivery queue and start tev for each channel */ + { + struct config_channel_listelem * chptr = config.channels; + while (chptr) + { + chptr->dqueue = nn_dqueue_new (chptr->name, config.delivery_queue_maxsamples, user_dqueue_handler, NULL); + if (chptr->evq) + { + int r; + if ((r = xeventq_start (chptr->evq, chptr->name)) < 0) + NN_FATAL ("failed to start event processing thread for channel '%s' (%d)\n", chptr->name, r); + } + chptr = chptr->next; + } + } +#else + gv.user_dqueue = nn_dqueue_new ("user", config.delivery_queue_maxsamples, user_dqueue_handler, NULL); +#endif + + gv.recv_ts = create_thread ("recv", (uint32_t (*) (void *)) recv_thread, gv.rbufpool); + if (gv.listener) + { + gv.listen_ts = create_thread ("listen", (uint32_t (*) (void *)) listen_thread, gv.listener); + } + + if (gv.startup_mode) + { + qxev_end_startup_mode (add_duration_to_mtime (now_mt (), config.startup_mode_duration)); + } + + if (config.monitor_port >= 0) + { + gv.debmon = new_debug_monitor (config.monitor_port); + } + + return 0; + +err_mc_conn: + if (gv.disc_conn_mc) + ddsi_conn_free (gv.disc_conn_mc); + if (gv.data_conn_mc) + ddsi_conn_free (gv.data_conn_mc); + if (gv.pcap_fp) + os_mutexDestroy (&gv.pcap_lock); + os_sockWaitsetFree (gv.waitset); + if (gv.disc_conn_uc == gv.data_conn_uc) + ddsi_conn_free (gv.data_conn_uc); + else + { + ddsi_conn_free (gv.data_conn_uc); + ddsi_conn_free (gv.disc_conn_uc); + } +err_unicast_sockets: + dds_tkmap_free (gv.m_tkmap); + nn_reorder_free (gv.spdp_reorder); + nn_defrag_free (gv.spdp_defrag); + os_mutexDestroy (&gv.spdp_lock); + os_mutexDestroy (&gv.lock); + os_mutexDestroy (&gv.privileged_pp_lock); + ephash_free (gv.guid_hash); + gv.guid_hash = NULL; + deleted_participants_admin_fini (); + lease_management_term (); + os_condDestroy (&gv.participant_set_cond); + os_mutexDestroy (&gv.participant_set_lock); +#ifdef DDSI_INCLUDE_ENCRYPTION + if (q_security_plugin.free_decoder) + q_security_plugin.free_decoder (gv.recvSecurityCodec); +#endif + nn_xqos_fini (&gv.builtin_endpoint_xqos_wr); + nn_xqos_fini (&gv.builtin_endpoint_xqos_rd); + nn_xqos_fini (&gv.spdp_endpoint_xqos); + nn_xqos_fini (&gv.default_xqos_pub); + nn_xqos_fini (&gv.default_xqos_sub); + nn_xqos_fini (&gv.default_xqos_tp); + nn_xqos_fini (&gv.default_xqos_wr_nad); + nn_xqos_fini (&gv.default_xqos_wr); + nn_xqos_fini (&gv.default_xqos_rd); + nn_plist_fini (&gv.default_plist_pp); + ddsi_serstatepool_free (gv.serpool); + nn_xmsgpool_free (gv.xmsgpool); + (ddsi_plugin.fini_fn) (); +err_set_ext_address: + while (gv.recvips) + { + struct ospl_in_addr_node *n = gv.recvips; + gv.recvips = n->next; + os_free (n); + } +err_set_recvips: + { + int i; + for (i = 0; i < gv.n_interfaces; i++) + os_free (gv.interfaces[i].name); + } +err_find_own_ip: + ddsi_tran_factories_fini (); +err_udp_tcp_init: + if (config.tp_enable) + ut_thread_pool_free (gv.thread_pool); + return -1; +} + +struct dq_builtins_ready_arg { + os_mutex lock; + os_cond cond; + int ready; +}; + +static void builtins_dqueue_ready_cb (void *varg) +{ + struct dq_builtins_ready_arg *arg = varg; + os_mutexLock (&arg->lock); + arg->ready = 1; + os_condBroadcast (&arg->cond); + os_mutexUnlock (&arg->lock); +} + +void rtps_term_prep (void) +{ + /* Stop all I/O */ + os_mutexLock (&gv.lock); + if (gv.rtps_keepgoing) + { + gv.rtps_keepgoing = 0; /* so threads will stop once they get round to checking */ + os_atomic_fence (); + /* can't wake up throttle_writer, currently, but it'll check every few seconds */ + os_sockWaitsetTrigger (gv.waitset); + } + os_mutexUnlock (&gv.lock); +} + +void rtps_term (void) +{ + struct thread_state1 *self = lookup_thread_state (); +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + struct config_channel_listelem * chptr; +#endif + + if (gv.debmon) + { + free_debug_monitor (gv.debmon); + gv.debmon = NULL; + } + + /* Stop all I/O */ + rtps_term_prep (); + join_thread (gv.recv_ts); + + if (gv.listener) + { + ddsi_listener_unblock(gv.listener); + join_thread (gv.listen_ts); + ddsi_listener_free(gv.listener); + } + + xeventq_stop (gv.xevents); +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + for (chptr = config.channels; chptr; chptr = chptr->next) + { + if (chptr->evq) + xeventq_stop (chptr->evq); + } +#endif /* DDSI_INCLUDE_NETWORK_CHANNELS */ + + /* Send a bubble through the delivery queue for built-ins, so that any + pending proxy participant discovery is finished before we start + deleting them */ + { + struct dq_builtins_ready_arg arg; + os_mutexInit (&arg.lock); + os_condInit (&arg.cond, &arg.lock); + arg.ready = 0; + nn_dqueue_enqueue_callback(gv.builtins_dqueue, builtins_dqueue_ready_cb, &arg); + os_mutexLock (&arg.lock); + while (!arg.ready) + os_condWait (&arg.cond, &arg.lock); + os_mutexUnlock (&arg.lock); + os_condDestroy (&arg.cond); + os_mutexDestroy (&arg.lock); + } + + /* Once the receive threads have stopped, defragmentation and + reorder state can't change anymore, and can be freed safely. */ + nn_reorder_free (gv.spdp_reorder); + nn_defrag_free (gv.spdp_defrag); + os_mutexDestroy (&gv.spdp_lock); +#ifdef DDSI_INCLUDE_ENCRYPTION + if (q_security_plugin.free_decoder) + { + (q_security_plugin.free_decoder) (gv.recvSecurityCodec); + } +#endif /* DDSI_INCLUDE_ENCRYPTION */ + + { + struct ephash_enum_proxy_participant est; + struct proxy_participant *proxypp; + const nn_wctime_t tnow = now(); + /* Clean up proxy readers, proxy writers and proxy + participants. Deleting a proxy participants deletes all its + readers and writers automatically */ + thread_state_awake (self); + ephash_enum_proxy_participant_init (&est); + while ((proxypp = ephash_enum_proxy_participant_next (&est)) != NULL) + { + delete_proxy_participant_by_guid(&proxypp->e.guid, tnow, 1); + } + ephash_enum_proxy_participant_fini (&est); + thread_state_asleep (self); + } + + { + const nn_vendorid_t ownvendorid = MY_VENDOR_ID; + struct ephash_enum_writer est_wr; + struct ephash_enum_reader est_rd; + struct ephash_enum_participant est_pp; + struct participant *pp; + struct writer *wr; + struct reader *rd; + /* Delete readers, writers and participants, relying on + delete_participant to schedule the deletion of the built-in + rwriters to get all SEDP and SPDP dispose+unregister messages + out. FIXME: need to keep xevent thread alive for a while + longer. */ + thread_state_awake (self); + ephash_enum_writer_init (&est_wr); + while ((wr = ephash_enum_writer_next (&est_wr)) != NULL) + { + if (!is_builtin_entityid (wr->e.guid.entityid, ownvendorid)) + delete_writer_nolinger (&wr->e.guid); + } + ephash_enum_writer_fini (&est_wr); + thread_state_awake (self); + ephash_enum_reader_init (&est_rd); + while ((rd = ephash_enum_reader_next (&est_rd)) != NULL) + { + if (!is_builtin_entityid (rd->e.guid.entityid, ownvendorid)) + (void)delete_reader (&rd->e.guid); + } + ephash_enum_reader_fini (&est_rd); + thread_state_awake (self); + ephash_enum_participant_init (&est_pp); + while ((pp = ephash_enum_participant_next (&est_pp)) != NULL) + { + delete_participant (&pp->e.guid); + } + ephash_enum_participant_fini (&est_pp); + thread_state_asleep (self); + } + + /* Wait until all participants are really gone => by then we can be + certain that no new GC requests will be added */ + os_mutexLock (&gv.participant_set_lock); + while (gv.nparticipants > 0) + os_condWait (&gv.participant_set_cond, &gv.participant_set_lock); + os_mutexUnlock (&gv.participant_set_lock); + + /* Clean up privileged_pp -- it must be NULL now (all participants + are gone), but the lock still needs to be destroyed */ + assert (gv.privileged_pp == NULL); + os_mutexDestroy (&gv.privileged_pp_lock); + + /* Shut down the GC system -- no new requests will be added */ + gcreq_queue_free (gv.gcreq_queue); + + /* No new data gets added to any admin, all synchronous processing + has ended, so now we can drain the delivery queues to end up with + the expected reference counts all over the radmin thingummies. */ + nn_dqueue_free (gv.builtins_dqueue); + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + chptr = config.channels; + while (chptr) + { + nn_dqueue_free (chptr->dqueue); + chptr = chptr->next; + } +#else + nn_dqueue_free (gv.user_dqueue); +#endif + + xeventq_free (gv.xevents); + + nn_xpack_sendq_stop(); + nn_xpack_sendq_fini(); + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS + chptr = config.channels; + while (chptr) + { + if (chptr->evq) + { + xeventq_free (chptr->evq); + } + if (chptr->transmit_conn != gv.data_conn_uc) + { + ddsi_conn_free (chptr->transmit_conn); + } + chptr = chptr->next; + } +#endif + + ut_thread_pool_free (gv.thread_pool); + + os_sockWaitsetFree (gv.waitset); + + (void) joinleave_spdp_defmcip (0); + + ddsi_conn_free (gv.disc_conn_mc); + ddsi_conn_free (gv.data_conn_mc); + if (gv.disc_conn_uc == gv.data_conn_uc) + { + ddsi_conn_free (gv.data_conn_uc); + } + else + { + ddsi_conn_free (gv.data_conn_uc); + ddsi_conn_free (gv.disc_conn_uc); + } + + /* Not freeing gv.tev_conn: it aliases data_conn_uc */ + + ddsi_tran_factories_fini (); + + if (gv.pcap_fp) + { + os_mutexDestroy (&gv.pcap_lock); + fclose (gv.pcap_fp); + } + + unref_addrset (gv.as_disc); + unref_addrset (gv.as_disc_group); + + /* Must delay freeing of rbufpools until after *all* references have + been dropped, which only happens once all receive threads have + stopped, defrags and reorders have been freed, and all delivery + queues been drained. I.e., until very late in the game. */ + nn_rbufpool_free (gv.rbufpool); + dds_tkmap_free (gv.m_tkmap); + + ephash_free (gv.guid_hash); + gv.guid_hash = NULL; + deleted_participants_admin_fini (); + lease_management_term (); + os_mutexDestroy (&gv.participant_set_lock); + os_condDestroy (&gv.participant_set_cond); + + nn_xqos_fini (&gv.builtin_endpoint_xqos_wr); + nn_xqos_fini (&gv.builtin_endpoint_xqos_rd); + nn_xqos_fini (&gv.spdp_endpoint_xqos); + nn_xqos_fini (&gv.default_xqos_pub); + nn_xqos_fini (&gv.default_xqos_sub); + nn_xqos_fini (&gv.default_xqos_tp); + nn_xqos_fini (&gv.default_xqos_wr_nad); + nn_xqos_fini (&gv.default_xqos_wr); + nn_xqos_fini (&gv.default_xqos_rd); + nn_plist_fini (&gv.default_plist_pp); + + os_mutexDestroy (&gv.lock); + os_rwlockDestroy (&gv.qoslock); + + while (gv.recvips) + { + struct ospl_in_addr_node *n = gv.recvips; +/* The compiler doesn't realize that n->next is always initialized. */ +OS_WARNING_MSVC_OFF(6001); + gv.recvips = n->next; +OS_WARNING_MSVC_ON(6001); + os_free (n); + } + + { + int i; + for (i = 0; i < (int) gv.n_interfaces; i++) + os_free (gv.interfaces[i].name); + } + + ddsi_serstatepool_free (gv.serpool); + nn_xmsgpool_free (gv.xmsgpool); + (ddsi_plugin.fini_fn) (); +} diff --git a/src/core/ddsi/src/q_lat_estim.c b/src/core/ddsi/src/q_lat_estim.c new file mode 100644 index 0000000..f3c684d --- /dev/null +++ b/src/core/ddsi/src/q_lat_estim.c @@ -0,0 +1,84 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include + +#include "ddsi/q_log.h" +#include "ddsi/q_unused.h" +#include "ddsi/q_lat_estim.h" +#include +#include + +void nn_lat_estim_init (struct nn_lat_estim *le) +{ + int i; + le->index = 0; + for (i = 0; i < NN_LAT_ESTIM_MEDIAN_WINSZ; i++) + le->window[i] = 0; + le->smoothed = 0; +} + +void nn_lat_estim_fini (UNUSED_ARG (struct nn_lat_estim *le)) +{ +} + +static int cmpfloat (const float *a, const float *b) +{ + return (*a < *b) ? -1 : (*a > *b) ? 1 : 0; +} + +void nn_lat_estim_update (struct nn_lat_estim *le, int64_t est) +{ + const float alpha = 0.01f; + float fest, med; + float tmp[NN_LAT_ESTIM_MEDIAN_WINSZ]; + if (est <= 0) + return; + fest = (float) est / 1e3f; /* we do latencies in microseconds */ + le->window[le->index] = fest; + if (++le->index == NN_LAT_ESTIM_MEDIAN_WINSZ) + le->index = 0; + memcpy (tmp, le->window, sizeof (tmp)); + qsort (tmp, NN_LAT_ESTIM_MEDIAN_WINSZ, sizeof (tmp[0]), (int (*) (const void *, const void *)) cmpfloat); + med = tmp[NN_LAT_ESTIM_MEDIAN_WINSZ / 2]; + if (le->smoothed == 0 && le->index == 0) + le->smoothed = med; + else if (le->smoothed != 0) + le->smoothed = (1.0f - alpha) * le->smoothed + alpha * med; +} + +int nn_lat_estim_log (logcat_t logcat, const char *tag, const struct nn_lat_estim *le) +{ + if (le->smoothed == 0.0f) + return 0; + else + { + float tmp[NN_LAT_ESTIM_MEDIAN_WINSZ]; + int i; + memcpy (tmp, le->window, sizeof (tmp)); + qsort (tmp, NN_LAT_ESTIM_MEDIAN_WINSZ, sizeof (tmp[0]), (int (*) (const void *, const void *)) cmpfloat); + if (tag) + nn_log (logcat, " LAT(%s: %e {", tag, le->smoothed); + else + nn_log (logcat, " LAT(%e {", le->smoothed); + for (i = 0; i < NN_LAT_ESTIM_MEDIAN_WINSZ; i++) + nn_log (logcat, "%s%e", (i > 0) ? "," : "", tmp[i]); + nn_log (logcat, "})"); + return 1; + } +} + +#if 0 /* not implemented yet */ +double nn_lat_estim_current (const struct nn_lat_estim *le) +{ +} +#endif diff --git a/src/core/ddsi/src/q_lease.c b/src/core/ddsi/src/q_lease.c new file mode 100644 index 0000000..bc24406 --- /dev/null +++ b/src/core/ddsi/src/q_lease.c @@ -0,0 +1,370 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include + +#include "os/os.h" + +#include "util/ut_fibheap.h" + +#include "ddsi/ddsi_ser.h" +#include "ddsi/q_protocol.h" +#include "ddsi/q_rtps.h" +#include "ddsi/q_misc.h" +#include "ddsi/q_config.h" +#include "ddsi/q_log.h" +#include "ddsi/q_plist.h" +#include "ddsi/q_unused.h" +#include "ddsi/q_xevent.h" +#include "ddsi/q_addrset.h" +#include "ddsi/q_ddsi_discovery.h" +#include "ddsi/q_radmin.h" +#include "ddsi/q_ephash.h" +#include "ddsi/q_entity.h" +#include "ddsi/q_globals.h" +#include "ddsi/q_xmsg.h" +#include "ddsi/q_bswap.h" +#include "ddsi/q_transmit.h" +#include "ddsi/q_lease.h" + +#include "ddsi/sysdeps.h" + +/* This is absolute bottom for signed integers, where -x = x and yet x + != 0 -- and note that it had better be 2's complement machine! */ +#define TSCHED_NOT_ON_HEAP INT64_MIN + +struct lease { + ut_fibheapNode_t heapnode; + nn_etime_t tsched; /* access guarded by leaseheap_lock */ + nn_etime_t tend; /* access guarded by lock_lease/unlock_lease */ + int64_t tdur; /* constant (renew depends on it) */ + struct entity_common *entity; /* constant */ +}; + +static int compare_lease_tsched (const void *va, const void *vb); + +static const ut_fibheapDef_t lease_fhdef = UT_FIBHEAPDEF_INITIALIZER(offsetof (struct lease, heapnode), compare_lease_tsched); + +static int compare_lease_tsched (const void *va, const void *vb) +{ + const struct lease *a = va; + const struct lease *b = vb; + return (a->tsched.v == b->tsched.v) ? 0 : (a->tsched.v < b->tsched.v) ? -1 : 1; +} + +void lease_management_init (void) +{ + int i; + os_mutexInit (&gv.leaseheap_lock); + for (i = 0; i < N_LEASE_LOCKS; i++) + os_mutexInit (&gv.lease_locks[i]); + ut_fibheapInit (&lease_fhdef, &gv.leaseheap); +} + +void lease_management_term (void) +{ + int i; + assert (ut_fibheapMin (&lease_fhdef, &gv.leaseheap) == NULL); + for (i = 0; i < N_LEASE_LOCKS; i++) + os_mutexDestroy (&gv.lease_locks[i]); + os_mutexDestroy (&gv.leaseheap_lock); +} + +static os_mutex *lock_lease_addr (struct lease const * const l) +{ + uint32_t u = (uint16_t) ((uintptr_t) l >> 3); + uint32_t v = u * 0xb4817365; + unsigned idx = v >> (32 - N_LEASE_LOCKS_LG2); + return &gv.lease_locks[idx]; +} + +static void lock_lease (const struct lease *l) +{ + os_mutexLock (lock_lease_addr (l)); +} + +static void unlock_lease (const struct lease *l) +{ + os_mutexUnlock (lock_lease_addr (l)); +} + +struct lease *lease_new (nn_etime_t texpire, int64_t tdur, struct entity_common *e) +{ + struct lease *l; + if ((l = os_malloc (sizeof (*l))) == NULL) + return NULL; + TRACE (("lease_new(tdur %"PRId64" guid %x:%x:%x:%x) @ %p\n", tdur, PGUID (e->guid), (void *) l)); + l->tdur = tdur; + l->tend = texpire; + l->tsched.v = TSCHED_NOT_ON_HEAP; + l->entity = e; + return l; +} + +void lease_register (struct lease *l) +{ + TRACE (("lease_register(l %p guid %x:%x:%x:%x)\n", (void *) l, PGUID (l->entity->guid))); + os_mutexLock (&gv.leaseheap_lock); + lock_lease (l); + assert (l->tsched.v == TSCHED_NOT_ON_HEAP); + if (l->tend.v != T_NEVER) + { + l->tsched = l->tend; + ut_fibheapInsert (&lease_fhdef, &gv.leaseheap, l); + } + unlock_lease (l); + os_mutexUnlock (&gv.leaseheap_lock); +} + +void lease_free (struct lease *l) +{ + TRACE (("lease_free(l %p guid %x:%x:%x:%x)\n", (void *) l, PGUID (l->entity->guid))); + os_mutexLock (&gv.leaseheap_lock); + if (l->tsched.v != TSCHED_NOT_ON_HEAP) + ut_fibheapDelete (&lease_fhdef, &gv.leaseheap, l); + os_mutexUnlock (&gv.leaseheap_lock); + os_free (l); +} + +void lease_renew (struct lease *l, nn_etime_t tnowE) +{ + nn_etime_t tend_new = add_duration_to_etime (tnowE, l->tdur); + int did_update; + lock_lease (l); + /* do not touch tend if moving forward or if already expired */ + if (tend_new.v <= l->tend.v || tnowE.v >= l->tend.v) + did_update = 0; + else + { + l->tend = tend_new; + did_update = 1; + } + unlock_lease (l); + + if (did_update && (config.enabled_logcats & LC_TRACE)) + { + int tsec, tusec; + TRACE ((" L(")); + if (l->entity->guid.entityid.u == NN_ENTITYID_PARTICIPANT) + TRACE ((":%x", l->entity->guid.entityid.u)); + else + TRACE (("%x:%x:%x:%x", PGUID (l->entity->guid))); + etime_to_sec_usec (&tsec, &tusec, tend_new); + TRACE ((" %d.%06d)", tsec, tusec)); + } +} + +void lease_set_expiry (struct lease *l, nn_etime_t when) +{ + assert (when.v >= 0); + os_mutexLock (&gv.leaseheap_lock); + lock_lease (l); + l->tend = when; + if (l->tend.v < l->tsched.v) + { + /* moved forward and currently scheduled (by virtue of + TSCHED_NOT_ON_HEAP == INT64_MIN) */ + l->tsched = l->tend; + ut_fibheapDecreaseKey (&lease_fhdef, &gv.leaseheap, l); + } + else if (l->tsched.v == TSCHED_NOT_ON_HEAP && l->tend.v < T_NEVER) + { + /* not currently scheduled, with a finite new expiry time */ + l->tsched = l->tend; + ut_fibheapInsert (&lease_fhdef, &gv.leaseheap, l); + } + unlock_lease (l); + os_mutexUnlock (&gv.leaseheap_lock); +} + +void check_and_handle_lease_expiration (UNUSED_ARG (struct thread_state1 *self), nn_etime_t tnowE) +{ + struct lease *l; + const nn_wctime_t tnow = now(); + os_mutexLock (&gv.leaseheap_lock); + while ((l = ut_fibheapMin (&lease_fhdef, &gv.leaseheap)) != NULL && l->tsched.v <= tnowE.v) + { + nn_guid_t g = l->entity->guid; + enum entity_kind k = l->entity->kind; + + assert (l->tsched.v != TSCHED_NOT_ON_HEAP); + ut_fibheapExtractMin (&lease_fhdef, &gv.leaseheap); + + lock_lease (l); + if (tnowE.v < l->tend.v) + { + if (l->tend.v == T_NEVER) { + /* don't reinsert if it won't expire */ + l->tsched.v = TSCHED_NOT_ON_HEAP; + unlock_lease (l); + } else { + l->tsched = l->tend; + unlock_lease (l); + ut_fibheapInsert (&lease_fhdef, &gv.leaseheap, l); + } + continue; + } + + nn_log (LC_DISCOVERY, "lease expired: l %p guid %x:%x:%x:%x tend %"PRId64" < now %"PRId64"\n", (void *) l, PGUID (g), l->tend.v, tnowE.v); + + /* If the proxy participant is relying on another participant for + writing its discovery data (on the privileged participant, + i.e., its ddsi2 instance), we can't afford to drop it while the + privileged one is still considered live. If we do and it was a + temporary asymmetrical thing and the ddsi2 instance never lost + its liveliness, we will not rediscover the endpoints of this + participant because we will not rediscover the ddsi2 + participant. + + So IF it is dependent on another one, we renew the lease for a + very short while if the other one is still alive. If it is a + real case of lost liveliness, the other one will be gone soon + enough; if not, we should get a sign of life soon enough. + + In this case, we simply abort the current iteration of the loop + after renewing the lease and continue with the next one. + + This trick would fail if the ddsi2 participant can lose its + liveliness and regain it before we re-check the liveliness of + the dependent participants, and so the interval here must + significantly less than the pruning time for the + deleted_participants admin. + + I guess that means there is a really good argument for the SPDP + and SEDP writers to be per-participant! */ + if (k == EK_PROXY_PARTICIPANT) + { + struct proxy_participant *proxypp; + if ((proxypp = ephash_lookup_proxy_participant_guid (&g)) != NULL && + ephash_lookup_proxy_participant_guid (&proxypp->privileged_pp_guid) != NULL) + { + nn_log (LC_DISCOVERY, "but postponing because privileged pp %x:%x:%x:%x is still live\n", + PGUID (proxypp->privileged_pp_guid)); + l->tsched = l->tend = add_duration_to_etime (tnowE, 200 * T_MILLISECOND); + unlock_lease (l); + ut_fibheapInsert (&lease_fhdef, &gv.leaseheap, l); + continue; + } + } + + unlock_lease (l); + + l->tsched.v = TSCHED_NOT_ON_HEAP; + os_mutexUnlock (&gv.leaseheap_lock); + + switch (k) + { + case EK_PARTICIPANT: + delete_participant (&g); + break; + case EK_PROXY_PARTICIPANT: + delete_proxy_participant_by_guid (&g, tnow, 1); + break; + case EK_WRITER: + delete_writer_nolinger (&g); + break; + case EK_PROXY_WRITER: + delete_proxy_writer (&g, tnow, 1); + break; + case EK_READER: + (void)delete_reader (&g); + break; + case EK_PROXY_READER: + delete_proxy_reader (&g, tnow, 1); + break; + } + + os_mutexLock (&gv.leaseheap_lock); + } + os_mutexUnlock (&gv.leaseheap_lock); +} + +/******/ + +static void debug_print_rawdata (const char *msg, const void *data, size_t len) +{ + const unsigned char *c = data; + size_t i; + TRACE (("%s<", msg)); + for (i = 0; i < len; i++) + { + if (32 < c[i] && c[i] <= 127) + TRACE (("%s%c", (i > 0 && (i%4) == 0) ? " " : "", c[i])); + else + TRACE (("%s\\x%02x", (i > 0 && (i%4) == 0) ? " " : "", c[i])); + } + TRACE ((">")); +} + +void handle_PMD (UNUSED_ARG (const struct receiver_state *rst), nn_wctime_t timestamp, unsigned statusinfo, const void *vdata, unsigned len) +{ + const struct CDRHeader *data = vdata; /* built-ins not deserialized (yet) */ + const int bswap = (data->identifier == CDR_LE) ^ PLATFORM_IS_LITTLE_ENDIAN; + struct proxy_participant *pp; + nn_guid_t ppguid; + TRACE ((" PMD ST%x", statusinfo)); + if (data->identifier != CDR_LE && data->identifier != CDR_BE) + { + TRACE ((" PMD data->identifier %u !?\n", ntohs (data->identifier))); + return; + } + switch (statusinfo & (NN_STATUSINFO_DISPOSE | NN_STATUSINFO_UNREGISTER)) + { + case 0: + if (offsetof (ParticipantMessageData_t, value) > len - sizeof (struct CDRHeader)) + debug_print_rawdata (" SHORT1", data, len); + else + { + const ParticipantMessageData_t *pmd = (ParticipantMessageData_t *) (data + 1); + nn_guid_prefix_t p = nn_ntoh_guid_prefix (pmd->participantGuidPrefix); + unsigned kind = ntohl (pmd->kind); + unsigned length = bswap ? bswap4u (pmd->length) : pmd->length; + TRACE ((" pp %x:%x:%x kind %u data %u", p.u[0], p.u[1], p.u[2], kind, length)); + if (len - sizeof (struct CDRHeader) - offsetof (ParticipantMessageData_t, value) < length) + debug_print_rawdata (" SHORT2", pmd->value, len - sizeof (struct CDRHeader) - offsetof (ParticipantMessageData_t, value)); + else + debug_print_rawdata ("", pmd->value, length); + ppguid.prefix = p; + ppguid.entityid.u = NN_ENTITYID_PARTICIPANT; + if ((pp = ephash_lookup_proxy_participant_guid (&ppguid)) == NULL) + TRACE ((" PPunknown")); + else + { + /* Renew lease if arrival of this message didn't already do so, also renew the lease + of the virtual participant used for DS-discovered endpoints */ + if (!config.arrival_of_data_asserts_pp_and_ep_liveliness) + lease_renew (os_atomic_ldvoidp (&pp->lease), now_et ()); + } + } + break; + + case NN_STATUSINFO_DISPOSE: + case NN_STATUSINFO_UNREGISTER: + case NN_STATUSINFO_DISPOSE | NN_STATUSINFO_UNREGISTER: + /* Serialized key; BE or LE doesn't matter as both fields are + defined as octets. */ + if (len < (int) (sizeof (struct CDRHeader) + sizeof (nn_guid_prefix_t))) + debug_print_rawdata (" SHORT3", data, len); + else + { + ppguid.prefix = nn_ntoh_guid_prefix (*((nn_guid_prefix_t *) (data + 1))); + ppguid.entityid.u = NN_ENTITYID_PARTICIPANT; + if (delete_proxy_participant_by_guid (&ppguid, timestamp, 0) < 0) + TRACE ((" unknown")); + else + TRACE ((" delete")); + } + break; + } + TRACE (("\n")); +} diff --git a/src/core/ddsi/src/q_log.c b/src/core/ddsi/src/q_log.c new file mode 100644 index 0000000..1ebd06f --- /dev/null +++ b/src/core/ddsi/src/q_log.c @@ -0,0 +1,185 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include +#include +#include + +#include "os/os.h" + +#include "ddsi/q_config.h" +#include "ddsi/q_globals.h" +#include "ddsi/q_thread.h" +#include "ddsi/q_time.h" +#include "ddsi/q_log.h" + +#define MAX_TIMESTAMP_LENGTH (10 + 1 + 6) +#define MAX_TID_LENGTH 10 +#define MAX_HDR_LENGTH (MAX_TIMESTAMP_LENGTH + 1 + MAX_TID_LENGTH + + 2) + +#define BUF_OFFSET MAX_HDR_LENGTH + +static void logbuf_flush_real (struct thread_state1 *self, logbuf_t lb) +{ + if (config.tracingOutputFile != NULL) + { + const char *tname = self ? self->name : "(anon)"; + char hdr[MAX_HDR_LENGTH + 1]; + int n, tsec, tusec; + if (lb->tstamp.v < 0) + lb->tstamp = now (); + wctime_to_sec_usec (&tsec, &tusec, lb->tstamp); + lb->tstamp.v = -1; + n = snprintf (hdr, sizeof (hdr), "%d.%06d/%*.*s: ", tsec, tusec, MAX_TID_LENGTH, MAX_TID_LENGTH, tname); + assert (0 < n && n <= BUF_OFFSET); + memcpy (lb->buf + BUF_OFFSET - n, hdr, (size_t) n); + fwrite (lb->buf + BUF_OFFSET - n, 1, lb->pos - BUF_OFFSET + (size_t) n, config.tracingOutputFile); + fflush (config.tracingOutputFile); + } + lb->pos = BUF_OFFSET; + lb->buf[lb->pos] = 0; +} + +static void logbuf_flush (struct thread_state1 *self, logbuf_t lb) +{ + if (lb->pos > BUF_OFFSET) + { + if (lb->pos < (int) sizeof (lb->buf)) + lb->buf[lb->pos++] = '\n'; + else + lb->buf[sizeof (lb->buf) - 1] = '\n'; + logbuf_flush_real (self, lb); + } +} + +void logbuf_init (logbuf_t lb) +{ + lb->bufsz = sizeof (lb->buf); + lb->pos = BUF_OFFSET; + lb->tstamp.v = -1; + lb->buf[lb->pos] = 0; +} + +logbuf_t logbuf_new (void) +{ + logbuf_t lb = os_malloc (sizeof (*lb)); + logbuf_init (lb); + return lb; +} + +void logbuf_free (logbuf_t lb) +{ + logbuf_flush (lookup_thread_state (), lb); + os_free (lb); +} + +/* LOGGING ROUTINES */ + +static void nn_vlogb (struct thread_state1 *self, const char *fmt, va_list ap) +{ + int n, trunc = 0; + size_t nrem; + logbuf_t lb; + if (*fmt == 0) + return; + if (self && self->lb) + lb = self->lb; + else + { + lb = &gv.static_logbuf; + if (gv.static_logbuf_lock_inited) + { + /* not supposed to be multi-threaded when mutex not + initialized */ + os_mutexLock (&gv.static_logbuf_lock); + } + } + + nrem = lb->bufsz - lb->pos; + if (nrem > 0) + { + n = os_vsnprintf (lb->buf + lb->pos, nrem, fmt, ap); + if (n >= 0 && (size_t) n < nrem) + lb->pos += (size_t) n; + else + { + lb->pos += nrem; + trunc = 1; + } + if (trunc) + { + static const char msg[] = "(trunc)\n"; + const size_t msglen = sizeof (msg) - 1; + assert (lb->pos <= lb->bufsz); + assert (lb->pos >= msglen); + memcpy (lb->buf + lb->pos - msglen, msg, msglen); + } + } + if (fmt[strlen (fmt) - 1] == '\n') + { + logbuf_flush_real (self, lb); + } + + if (lb == &gv.static_logbuf && gv.static_logbuf_lock_inited) + { + os_mutexUnlock (&gv.static_logbuf_lock); + } +} + +int nn_vlog (logcat_t cat, const char *fmt, va_list ap) +{ + if (config.enabled_logcats & cat) + { + struct thread_state1 *self = lookup_thread_state (); + nn_vlogb (self, fmt, ap); + } + return 0; +} + +int nn_log (_In_ logcat_t cat, _In_z_ _Printf_format_string_ const char *fmt, ...) +{ + if (config.enabled_logcats & cat) + { + struct thread_state1 *self = lookup_thread_state (); + va_list ap; + va_start (ap, fmt); + nn_vlogb (self, fmt, ap); + va_end (ap); + } + if (cat == LC_FATAL) + { + abort (); + } + return 0; +} + +int nn_trace (_In_z_ _Printf_format_string_ const char *fmt, ...) +{ + if (config.enabled_logcats & LC_TRACE) + { + struct thread_state1 *self = lookup_thread_state (); + va_list ap; + va_start (ap, fmt); + nn_vlogb (self, fmt, ap); + va_end (ap); + } + return 0; +} + +void nn_log_set_tstamp (nn_wctime_t tnow) +{ + struct thread_state1 *self = lookup_thread_state (); + if (self && self->lb) + self->lb->tstamp = tnow; +} diff --git a/src/core/ddsi/src/q_misc.c b/src/core/ddsi/src/q_misc.c new file mode 100644 index 0000000..32ea930 --- /dev/null +++ b/src/core/ddsi/src/q_misc.c @@ -0,0 +1,209 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include + +#include "ddsi/q_misc.h" +#include "ddsi/q_bswap.h" +#include "ddsi/q_md5.h" + +int vendor_is_rti (nn_vendorid_t vendor) +{ + const nn_vendorid_t rti = NN_VENDORID_RTI; + return vendor.id[0] == rti.id[0] && vendor.id[1] == rti.id[1]; +} + +int vendor_is_twinoaks (nn_vendorid_t vendor) +{ + const nn_vendorid_t twinoaks = NN_VENDORID_TWINOAKS; + return vendor.id[0] == twinoaks.id[0] && vendor.id[1] == twinoaks.id[1]; +} + +int vendor_is_cloud (nn_vendorid_t vendor) +{ + const nn_vendorid_t cloud = NN_VENDORID_PRISMTECH_CLOUD; + return vendor.id[0] == cloud.id[0] && vendor.id[1] == cloud.id[1]; +} + +int vendor_is_prismtech (nn_vendorid_t vid) +{ + const nn_vendorid_t pt1 = NN_VENDORID_PRISMTECH_OSPL; + const nn_vendorid_t pt2 = NN_VENDORID_PRISMTECH_LITE; + const nn_vendorid_t pt3 = NN_VENDORID_PRISMTECH_GATEWAY; + const nn_vendorid_t pt4 = NN_VENDORID_PRISMTECH_JAVA; + const nn_vendorid_t pt5 = NN_VENDORID_PRISMTECH_CLOUD; + const nn_vendorid_t pt6 = NN_VENDORID_ECLIPSE_CYCLONEDDS; + + return + (vid.id[0] == pt1.id[0]) && + ((vid.id[1] == pt1.id[1]) || (vid.id[1] == pt2.id[1]) + || (vid.id[1] == pt3.id[1]) || (vid.id[1] == pt4.id[1]) + || (vid.id[1] == pt5.id[1]) || (vid.id[1] == pt6.id[1])); +} + +int vendor_is_opensplice (nn_vendorid_t vid) +{ + const nn_vendorid_t pt1 = NN_VENDORID_PRISMTECH_OSPL; + return (vid.id[0] == pt1.id[0] && vid.id[1] == pt1.id[1]); +} + +int is_own_vendor (nn_vendorid_t vendor) +{ + const nn_vendorid_t ownid = MY_VENDOR_ID; + return vendor.id[0] == ownid.id[0] && vendor.id[1] == ownid.id[1]; +} + + +seqno_t fromSN (const nn_sequence_number_t sn) +{ + return ((seqno_t) sn.high << 32) | sn.low; +} + +nn_sequence_number_t toSN (seqno_t n) +{ + nn_sequence_number_t x; + x.high = (int) (n >> 32); + x.low = (unsigned) n; + return x; +} + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS +int WildcardOverlap(char * p1, char * p2) +{ + /* both patterns are empty or contain wildcards => overlap */ + if ((p1 == NULL || strcmp(p1,"") == 0 || strcmp(p1,"*") == 0) && + (p2 == NULL || strcmp(p2,"") == 0 || strcmp(p2,"*") == 0)) + return 1; + + /* Either pattern is empty (but the other is not empty or wildcard only) => no overlap */ + if (p1 == NULL || strcmp(p1,"") == 0 || p2 == NULL || strcmp(p2,"")==0) + return 0; + + if ( (p1[0] == '*' || p2[0] == '*') && (WildcardOverlap(p1,p2+1)|| WildcardOverlap(p1+1,p2))) + return 1; + + if ( (p1[0] == '?' || p2[0] == '?' || p1[0] == p2[0] ) && WildcardOverlap(p1+1,p2+1)) + return 1; + + /* else, no match, return false */ + return 0; +} +#endif + +int ddsi2_patmatch (const char *pat, const char *str) +{ + while (*pat) + { + if (*pat == '?') + { + /* any character will do */ + if (*str++ == 0) + { + return 0; + } + pat++; + } + else if (*pat == '*') + { + /* collapse a sequence of wildcards, requiring as many + characters in str as there are ?s in the sequence */ + while (*pat == '*' || *pat == '?') + { + if (*pat == '?' && *str++ == 0) + { + return 0; + } + pat++; + } + /* try matching on all positions where str matches pat */ + while (*str) + { + if (*str == *pat && ddsi2_patmatch (pat+1, str+1)) + { + return 1; + } + str++; + } + return *pat == 0; + } + else + { + /* only an exact match */ + if (*str++ != *pat++) + { + return 0; + } + } + } + return *str == 0; +} + + +static const uint32_t crc32_table[] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, + 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, + 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, + 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, + 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, + 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, + 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, + 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, + 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, + 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, + 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, + 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, + 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, + 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, + 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, + 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, + 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4 +}; + +uint32_t crc32_calc (const void *buf, uint32_t length) +{ + const uint8_t *vptr = buf; + uint32_t i, reg = 0; + uint8_t top; + for (i = 0; i < length; i++) + { + top = (uint8_t) (reg >> 24); + top ^= *vptr; + reg = (reg << 8) ^ crc32_table[top]; + vptr++; + } + return reg; +} diff --git a/src/core/ddsi/src/q_nwif.c b/src/core/ddsi/src/q_nwif.c new file mode 100644 index 0000000..f437fbc --- /dev/null +++ b/src/core/ddsi/src/q_nwif.c @@ -0,0 +1,1160 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include +#include + +#include "os/os.h" + +#ifndef _WIN32 +#include +#endif + +#include "ddsi/q_log.h" +#include "ddsi/q_nwif.h" +#include "ddsi/q_globals.h" +#include "ddsi/q_config.h" +#include "ddsi/q_unused.h" +#include "ddsi/q_md5.h" +#include "ddsi/q_misc.h" +#include "ddsi/q_addrset.h" /* unspec locator */ +#include "ddsi/q_feature_check.h" +#include "util/ut_avl.h" + + +struct nn_group_membership_node { + ut_avlNode_t avlnode; + os_socket sock; + os_sockaddr_storage srcip; + os_sockaddr_storage mcip; + unsigned count; +}; + +struct nn_group_membership { + os_mutex lock; + ut_avlTree_t mships; +}; + +static int sockaddr_compare_no_port (const os_sockaddr_storage *as, const os_sockaddr_storage *bs) +{ + if (as->ss_family != bs->ss_family) + return (as->ss_family < bs->ss_family) ? -1 : 1; + else if (as->ss_family == 0) + return 0; /* unspec address */ + else if (as->ss_family == AF_INET) + { + const os_sockaddr_in *a = (const os_sockaddr_in *) as; + const os_sockaddr_in *b = (const os_sockaddr_in *) bs; + if (a->sin_addr.s_addr != b->sin_addr.s_addr) + return (a->sin_addr.s_addr < b->sin_addr.s_addr) ? -1 : 1; + else + return 0; + } +#if OS_SOCKET_HAS_IPV6 + else if (as->ss_family == AF_INET6) + { + const os_sockaddr_in6 *a = (const os_sockaddr_in6 *) as; + const os_sockaddr_in6 *b = (const os_sockaddr_in6 *) bs; + int c; + if ((c = memcmp (&a->sin6_addr, &b->sin6_addr, 16)) != 0) + return c; + else + return 0; + } +#endif + else + { + assert (0); + return 0; + } +} + +static int cmp_group_membership (const void *va, const void *vb) +{ + const struct nn_group_membership_node *a = va; + const struct nn_group_membership_node *b = vb; + int c; + if (a->sock < b->sock) + return -1; + else if (a->sock > b->sock) + return 1; + else if ((c = sockaddr_compare_no_port (&a->srcip, &b->srcip)) != 0) + return c; + else if ((c = sockaddr_compare_no_port (&a->mcip, &b->mcip)) != 0) + return c; + else + return 0; +} + +static ut_avlTreedef_t mship_td = UT_AVL_TREEDEF_INITIALIZER(offsetof (struct nn_group_membership_node, avlnode), 0, cmp_group_membership, 0); + +struct nn_group_membership *new_group_membership (void) +{ + struct nn_group_membership *mship = os_malloc (sizeof (*mship)); + os_mutexInit (&mship->lock); + ut_avlInit (&mship_td, &mship->mships); + return mship; +} + +void free_group_membership (struct nn_group_membership *mship) +{ + ut_avlFree (&mship_td, &mship->mships, os_free); + os_mutexDestroy (&mship->lock); + os_free (mship); +} + +static int reg_group_membership (struct nn_group_membership *mship, os_socket sock, const os_sockaddr_storage *srcip, const os_sockaddr_storage *mcip) +{ + struct nn_group_membership_node key, *n; + ut_avlIPath_t ip; + int isnew; + key.sock = sock; + if (srcip) + key.srcip = *srcip; + else + memset (&key.srcip, 0, sizeof (key.srcip)); + key.mcip = *mcip; + if ((n = ut_avlLookupIPath (&mship_td, &mship->mships, &key, &ip)) != NULL) { + isnew = 0; + n->count++; + } else { + isnew = 1; + n = os_malloc (sizeof (*n)); + n->sock = sock; + n->srcip = key.srcip; + n->mcip = key.mcip; + n->count = 1; + ut_avlInsertIPath (&mship_td, &mship->mships, n, &ip); + } + return isnew; +} + +static int unreg_group_membership (struct nn_group_membership *mship, os_socket sock, const os_sockaddr_storage *srcip, const os_sockaddr_storage *mcip) +{ + struct nn_group_membership_node key, *n; + ut_avlDPath_t dp; + int mustdel; + key.sock = sock; + if (srcip) + key.srcip = *srcip; + else + memset (&key.srcip, 0, sizeof (key.srcip)); + key.mcip = *mcip; + n = ut_avlLookupDPath (&mship_td, &mship->mships, &key, &dp); + assert (n != NULL); + assert (n->count > 0); + if (--n->count > 0) + mustdel = 0; + else + { + mustdel = 1; + ut_avlDeleteDPath (&mship_td, &mship->mships, n, &dp); + os_free (n); + } + return mustdel; +} + +void nn_loc_to_address (os_sockaddr_storage *dst, const nn_locator_t *src) +{ + memset (dst, 0, sizeof (*dst)); + switch (src->kind) + { + case NN_LOCATOR_KIND_INVALID: +#if OS_SOCKET_HAS_IPV6 + dst->ss_family = config.useIpv6 ? AF_INET6 : AF_INET; +#else + dst->ss_family = AF_INET; +#endif + break; + case NN_LOCATOR_KIND_UDPv4: + case NN_LOCATOR_KIND_TCPv4: + { + os_sockaddr_in *x = (os_sockaddr_in *) dst; + x->sin_family = AF_INET; + x->sin_port = htons ((unsigned short) src->port); + memcpy (&x->sin_addr.s_addr, src->address + 12, 4); + break; + } +#if OS_SOCKET_HAS_IPV6 + case NN_LOCATOR_KIND_UDPv6: + case NN_LOCATOR_KIND_TCPv6: + { + os_sockaddr_in6 *x = (os_sockaddr_in6 *) dst; + x->sin6_family = AF_INET6; + x->sin6_port = htons ((unsigned short) src->port); + memcpy (&x->sin6_addr.s6_addr, src->address, 16); + if (IN6_IS_ADDR_LINKLOCAL (&x->sin6_addr)) + { + x->sin6_scope_id = gv.interfaceNo; + } + break; + } +#endif + case NN_LOCATOR_KIND_UDPv4MCGEN: + NN_ERROR ("nn_address_to_loc: kind %x unsupported\n", src->kind); + break; + default: + break; + } +} + +void nn_address_to_loc (nn_locator_t *dst, const os_sockaddr_storage *src, int32_t kind) +{ + dst->kind = kind; + switch (src->ss_family) + { + case AF_INET: + { + const os_sockaddr_in *x = (const os_sockaddr_in *) src; + assert (kind == NN_LOCATOR_KIND_UDPv4 || kind == NN_LOCATOR_KIND_TCPv4); + if (x->sin_addr.s_addr == htonl (INADDR_ANY)) + set_unspec_locator (dst); + else + { + dst->port = ntohs (x->sin_port); + memset (dst->address, 0, 12); + memcpy (dst->address + 12, &x->sin_addr.s_addr, 4); + } + break; + } +#if OS_SOCKET_HAS_IPV6 + case AF_INET6: + { + const os_sockaddr_in6 *x = (const os_sockaddr_in6 *) src; + assert (kind == NN_LOCATOR_KIND_UDPv6 || kind == NN_LOCATOR_KIND_TCPv6); + if (IN6_IS_ADDR_UNSPECIFIED (&x->sin6_addr)) + set_unspec_locator (dst); + else + { + dst->port = ntohs (x->sin6_port); + memcpy (dst->address, &x->sin6_addr.s6_addr, 16); + } + break; + } +#endif + default: + NN_FATAL ("nn_address_to_loc: family %d unsupported\n", (int) src->ss_family); + } +} + +void print_sockerror (const char *msg) +{ + int err = os_getErrno (); + NN_ERROR ("SOCKET %s errno %d\n", msg, err); +} + +unsigned short sockaddr_get_port (const os_sockaddr_storage *addr) +{ + if (addr->ss_family == AF_INET) + return ntohs (((os_sockaddr_in *) addr)->sin_port); +#if OS_SOCKET_HAS_IPV6 + else + return ntohs (((os_sockaddr_in6 *) addr)->sin6_port); +#endif +} + +void sockaddr_set_port (os_sockaddr_storage *addr, unsigned short port) +{ + if (addr->ss_family == AF_INET) + ((os_sockaddr_in *) addr)->sin_port = htons (port); +#if OS_SOCKET_HAS_IPV6 + else + ((os_sockaddr_in6 *) addr)->sin6_port = htons (port); +#endif +} + +char *sockaddr_to_string_with_port (char addrbuf[INET6_ADDRSTRLEN_EXTENDED], const os_sockaddr_storage *src) +{ + size_t pos; + int n; + switch (src->ss_family) + { + case AF_INET: + os_sockaddrAddressToString ((const os_sockaddr *) src, addrbuf, INET6_ADDRSTRLEN); + pos = strlen (addrbuf); + assert(pos <= INET6_ADDRSTRLEN_EXTENDED); + n = snprintf (addrbuf + pos, INET6_ADDRSTRLEN_EXTENDED - pos, ":%u", ntohs (((os_sockaddr_in *) src)->sin_port)); + assert (n < INET6_ADDRSTRLEN_EXTENDED); + (void)n; + break; +#if OS_SOCKET_HAS_IPV6 + case AF_INET6: + addrbuf[0] = '['; + os_sockaddrAddressToString ((const os_sockaddr *) src, addrbuf + 1, INET6_ADDRSTRLEN); + pos = strlen (addrbuf); + assert(pos <= INET6_ADDRSTRLEN_EXTENDED); + n = snprintf (addrbuf + pos, INET6_ADDRSTRLEN_EXTENDED - pos, "]:%u", ntohs (((os_sockaddr_in6 *) src)->sin6_port)); + assert (n < INET6_ADDRSTRLEN_EXTENDED); + (void)n; + break; +#endif + default: + NN_WARNING ("sockaddr_to_string_with_port: unknown address family\n"); + strcpy (addrbuf, "???"); + break; + } + return addrbuf; +} + +char *sockaddr_to_string_no_port (char addrbuf[INET6_ADDRSTRLEN_EXTENDED], const os_sockaddr_storage *src) +{ + return os_sockaddrAddressToString ((const os_sockaddr *) src, addrbuf, INET6_ADDRSTRLEN); +} + +char *locator_to_string_with_port (char addrbuf[INET6_ADDRSTRLEN_EXTENDED], const nn_locator_t *loc) +{ + os_sockaddr_storage addr; + nn_locator_t tmploc = *loc; + if (tmploc.kind == NN_LOCATOR_KIND_UDPv4MCGEN) + { + nn_udpv4mcgen_address_t *x = (nn_udpv4mcgen_address_t *) &tmploc.address; + memmove(tmploc.address + 12, &x->ipv4, 4); + memset(tmploc.address, 0, 12); + tmploc.kind = NN_LOCATOR_KIND_UDPv4; + } + nn_loc_to_address (&addr, &tmploc); + return sockaddr_to_string_with_port (addrbuf, &addr); +} + +char *locator_to_string_no_port (char addrbuf[INET6_ADDRSTRLEN_EXTENDED], const nn_locator_t *loc) +{ + os_sockaddr_storage addr; + nn_locator_t tmploc = *loc; + if (tmploc.kind == NN_LOCATOR_KIND_UDPv4MCGEN) + { + nn_udpv4mcgen_address_t *x = (nn_udpv4mcgen_address_t *) &tmploc.address; + memmove(tmploc.address + 12, &x->ipv4, 4); + memset(tmploc.address, 0, 12); + tmploc.kind = NN_LOCATOR_KIND_UDPv4; + } + nn_loc_to_address (&addr, &tmploc); + return sockaddr_to_string_no_port (addrbuf, &addr); +} + +unsigned sockaddr_to_hopefully_unique_uint32 (const os_sockaddr_storage *src) +{ + switch (src->ss_family) + { + case AF_INET: + return (unsigned) ((const os_sockaddr_in *) src)->sin_addr.s_addr; +#if OS_SOCKET_HAS_IPV6 + case AF_INET6: + { + unsigned id; + md5_state_t st; + md5_byte_t digest[16]; + md5_init (&st); + md5_append (&st, (const md5_byte_t *) ((const os_sockaddr_in6 *) src)->sin6_addr.s6_addr, 16); + md5_finish (&st, digest); + memcpy (&id, digest, sizeof (id)); + return id; + } +#endif + default: + NN_FATAL ("sockaddr_to_hopefully_unique_uint32: unknown address family\n"); + return 0; + } +} + +unsigned short get_socket_port (os_socket socket) +{ + os_sockaddr_storage addr; + socklen_t addrlen = sizeof (addr); + if (getsockname (socket, (os_sockaddr *) &addr, &addrlen) < 0) + { + print_sockerror ("getsockname"); + return 0; + } + switch (addr.ss_family) + { + case AF_INET: + return ntohs (((os_sockaddr_in *) &addr)->sin_port); +#if OS_SOCKET_HAS_IPV6 + case AF_INET6: + return ntohs (((os_sockaddr_in6 *) &addr)->sin6_port); +#endif + default: + abort (); + return 0; + } +} + +#ifdef DDSI_INCLUDE_NETWORK_CHANNELS +void set_socket_diffserv (os_socket sock, int diffserv) +{ + if (os_sockSetsockopt (sock, IPPROTO_IP, IP_TOS, (char*) &diffserv, sizeof (diffserv)) != os_resultSuccess) + { + print_sockerror ("IP_TOS"); + } +} +#endif + +#ifdef SO_NOSIGPIPE +static void set_socket_nosigpipe (os_socket sock) +{ + int val = 1; + if (os_sockSetsockopt (sock, SOL_SOCKET, SO_NOSIGPIPE, (char*) &val, sizeof (val)) != os_resultSuccess) + { + print_sockerror ("SO_NOSIGPIPE"); + } +} +#endif + +#ifdef TCP_NODELAY +static void set_socket_nodelay (os_socket sock) +{ + int val = 1; + if (os_sockSetsockopt (sock, IPPROTO_TCP, TCP_NODELAY, (char*) &val, sizeof (val)) != os_resultSuccess) + { + print_sockerror ("TCP_NODELAY"); + } +} +#endif + +static int set_rcvbuf (os_socket socket) +{ + uint32_t ReceiveBufferSize; + uint32_t optlen = (uint32_t) sizeof (ReceiveBufferSize); + uint32_t socket_min_rcvbuf_size; + if (config.socket_min_rcvbuf_size.isdefault) + socket_min_rcvbuf_size = 1048576; + else + socket_min_rcvbuf_size = config.socket_min_rcvbuf_size.value; + if (os_sockGetsockopt (socket, SOL_SOCKET, SO_RCVBUF, (char *) &ReceiveBufferSize, &optlen) != os_resultSuccess) + { + print_sockerror ("get SO_RCVBUF"); + return -2; + } + if (ReceiveBufferSize < socket_min_rcvbuf_size) + { + /* make sure the receive buffersize is at least the minimum required */ + ReceiveBufferSize = socket_min_rcvbuf_size; + (void) os_sockSetsockopt (socket, SOL_SOCKET, SO_RCVBUF, (const char *) &ReceiveBufferSize, sizeof (ReceiveBufferSize)); + + /* We don't check the return code from setsockopt, because some O/Ss tend + to silently cap the buffer size. The only way to make sure is to read + the option value back and check it is now set correctly. */ + if (os_sockGetsockopt (socket, SOL_SOCKET, SO_RCVBUF, (char *) &ReceiveBufferSize, &optlen) != os_resultSuccess) + { + print_sockerror ("get SO_RCVBUF"); + return -2; + } + if (ReceiveBufferSize < socket_min_rcvbuf_size) + { + /* NN_ERROR does more than just nn_log(LC_ERROR), hence the duplication */ + if (config.socket_min_rcvbuf_size.isdefault) + nn_log (LC_CONFIG, "failed to increase socket receive buffer size to %u bytes, continuing with %u bytes\n", socket_min_rcvbuf_size, ReceiveBufferSize); + else + NN_ERROR ("failed to increase socket receive buffer size to %u bytes, continuing with %u bytes\n", socket_min_rcvbuf_size, ReceiveBufferSize); + } + else + { + nn_log (LC_CONFIG, "socket receive buffer size set to %u bytes\n", ReceiveBufferSize); + } + } + return 0; +} + +static int set_sndbuf (os_socket socket) +{ + unsigned SendBufferSize; + uint32_t optlen = (uint32_t) sizeof(SendBufferSize); + if (os_sockGetsockopt(socket, SOL_SOCKET, SO_SNDBUF,(char *)&SendBufferSize, &optlen) != os_resultSuccess) + { + print_sockerror ("get SO_SNDBUF"); + return -2; + } + if (SendBufferSize < config.socket_min_sndbuf_size ) + { + /* make sure the send buffersize is at least the minimum required */ + SendBufferSize = config.socket_min_sndbuf_size; + if (os_sockSetsockopt (socket, SOL_SOCKET, SO_SNDBUF, (const char *)&SendBufferSize, sizeof (SendBufferSize)) != os_resultSuccess) + { + print_sockerror ("SO_SNDBUF"); + return -2; + } + } + return 0; +} + +static int maybe_set_dont_route (os_socket socket) +{ + if (config.dontRoute) + { +#if OS_SOCKET_HAS_IPV6 + if (config.useIpv6) + { + unsigned ipv6Flag = 1; + if (os_sockSetsockopt (socket, IPPROTO_IPV6, IPV6_UNICAST_HOPS, &ipv6Flag, sizeof (ipv6Flag))) + { + print_sockerror ("IPV6_UNICAST_HOPS"); + return -2; + } + } + else +#endif + { + int one = 1; + if (os_sockSetsockopt (socket, SOL_SOCKET, SO_DONTROUTE, (char *) &one, sizeof (one)) != os_resultSuccess) + { + print_sockerror ("SO_DONTROUTE"); + return -2; + } + } + } + return 0; +} + +static int set_reuse_options (os_socket socket) +{ + /* Set REUSEADDR (if available on platform) for + multicast sockets, leave unicast sockets alone. */ + int one = 1; + + if (os_sockSetsockopt (socket, SOL_SOCKET, SO_REUSEADDR, (char *) &one, sizeof (one)) != os_resultSuccess) + { + print_sockerror ("SO_REUSEADDR"); + return -2; + } + return 0; +} + +static int interface_in_recvips_p (const struct nn_interface *interf) +{ + struct ospl_in_addr_node *nodeaddr; + for (nodeaddr = gv.recvips; nodeaddr; nodeaddr = nodeaddr->next) + { + if (os_sockaddrIPAddressEqual ((const os_sockaddr *) &nodeaddr->addr, (const os_sockaddr *) &interf->addr)) + return 1; + } + return 0; +} + +static int bind_socket (os_socket socket, unsigned short port) +{ + int rc; + +#if OS_SOCKET_HAS_IPV6 + if (config.useIpv6) + { + os_sockaddr_in6 socketname; + memset (&socketname, 0, sizeof (socketname)); + socketname.sin6_family = AF_INET6; + socketname.sin6_port = htons (port); + socketname.sin6_addr = os_in6addr_any; + if (IN6_IS_ADDR_LINKLOCAL (&socketname.sin6_addr)) { + socketname.sin6_scope_id = gv.interfaceNo; + } + rc = os_sockBind (socket, (struct sockaddr *) &socketname, sizeof (socketname)); + } + else +#endif + { + struct sockaddr_in socketname; + socketname.sin_family = AF_INET; + socketname.sin_port = htons (port); + socketname.sin_addr.s_addr = htonl (INADDR_ANY); + rc = os_sockBind (socket, (struct sockaddr *) &socketname, sizeof (socketname)); + } + if (rc != os_resultSuccess) + { + if (os_getErrno () != os_sockEADDRINUSE) + { + print_sockerror ("bind"); + } + } + return (rc == os_resultSuccess) ? 0 : -1; +} + +#if OS_SOCKET_HAS_IPV6 +static int set_mc_options_transmit_ipv6 (os_socket socket) +{ + unsigned interfaceNo = gv.interfaceNo; + unsigned ttl = (unsigned) config.multicast_ttl; + unsigned loop; + if (os_sockSetsockopt (socket, IPPROTO_IPV6, IPV6_MULTICAST_IF, &interfaceNo, sizeof (interfaceNo)) != os_resultSuccess) + { + print_sockerror ("IPV6_MULTICAST_IF"); + return -2; + } + if (os_sockSetsockopt (socket, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *) &ttl, sizeof (ttl)) != os_resultSuccess) + { + print_sockerror ("IPV6_MULTICAST_HOPS"); + return -2; + } + loop = (unsigned) !!config.enableMulticastLoopback; + if (os_sockSetsockopt (socket, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, &loop, sizeof (loop)) != os_resultSuccess) + { + print_sockerror ("IPV6_MULTICAST_LOOP"); + return -2; + } + return 0; +} +#endif + +static int set_mc_options_transmit_ipv4 (os_socket socket) +{ + unsigned char ttl = (unsigned char) config.multicast_ttl; + unsigned char loop; + os_result ret; + +#if defined __linux || defined __APPLE__ + if (config.use_multicast_if_mreqn) + { + struct ip_mreqn mreqn; + memset (&mreqn, 0, sizeof (mreqn)); + /* looks like imr_multiaddr is not relevant, not sure about imr_address */ + mreqn.imr_multiaddr.s_addr = htonl (INADDR_ANY); + if (config.use_multicast_if_mreqn > 1) + mreqn.imr_address.s_addr = ((os_sockaddr_in *) &gv.ownip)->sin_addr.s_addr; + else + mreqn.imr_address.s_addr = htonl (INADDR_ANY); + mreqn.imr_ifindex = (int) gv.interfaceNo; + ret = os_sockSetsockopt (socket, IPPROTO_IP, IP_MULTICAST_IF, &mreqn, sizeof (mreqn)); + } + else +#endif + { + ret = os_sockSetsockopt (socket, IPPROTO_IP, IP_MULTICAST_IF, (char *) &((os_sockaddr_in *) &gv.ownip)->sin_addr, sizeof (((os_sockaddr_in *) &gv.ownip)->sin_addr)); + } + if (ret != os_resultSuccess) + { + print_sockerror ("IP_MULTICAST_IF"); + return -2; + } + if (os_sockSetsockopt (socket, IPPROTO_IP, IP_MULTICAST_TTL, (char *) &ttl, sizeof (ttl)) != os_resultSuccess) + { + print_sockerror ("IP_MULICAST_TTL"); + return -2; + } + loop = (unsigned char) config.enableMulticastLoopback; + + if (os_sockSetsockopt (socket, IPPROTO_IP, IP_MULTICAST_LOOP, &loop, sizeof (loop)) != os_resultSuccess) + { + print_sockerror ("IP_MULTICAST_LOOP"); + return -2; + } + return 0; +} + +static int set_mc_options_transmit (os_socket socket) +{ +#if OS_SOCKET_HAS_IPV6 + if (config.useIpv6) + { + return set_mc_options_transmit_ipv6 (socket); + } + else +#endif + { + return set_mc_options_transmit_ipv4 (socket); + } +} + +static int joinleave_asm_mcgroup (os_socket socket, int join, const os_sockaddr_storage *mcip, const struct nn_interface *interf) +{ + int rc; +#if OS_SOCKET_HAS_IPV6 + if (config.useIpv6) + { + os_ipv6_mreq ipv6mreq; + memset (&ipv6mreq, 0, sizeof (ipv6mreq)); + memcpy (&ipv6mreq.ipv6mr_multiaddr, &((os_sockaddr_in6 *) mcip)->sin6_addr, sizeof (ipv6mreq.ipv6mr_multiaddr)); + ipv6mreq.ipv6mr_interface = interf ? interf->if_index : 0; + rc = os_sockSetsockopt (socket, IPPROTO_IPV6, join ? IPV6_JOIN_GROUP : IPV6_LEAVE_GROUP, &ipv6mreq, sizeof (ipv6mreq)); + } + else +#endif + { + struct ip_mreq mreq; + mreq.imr_multiaddr = ((os_sockaddr_in *) mcip)->sin_addr; + if (interf) + mreq.imr_interface = ((os_sockaddr_in *) &interf->addr)->sin_addr; + else + mreq.imr_interface.s_addr = htonl (INADDR_ANY); + rc = os_sockSetsockopt (socket, IPPROTO_IP, join ? IP_ADD_MEMBERSHIP : IP_DROP_MEMBERSHIP, (char *) &mreq, sizeof (mreq)); + } + return (rc == -1) ? os_getErrno() : 0; +} + +#ifdef DDSI_INCLUDE_SSM +static int joinleave_ssm_mcgroup (os_socket socket, int join, const os_sockaddr_storage *srcip, const os_sockaddr_storage *mcip, const struct nn_interface *interf) +{ + int rc; +#if OS_SOCKET_HAS_IPV6 + if (config.useIpv6) + { + struct group_source_req gsr; + memset (&gsr, 0, sizeof (gsr)); + gsr.gsr_interface = interf ? interf->if_index : 0; + memcpy (&gsr.gsr_group, mcip, sizeof (gsr.gsr_group)); + memcpy (&gsr.gsr_source, srcip, sizeof (gsr.gsr_source)); + rc = os_sockSetsockopt (socket, IPPROTO_IPV6, join ? MCAST_JOIN_SOURCE_GROUP : MCAST_LEAVE_SOURCE_GROUP, &gsr, sizeof (gsr)); + } + else +#endif + { + struct ip_mreq_source mreq; + memset (&mreq, 0, sizeof (mreq)); + mreq.imr_sourceaddr = ((os_sockaddr_in *) srcip)->sin_addr; + mreq.imr_multiaddr = ((os_sockaddr_in *) mcip)->sin_addr; + if (interf) + mreq.imr_interface = ((os_sockaddr_in *) &interf->addr)->sin_addr; + else + mreq.imr_interface.s_addr = INADDR_ANY; + rc = os_sockSetsockopt (socket, IPPROTO_IP, join ? IP_ADD_SOURCE_MEMBERSHIP : IP_DROP_SOURCE_MEMBERSHIP, &mreq, sizeof (mreq)); + } + return (rc == -1) ? os_getErrno() : 0; +} +#endif + +static char *make_joinleave_msg (char *buf, size_t bufsz, os_socket socket, int join, const os_sockaddr_storage *srcip, const os_sockaddr_storage *mcip, const struct nn_interface *interf, int err) +{ + char mcstr[INET6_ADDRSTRLEN_EXTENDED], srcstr[INET6_ADDRSTRLEN_EXTENDED], interfstr[INET6_ADDRSTRLEN_EXTENDED]; + int n; +#ifdef DDSI_INCLUDE_SSM + if (srcip) + sockaddr_to_string_no_port(srcstr, srcip); + else + strcpy (srcstr, "*"); +#else + OS_UNUSED_ARG (srcip); + strcpy (srcstr, "*"); +#endif + sockaddr_to_string_no_port (mcstr, mcip); + if (interf) + sockaddr_to_string_no_port(interfstr, &interf->addr); + else + (void) snprintf (interfstr, sizeof (interfstr), "(default)"); + n = err ? snprintf (buf, bufsz, "error %d in ", err) : 0; + if ((size_t) n < bufsz) + (void) snprintf (buf + n, bufsz - (size_t) n, "%s socket %lu for (%s, %s) interface %s", join ? "join" : "leave", (unsigned long) socket, mcstr, srcstr, interfstr); + return buf; +} + +static int joinleave_mcgroup (os_socket socket, int join, const os_sockaddr_storage *srcip, const os_sockaddr_storage *mcip, const struct nn_interface *interf) +{ + char buf[256]; + int err; + nn_log (LC_DISCOVERY, "%s\n", make_joinleave_msg (buf, sizeof(buf), socket, join, srcip, mcip, interf, 0)); +#ifdef DDSI_INCLUDE_SSM + if (srcip) + err = joinleave_ssm_mcgroup(socket, join, srcip, mcip, interf); + else + err = joinleave_asm_mcgroup(socket, join, mcip, interf); +#else + assert (srcip == NULL); + err = joinleave_asm_mcgroup(socket, join, mcip, interf); +#endif + if (err) + NN_WARNING ("%s\n", make_joinleave_msg (buf, sizeof(buf), socket, join, srcip, mcip, interf, err)); + return err ? -1 : 0; +} + +static int joinleave_mcgroups (os_socket socket, int join, const os_sockaddr_storage *srcip, const os_sockaddr_storage *mcip) +{ + int rc; + switch (gv.recvips_mode) + { + case RECVIPS_MODE_NONE: + break; + case RECVIPS_MODE_ANY: + /* User has specified to use the OS default interface */ + if ((rc = joinleave_mcgroup (socket, join, srcip, mcip, NULL)) < 0) + return rc; + break; + case RECVIPS_MODE_PREFERRED: + if (gv.interfaces[gv.selected_interface].mc_capable) + return joinleave_mcgroup (socket, join, srcip, mcip, &gv.interfaces[gv.selected_interface]); + return 0; + case RECVIPS_MODE_ALL: + case RECVIPS_MODE_SOME: + { + int i, fails = 0, oks = 0; + for (i = 0; i < gv.n_interfaces; i++) + { + if (gv.interfaces[i].mc_capable) + { + if (gv.recvips_mode == RECVIPS_MODE_ALL || interface_in_recvips_p (&gv.interfaces[i])) + { + if ((rc = joinleave_mcgroup (socket, join, srcip, mcip, &gv.interfaces[i])) < 0) + fails++; + else + oks++; + } + } + } + if (fails > 0) + { + if (oks > 0) + TRACE (("multicast join failed for some but not all interfaces, proceeding\n")); + else + return -2; + } + } + break; + } + return 0; +} + +int join_mcgroups (struct nn_group_membership *mship, os_socket socket, const os_sockaddr_storage *srcip, const os_sockaddr_storage *mcip) +{ + int ret; + os_mutexLock (&mship->lock); + if (!reg_group_membership (mship, socket, srcip, mcip)) + { + char buf[256]; + TRACE (("%s: already joined\n", make_joinleave_msg (buf, sizeof(buf), socket, 1, srcip, mcip, NULL, 0))); + ret = 0; + } + else + { + ret = joinleave_mcgroups (socket, 1, srcip, mcip); + } + os_mutexUnlock (&mship->lock); + return ret; +} + +int leave_mcgroups (struct nn_group_membership *mship, os_socket socket, const os_sockaddr_storage *srcip, const os_sockaddr_storage *mcip) +{ + int ret; + os_mutexLock (&mship->lock); + if (!unreg_group_membership (mship, socket, srcip, mcip)) + { + char buf[256]; + TRACE (("%s: not leaving yet\n", make_joinleave_msg (buf, sizeof(buf), socket, 0, srcip, mcip, NULL, 0))); + ret = 0; + } + else + { + ret = joinleave_mcgroups (socket, 0, srcip, mcip); + } + os_mutexUnlock (&mship->lock); + return ret; +} + +int make_socket +( + os_socket * sock, + unsigned short port, + bool stream, + bool reuse +) +{ + int rc = -2; + +#if OS_SOCKET_HAS_IPV6 + if (config.useIpv6) + { + *sock = os_sockNew (AF_INET6, stream ? SOCK_STREAM : SOCK_DGRAM); + } + else +#endif + { + *sock = os_sockNew (AF_INET, stream ? SOCK_STREAM : SOCK_DGRAM); + } + + if (! Q_VALID_SOCKET (*sock)) + { + print_sockerror ("socket"); + return rc; + } + + if (port && reuse && ((rc = set_reuse_options (*sock)) < 0)) + { + goto fail; + } + + if + ( + (rc = set_rcvbuf (*sock) < 0) || + (rc = set_sndbuf (*sock) < 0) || + ((rc = maybe_set_dont_route (*sock)) < 0) || + ((rc = bind_socket (*sock, port)) < 0) + ) + { + goto fail; + } + + if (! stream) + { + if ((rc = set_mc_options_transmit (*sock)) < 0) + { + goto fail; + } + } + + if (stream) + { +#ifdef SO_NOSIGPIPE + set_socket_nosigpipe (*sock); +#endif +#ifdef TCP_NODELAY + if (config.tcp_nodelay) + { + set_socket_nodelay (*sock); + } +#endif + } + + return 0; + +fail: + + os_sockFree (*sock); + *sock = Q_INVALID_SOCKET; + return rc; +} + +static int multicast_override(const char *ifname) +{ + char *copy = os_strdup (config.assumeMulticastCapable), *cursor = copy, *tok; + int match = 0; + if (copy != NULL) + { + while ((tok = os_strsep (&cursor, ",")) != NULL) + { + if (ddsi2_patmatch (tok, ifname)) + match = 1; + } + } + os_free (copy); + return match; +} + +int find_own_ip (const char *requested_address) +{ + const char *sep = " "; + char last_if_name[80] = ""; + int quality = -1; + os_result res; + int i; + unsigned int nif; + os_ifAttributes *ifs; + int maxq_list[MAX_INTERFACES]; + int maxq_count = 0; + size_t maxq_strlen = 0; + int selected_idx = -1; + char addrbuf[INET6_ADDRSTRLEN_EXTENDED]; + + ifs = os_malloc (MAX_INTERFACES * sizeof (*ifs)); + + nn_log (LC_CONFIG, "interfaces:"); + + if (config.useIpv6) + res = os_sockQueryIPv6Interfaces (ifs, MAX_INTERFACES, &nif); + else + res = os_sockQueryInterfaces (ifs, MAX_INTERFACES, &nif); + if (res != os_resultSuccess) + { + NN_ERROR ("os_sockQueryInterfaces: %d\n", (int) res); + os_free (ifs); + return 0; + } + + gv.n_interfaces = 0; + last_if_name[0] = 0; + for (i = 0; i < (int) nif; i++, sep = ", ") + { + os_sockaddr_storage tmpip, tmpmask; + char if_name[sizeof (last_if_name)]; + int q = 0; + + strncpy (if_name, ifs[i].name, sizeof (if_name) - 1); + if_name[sizeof (if_name) - 1] = 0; + + if (strcmp (if_name, last_if_name)) + nn_log (LC_CONFIG, "%s%s", sep, if_name); + strcpy (last_if_name, if_name); + + /* interface must be up */ + if ((ifs[i].flags & IFF_UP) == 0) + { + nn_log (LC_CONFIG, " (interface down)"); + continue; + } + + tmpip = ifs[i].address; + tmpmask = ifs[i].network_mask; + sockaddr_to_string_no_port (addrbuf, &tmpip); + nn_log (LC_CONFIG, " %s(", addrbuf); + + if (!(ifs[i].flags & IFF_MULTICAST) && multicast_override (if_name)) + { + nn_log (LC_CONFIG, "assume-mc:"); + ifs[i].flags |= IFF_MULTICAST; + } + + if (ifs[i].flags & IFF_LOOPBACK) + { + /* Loopback device has the lowest priority of every interface + available, because the other interfaces at least in principle + allow communicating with other machines. */ + q += 0; +#if OS_SOCKET_HAS_IPV6 + if (!(tmpip.ss_family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL (&((os_sockaddr_in6 *) &tmpip)->sin6_addr))) + q += 1; +#endif + } + else + { +#if OS_SOCKET_HAS_IPV6 + /* We accept link-local IPv6 addresses, but an interface with a + link-local address will end up lower in the ordering than one + with a global address. When forced to use a link-local + address, we restrict ourselves to operating on that one + interface only and assume any advertised (incoming) link-local + address belongs to that interface. FIXME: this is wrong, and + should be changed to tag addresses with the interface over + which it was received. But that means proper multi-homing + support and has quite an impact in various places, not least of + which is the abstraction layer. */ + if (!(tmpip.ss_family == AF_INET6 && IN6_IS_ADDR_LINKLOCAL (&((os_sockaddr_in6 *) &tmpip)->sin6_addr))) + q += 5; +#endif + + /* We strongly prefer a multicast capable interface, if that's + not available anything that's not point-to-point, or else we + hope IP routing will take care of the issues. */ + if (ifs[i].flags & IFF_MULTICAST) + q += 4; + else if (!(ifs[i].flags & IFF_POINTOPOINT)) + q += 3; + else + q += 2; + } + + nn_log (LC_CONFIG, "q%d)", q); + if (q == quality) { + maxq_list[maxq_count] = gv.n_interfaces; + maxq_strlen += 2 + strlen (if_name); + maxq_count++; + } else if (q > quality) { + maxq_list[0] = gv.n_interfaces; + maxq_strlen += 2 + strlen (if_name); + maxq_count = 1; + quality = q; + } + + gv.interfaces[gv.n_interfaces].addr = tmpip; + gv.interfaces[gv.n_interfaces].netmask = tmpmask; + gv.interfaces[gv.n_interfaces].mc_capable = ((ifs[i].flags & IFF_MULTICAST) != 0); + gv.interfaces[gv.n_interfaces].point_to_point = ((ifs[i].flags & IFF_POINTOPOINT) != 0); + gv.interfaces[gv.n_interfaces].if_index = ifs[i].interfaceIndexNo; + gv.interfaces[gv.n_interfaces].name = os_strdup (if_name); + gv.n_interfaces++; + } + nn_log (LC_CONFIG, "\n"); + os_free (ifs); + + if (requested_address == NULL) + { + if (maxq_count > 1) + { + const int idx = maxq_list[0]; + char *names; + int p; + sockaddr_to_string_no_port (addrbuf, &gv.interfaces[idx].addr); + names = os_malloc (maxq_strlen + 1); + p = 0; + for (i = 0; i < maxq_count && (size_t) p < maxq_strlen; i++) + p += snprintf (names + p, maxq_strlen - (size_t) p, ", %s", gv.interfaces[maxq_list[i]].name); + NN_WARNING ("using network interface %s (%s) selected arbitrarily from: %s\n", + gv.interfaces[idx].name, addrbuf, names + 2); + os_free (names); + } + + if (maxq_count > 0) + selected_idx = maxq_list[0]; + else + NN_ERROR ("failed to determine default own IP address\n"); + } + else + { + os_sockaddr_storage req; + /* Presumably an interface name */ + for (i = 0; i < gv.n_interfaces; i++) + { + if (strcmp (gv.interfaces[i].name, config.networkAddressString) == 0) + break; + } + if (i < gv.n_interfaces) + ; /* got a match */ + else if (!os_sockaddrStringToAddress (config.networkAddressString, (os_sockaddr *) &req, !config.useIpv6)) + ; /* not good, i = gv.n_interfaces, so error handling will kick in */ + else + { + /* Try an exact match on the address */ + for (i = 0; i < gv.n_interfaces; i++) + if (os_sockaddrIPAddressEqual ((os_sockaddr *) &gv.interfaces[i].addr, (os_sockaddr *) &req)) + break; + if (i == gv.n_interfaces && !config.useIpv6) + { + /* Try matching on network portion only, where the network + portion is based on the netmask of the interface under + consideration */ + for (i = 0; i < gv.n_interfaces; i++) + { + os_sockaddr_storage req1 = req, ip1 = gv.interfaces[i].addr; + assert (req1.ss_family == AF_INET); + assert (ip1.ss_family == AF_INET); + + /* If the host portion of the requested address is non-zero, + skip this interface */ + if (((os_sockaddr_in *) &req1)->sin_addr.s_addr & + ~((os_sockaddr_in *) &gv.interfaces[i].netmask)->sin_addr.s_addr) + continue; + + ((os_sockaddr_in *) &req1)->sin_addr.s_addr &= + ((os_sockaddr_in *) &gv.interfaces[i].netmask)->sin_addr.s_addr; + ((os_sockaddr_in *) &ip1)->sin_addr.s_addr &= + ((os_sockaddr_in *) &gv.interfaces[i].netmask)->sin_addr.s_addr; + if (os_sockaddrIPAddressEqual ((os_sockaddr *) &ip1, (os_sockaddr *) &req1)) + break; + } + } + } + + if (i < gv.n_interfaces) + selected_idx = i; + else + NN_ERROR ("%s: does not match an available interface\n", config.networkAddressString); + } + + if (selected_idx < 0) + return 0; + else + { + gv.ownip = gv.interfaces[selected_idx].addr; + sockaddr_set_port (&gv.ownip, 0); + gv.selected_interface = selected_idx; + gv.interfaceNo = gv.interfaces[selected_idx].if_index; +#if OS_SOCKET_HAS_IPV6 + if (config.useIpv6) + { + assert (gv.ownip.ss_family == AF_INET6); + gv.ipv6_link_local = + IN6_IS_ADDR_LINKLOCAL (&((os_sockaddr_in6 *) &gv.ownip)->sin6_addr) != 0; + } + else + { + gv.ipv6_link_local = 0; + } +#endif + nn_log (LC_CONFIG, "selected interface: %s (index %u)\n", + gv.interfaces[selected_idx].name, gv.interfaceNo); + + return 1; + } +} diff --git a/src/core/ddsi/src/q_pcap.c b/src/core/ddsi/src/q_pcap.c new file mode 100644 index 0000000..25544e3 --- /dev/null +++ b/src/core/ddsi/src/q_pcap.c @@ -0,0 +1,209 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include + +#include "os/os.h" + +#include "ddsi/q_log.h" +#include "ddsi/q_time.h" +#include "ddsi/q_config.h" +#include "ddsi/q_globals.h" +#include "ddsi/q_bswap.h" +#include "ddsi/sysdeps.h" +#include "ddsi/q_pcap.h" + +/* pcap format info taken from http://wiki.wireshark.org/Development/LibpcapFileFormat */ + +#define LINKTYPE_RAW 101 /* Raw IP; the packet begins with an IPv4 or IPv6 header, with the "version" field of the header indicating whether it's an IPv4 or IPv6 header. */ + +typedef struct pcap_hdr_s { + uint32_t magic_number; /* magic number */ + uint16_t version_major; /* major version number */ + uint16_t version_minor; /* minor version number */ + int32_t thiszone; /* GMT to local correction */ + uint32_t sigfigs; /* accuracy of timestamps */ + uint32_t snaplen; /* max length of captured packets, in octets */ + uint32_t network; /* data link type */ +} pcap_hdr_t; + +typedef struct pcaprec_hdr_s { + int32_t ts_sec; /* timestamp seconds (orig: unsigned) */ + int32_t ts_usec; /* timestamp microseconds (orig: unsigned) */ + uint32_t incl_len; /* number of octets of packet saved in file */ + uint32_t orig_len; /* actual length of packet */ +} pcaprec_hdr_t; + +typedef struct ipv4_hdr_s { + unsigned char version_hdrlength; + unsigned char diffserv_congestion; + uint16_t totallength; + uint16_t identification; + uint16_t flags_fragment_offset; + unsigned char ttl; + unsigned char proto; + uint16_t checksum; + uint32_t srcip; + uint32_t dstip; +} ipv4_hdr_t; + +typedef struct udp_hdr_s { + uint16_t srcport; + uint16_t dstport; + uint16_t length; + uint16_t checksum; +} udp_hdr_t; + +static const ipv4_hdr_t ipv4_hdr_template = { + (4 << 4) | 5, /* IPv4, minimum header length */ + 0 | 0, /* no diffserv, congestion */ + 0, /* total length will be overridden */ + 0, /* not fragmenting, so irrelevant */ + 0, /* not fragmenting */ + 0, /* TTL: received has it at 128, sent at 255 */ + 17, /* UDP */ + 0, /* checksum will be overridden */ + 0, /* srcip: set per packet */ + 0 /* dstip: set per packet */ +}; + +#define IPV4_HDR_SIZE 20 +#define UDP_HDR_SIZE 8 + +FILE *new_pcap_file (const char *name) +{ + FILE *fp; + pcap_hdr_t hdr; + + if ((fp = fopen (name, "wb")) == NULL) + { + NN_WARNING ("packet capture disabled: file %s could not be opened for writing\n", name); + return NULL; + } + + hdr.magic_number = 0xa1b2c3d4; + hdr.version_major = 2; + hdr.version_minor = 4; + hdr.thiszone = 0; + hdr.sigfigs = 0; + hdr.snaplen = 65535; + hdr.network = LINKTYPE_RAW; + fwrite (&hdr, sizeof (hdr), 1, fp); + + return fp; +} + +static void write_data (FILE *fp, const struct msghdr *msghdr, size_t sz) +{ + size_t i, n = 0; + for (i = 0; i < (size_t) msghdr->msg_iovlen && n < sz; i++) + { + size_t m1 = msghdr->msg_iov[i].iov_len; + size_t m = (n + m1 <= sz) ? m1 : sz - n; + fwrite (msghdr->msg_iov[i].iov_base, m, 1, fp); + n += m; + } + assert (n == sz); +} + +static uint16_t calc_ipv4_checksum (const uint16_t *x) +{ + uint32_t s = 0; + int i; + for (i = 0; i < 10; i++) + { + s += x[i]; + } + s = (s & 0xffff) + (s >> 16); + return (uint16_t) ~s; +} + +void write_pcap_received +( + FILE * fp, + nn_wctime_t tstamp, + const os_sockaddr_storage * src, + const os_sockaddr_storage * dst, + unsigned char * buf, + size_t sz +) +{ + if (!config.useIpv6) + { + pcaprec_hdr_t pcap_hdr; + union { + ipv4_hdr_t ipv4_hdr; + uint16_t x[10]; + } u; + udp_hdr_t udp_hdr; + size_t sz_ud = sz + UDP_HDR_SIZE; + size_t sz_iud = sz_ud + IPV4_HDR_SIZE; + os_mutexLock (&gv.pcap_lock); + wctime_to_sec_usec (&pcap_hdr.ts_sec, &pcap_hdr.ts_usec, tstamp); + pcap_hdr.incl_len = pcap_hdr.orig_len = (uint32_t) sz_iud; + fwrite (&pcap_hdr, sizeof (pcap_hdr), 1, fp); + u.ipv4_hdr = ipv4_hdr_template; + u.ipv4_hdr.totallength = toBE2u ((unsigned short) sz_iud); + u.ipv4_hdr.ttl = 128; + u.ipv4_hdr.srcip = ((os_sockaddr_in*) src)->sin_addr.s_addr; + u.ipv4_hdr.dstip = ((os_sockaddr_in*) dst)->sin_addr.s_addr; + u.ipv4_hdr.checksum = calc_ipv4_checksum (u.x); + fwrite (&u.ipv4_hdr, sizeof (u.ipv4_hdr), 1, fp); + udp_hdr.srcport = ((os_sockaddr_in*) src)->sin_port; + udp_hdr.dstport = ((os_sockaddr_in*) dst)->sin_port; + udp_hdr.length = toBE2u ((unsigned short) sz_ud); + udp_hdr.checksum = 0; /* don't have to compute a checksum for UDPv4 */ + fwrite (&udp_hdr, sizeof (udp_hdr), 1, fp); + fwrite (buf, sz, 1, fp); + os_mutexUnlock (&gv.pcap_lock); + } +} + +void write_pcap_sent +( + FILE * fp, + nn_wctime_t tstamp, + const os_sockaddr_storage * src, + const struct msghdr * hdr, + size_t sz +) +{ + if (!config.useIpv6) + { + pcaprec_hdr_t pcap_hdr; + union { + ipv4_hdr_t ipv4_hdr; + uint16_t x[10]; + } u; + udp_hdr_t udp_hdr; + size_t sz_ud = sz + UDP_HDR_SIZE; + size_t sz_iud = sz_ud + IPV4_HDR_SIZE; + os_mutexLock (&gv.pcap_lock); + wctime_to_sec_usec (&pcap_hdr.ts_sec, &pcap_hdr.ts_usec, tstamp); + pcap_hdr.incl_len = pcap_hdr.orig_len = (uint32_t) sz_iud; + fwrite (&pcap_hdr, sizeof (pcap_hdr), 1, fp); + u.ipv4_hdr = ipv4_hdr_template; + u.ipv4_hdr.totallength = toBE2u ((unsigned short) sz_iud); + u.ipv4_hdr.ttl = 255; + u.ipv4_hdr.srcip = ((os_sockaddr_in*) src)->sin_addr.s_addr; + u.ipv4_hdr.dstip = ((os_sockaddr_in*) hdr->msg_name)->sin_addr.s_addr; + u.ipv4_hdr.checksum = calc_ipv4_checksum (u.x); + fwrite (&u.ipv4_hdr, sizeof (u.ipv4_hdr), 1, fp); + udp_hdr.srcport = ((os_sockaddr_in*) src)->sin_port; + udp_hdr.dstport = ((os_sockaddr_in*) hdr->msg_name)->sin_port; + udp_hdr.length = toBE2u ((unsigned short) sz_ud); + udp_hdr.checksum = 0; /* don't have to compute a checksum for UDPv4 */ + fwrite (&udp_hdr, sizeof (udp_hdr), 1, fp); + write_data (fp, hdr, sz); + os_mutexUnlock (&gv.pcap_lock); + } +} diff --git a/src/core/ddsi/src/q_plist.c b/src/core/ddsi/src/q_plist.c new file mode 100644 index 0000000..8442756 --- /dev/null +++ b/src/core/ddsi/src/q_plist.c @@ -0,0 +1,4099 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include +#include + +#include "os/os.h" + +#include "ddsi/q_log.h" + +#include "ddsi/q_bswap.h" +#include "ddsi/q_unused.h" +#include "ddsi/q_align.h" +#include "ddsi/q_error.h" +#include "ddsi/q_plist.h" +#include "ddsi/q_time.h" +#include "ddsi/q_xmsg.h" + +#include "ddsi/q_config.h" +#include "ddsi/q_protocol.h" /* for NN_STATUSINFO_... */ +#include "ddsi/q_radmin.h" /* for nn_plist_quickscan */ +#include "ddsi/q_static_assert.h" + +#include "util/ut_avl.h" +#include "ddsi/q_misc.h" /* for vendor_is_... */ + +/* These are internal to the parameter list processing. We never + generate them, and we never want to do see them anywhere outside + the actual parsing of an incoming parameter list. (There are + entries in nn_plist, but they are never to be inspected and + the bits corresponding to them must be 0 except while processing an + incoming parameter list.) */ +#define PPTMP_MULTICAST_IPADDRESS (1 << 0) +#define PPTMP_DEFAULT_UNICAST_IPADDRESS (1 << 1) +#define PPTMP_DEFAULT_UNICAST_PORT (1 << 2) +#define PPTMP_METATRAFFIC_UNICAST_IPADDRESS (1 << 3) +#define PPTMP_METATRAFFIC_UNICAST_PORT (1 << 4) +#define PPTMP_METATRAFFIC_MULTICAST_IPADDRESS (1 << 5) +#define PPTMP_METATRAFFIC_MULTICAST_PORT (1 << 6) + +typedef struct nn_ipaddress_params_tmp { + unsigned present; + + nn_ipv4address_t multicast_ipaddress; + nn_ipv4address_t default_unicast_ipaddress; + nn_port_t default_unicast_port; + nn_ipv4address_t metatraffic_unicast_ipaddress; + nn_port_t metatraffic_unicast_port; + nn_ipv4address_t metatraffic_multicast_ipaddress; + nn_port_t metatraffic_multicast_port; +} nn_ipaddress_params_tmp_t; + +struct dd { + const unsigned char *buf; + unsigned bufsz; + unsigned bswap: 1; + nn_protocol_version_t protocol_version; + nn_vendorid_t vendorid; +}; + +struct cdroctetseq { + unsigned len; + unsigned char value[1]; +}; + +/* Avoiding all nn_log-related activities when LC_PLIST is not set + (and it hardly ever is, as it is not even included in "trace") + saves a couple of % CPU on a high-rate subscriber - that's worth + it. So we need a macro & a support function. */ +static int trace_plist (const char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + nn_vlog (LC_PLIST, fmt, ap); + va_end (ap); + return 0; +} +#define TRACE_PLIST(args) ((config.enabled_logcats & LC_PLIST) ? (trace_plist args) : 0) + +static void log_octetseq (logcat_t cat, unsigned n, const unsigned char *xs); + +static unsigned align4u (unsigned x) +{ + return (x + 3u) & (unsigned)-4; +} + +static int protocol_version_is_newer (nn_protocol_version_t pv) +{ + const nn_protocol_version_t mv = NN_PROTOCOL_VERSION_INITIALIZER; + return (pv.major < mv.major) ? 0 : (pv.major > mv.major) ? 1 : (pv.minor > mv.minor); +} + +static int validate_string (const struct dd *dd, unsigned *len) +{ + const struct cdrstring *x = (const struct cdrstring *) dd->buf; + if (dd->bufsz < sizeof (struct cdrstring)) + { + TRACE (("plist/validate_string: buffer too small (header)\n")); + return ERR_INVALID; + } + *len = dd->bswap ? bswap4u (x->length) : x->length; + if (*len < 1 || *len > dd->bufsz - offsetof (struct cdrstring, contents)) + { + TRACE (("plist/validate_string: length %u out of range\n", *len)); + return ERR_INVALID; + } + if (x->contents[*len-1] != 0) + { + TRACE (("plist/validate_string: terminator missing\n")); + return ERR_INVALID; + } + return 0; +} + +static int alias_string (const unsigned char **ptr, const struct dd *dd, unsigned *len) +{ + int rc; + if ((rc = validate_string (dd, len)) < 0) + return rc; + else + { + const struct cdrstring *x = (const struct cdrstring *) dd->buf; + *ptr = x->contents; + return 0; + } +} + +static void unalias_string (char **str, int bswap) +{ + const char *alias = *str; + unsigned len; + if (bswap == 0 || bswap == 1) + { + const unsigned *plen = (const unsigned *) alias - 1; + len = bswap ? bswap4u (*plen) : *plen; + } + else + { + len = (unsigned) strlen (alias) + 1; + } + *str = os_malloc (len); + memcpy (*str, alias, len); +} + +static int validate_octetseq (const struct dd *dd, unsigned *len) +{ + const struct cdroctetseq *x = (const struct cdroctetseq *) dd->buf; + if (dd->bufsz < offsetof (struct cdroctetseq, value)) + return ERR_INVALID; + *len = dd->bswap ? bswap4u (x->len) : x->len; + if (*len > dd->bufsz - offsetof (struct cdroctetseq, value)) + return ERR_INVALID; + return 0; +} + +static int alias_octetseq (nn_octetseq_t *oseq, const struct dd *dd) +{ + unsigned len; + int rc; + if ((rc = validate_octetseq (dd, &len)) < 0) + return rc; + else + { + const struct cdroctetseq *x = (const struct cdroctetseq *) dd->buf; + oseq->length = len; + oseq->value = (len == 0) ? NULL : (unsigned char *) x->value; + return 0; + } +} + +static int alias_blob (nn_octetseq_t *oseq, const struct dd *dd) +{ + oseq->length = dd->bufsz; + oseq->value = (oseq->length == 0) ? NULL : (unsigned char *) dd->buf; + return 0; +} + +static void unalias_octetseq (nn_octetseq_t *oseq, UNUSED_ARG (int bswap)) +{ + if (oseq->length != 0) + { + unsigned char *vs; + vs = os_malloc (oseq->length); + memcpy (vs, oseq->value, oseq->length); + oseq->value = vs; + } +} + +static int validate_stringseq (const struct dd *dd) +{ + const unsigned char *seq = dd->buf; + const unsigned char *seqend = seq + dd->bufsz; + struct dd dd1 = *dd; + int i, n; + if (dd->bufsz < sizeof (int)) + { + TRACE (("plist/validate_stringseq: buffer too small (header)\n")); + return ERR_INVALID; + } + memcpy (&n, seq, sizeof (n)); + if (dd->bswap) + n = bswap4 (n); + seq += sizeof (int); + if (n < 0) + { + TRACE (("plist/validate_stringseq: length %d out of range\n", n)); + return ERR_INVALID; + } + else if (n == 0) + { + return 0; + } + else + { + for (i = 0; i < n && seq <= seqend; i++) + { + unsigned len1; + int rc; + dd1.buf = seq; + dd1.bufsz = (unsigned) (seqend - seq); + if ((rc = validate_string (&dd1, &len1)) < 0) + { + TRACE (("plist/validate_stringseq: invalid string\n")); + return rc; + } + seq += sizeof (unsigned) + align4u (len1); + } + if (i < n) + { + TRACE (("plist/validate_stringseq: buffer too small (contents)\n")); + return ERR_INVALID; + } + } + /* Should I worry about junk between the last string & the end of + the parameter? */ + return 0; +} + +static int alias_stringseq (nn_stringseq_t *strseq, const struct dd *dd) +{ + /* Not truly an alias: it allocates an array of pointers that alias + the individual null-terminated strings. Also: see + validate_stringseq */ + const unsigned char *seq = dd->buf; + const unsigned char *seqend = seq + dd->bufsz; + struct dd dd1 = *dd; + char **strs; + unsigned i; + int result; + if (dd->bufsz < sizeof (int)) + { + TRACE (("plist/alias_stringseq: buffer too small (header)\n")); + return ERR_INVALID; + } + memcpy (&strseq->n, seq, sizeof (strseq->n)); + if (dd->bswap) + strseq->n = bswap4u (strseq->n); + seq += sizeof (unsigned); + if (strseq->n >= UINT_MAX / sizeof(*strs)) + { + TRACE (("plist/alias_stringseq: length %u out of range\n", strseq->n)); + return ERR_INVALID; + } + else if (strseq->n == 0) + { + strseq->strs = NULL; + } + else + { + strs = os_malloc (strseq->n * sizeof (*strs)); + for (i = 0; i < strseq->n && seq <= seqend; i++) + { + unsigned len1; + dd1.buf = seq; + dd1.bufsz = (unsigned) (seqend - seq); + /* (const char **) to silence the compiler, unfortunately strseq + can't have a const char **strs, that would require a const + and a non-const version of it. */ + if ((result = alias_string ((const unsigned char **) &strs[i], &dd1, &len1)) < 0) + { + TRACE (("plist/alias_stringseq: invalid string\n")); + goto fail; + } + seq += sizeof (unsigned) + align4u (len1); + } + if (i != strseq->n) + { + TRACE (("plist/validate_stringseq: buffer too small (contents)\n")); + result = ERR_INVALID; + goto fail; + } + strseq->strs = strs; + } + return 0; + fail: + os_free (strs); + return result; +} + +static void free_stringseq (nn_stringseq_t *strseq) +{ + unsigned i; + for (i = 0; i < strseq->n; i++) + { + if (strseq->strs[i]) + { + os_free (strseq->strs[i]); + } + } + os_free (strseq->strs); +} + +static int unalias_stringseq (nn_stringseq_t *strseq, int bswap) +{ + unsigned i; + char **strs; + if (strseq->n != 0) + { + strs = os_malloc (strseq->n * sizeof (*strs)); + for (i = 0; i < strseq->n; i++) + { + strs[i] = strseq->strs[i]; + unalias_string (&strs[i], bswap); + } + os_free (strseq->strs); + strseq->strs = strs; + } + return 0; +} + +static void duplicate_stringseq (nn_stringseq_t *dest, const nn_stringseq_t *src) +{ + unsigned i; + dest->n = src->n; +assert (dest->strs == NULL); + if (dest->n == 0) + { + dest->strs = NULL; + return; + } + dest->strs = os_malloc (dest->n * sizeof (*dest->strs)); + for (i = 0; i < dest->n; i++) + { + dest->strs[i] = src->strs[i]; + unalias_string (&dest->strs[i], -1); + } +} + +_Success_(return == 0) +static int validate_property (_In_ const struct dd *dd, _Out_ unsigned *len) +{ + struct dd ddV = *dd; + unsigned lenN; + unsigned lenV; + int rc; + + /* Check name. */ + rc = validate_string(dd, &lenN); + if (rc != 0) + { + TRACE (("plist/validate_property: name validation failed\n")); + return rc; + } + lenN = sizeof(unsigned) + /* cdr string len arg + */ + align4u(lenN); /* strlen + possible padding */ + + + /* Check value. */ + ddV.buf = dd->buf + lenN; + ddV.bufsz = dd->bufsz - lenN; + rc = validate_string(&ddV, &lenV); + if (rc != 0) + { + TRACE (("plist/validate_property: value validation failed\n")); + return rc; + } + lenV = sizeof(unsigned) + /* cdr string len arg + */ + align4u(lenV); /* strlen + possible padding */ + + *len = lenN + lenV; + + return 0; +} + +_Success_(return == 0) +static int alias_property (_Out_ nn_property_t *prop, _In_ const struct dd *dd, _Out_ unsigned *len) +{ + struct dd ddV = *dd; + unsigned lenN; + unsigned lenV; + int rc; + + /* Get name */ + rc = alias_string((const unsigned char **)&(prop->name), dd, &lenN); + if (rc != 0) + { + TRACE (("plist/alias_property: invalid name buffer\n")); + return rc; + } + lenN = sizeof(unsigned) + /* cdr string len arg + */ + align4u(lenN); /* strlen + possible padding */ + + /* Get value */ + ddV.buf = dd->buf + lenN; + ddV.bufsz = dd->bufsz - lenN; + rc = alias_string((const unsigned char **)&(prop->value), &ddV, &lenV); + if (rc != 0) + { + TRACE (("plist/validate_property: invalid value buffer\n")); + return rc; + } + lenV = sizeof(unsigned) + /* cdr string len arg + */ + align4u (lenV); /* strlen + possible padding */ + + /* We got this from the wire; so it has been propagated. */ + prop->propagate = true; + + *len = lenN + lenV; + + return 0; +} + +static void free_property (_Inout_ nn_property_t *prop) +{ + os_free(prop->name); + prop->name = NULL; + os_free(prop->value); + prop->value = NULL; +} + +static void unalias_property (_Inout_ nn_property_t *prop, _In_ int bswap) +{ + unalias_string(&prop->name, bswap); + unalias_string(&prop->value, bswap); +} + +static void duplicate_property (_Out_ nn_property_t *dest, _In_ const nn_property_t *src) +{ + nn_property_t tmp = *src; + unalias_property(&tmp, -1); + *dest = tmp; +} + +_Success_(return == 0) +static int validate_propertyseq (_In_ const struct dd *dd, _Out_ unsigned *len) +{ + const unsigned char *seq = dd->buf; + const unsigned char *seqend = seq + dd->bufsz; + struct dd dd1 = *dd; + int i, n; + if (dd->bufsz < sizeof (int)) + { + TRACE (("plist/validate_propertyseq: buffer too small (header)\n")); + return ERR_INVALID; + } + memcpy (&n, seq, sizeof (n)); + if (dd->bswap) + n = bswap4 (n); + seq += sizeof (int); + if (n < 0) + { + TRACE (("plist/validate_propertyseq: length %d out of range\n", n)); + return ERR_INVALID; + } + else if (n == 0) + { + /* nothing to check */ + } + else + { + for (i = 0; i < n && seq <= seqend; i++) + { + unsigned len1; + int rc; + dd1.buf = seq; + dd1.bufsz = (unsigned) (seqend - seq); + if ((rc = validate_property (&dd1, &len1)) != 0) + { + TRACE (("plist/validate_propertyseq: invalid property\n")); + return rc; + } + seq += len1; + } + if (i < n) + { + TRACE (("plist/validate_propertyseq: buffer too small (contents)\n")); + return ERR_INVALID; + } + } + *len = align4u((unsigned)(seq - dd->buf)); + return 0; +} + +_Success_(return == 0) +static int alias_propertyseq (_Out_ nn_propertyseq_t *pseq, _In_ const struct dd *dd, _Out_ unsigned *len) +{ + /* Not truly an alias: it allocates an array of pointers that alias + the individual components. Also: see validate_propertyseq */ + const unsigned char *seq = dd->buf; + const unsigned char *seqend = seq + dd->bufsz; + struct dd dd1 = *dd; + nn_property_t *props; + unsigned i; + int result; + if (dd->bufsz < sizeof (int)) + { + TRACE (("plist/alias_propertyseq: buffer too small (header)\n")); + return ERR_INVALID; + } + + memcpy (&pseq->n, seq, sizeof (pseq->n)); + if (dd->bswap) + pseq->n = bswap4u (pseq->n); + seq += sizeof (unsigned); + if (pseq->n >= UINT_MAX / sizeof(*props)) + { + TRACE (("plist/alias_propertyseq: length %u out of range\n", pseq->n)); + return ERR_INVALID; + } + else if (pseq->n == 0) + { + pseq->props = NULL; + } + else + { + props = os_malloc (pseq->n * sizeof (*props)); + for (i = 0; i < pseq->n && seq <= seqend; i++) + { + unsigned len1; + dd1.buf = seq; + dd1.bufsz = (unsigned) (seqend - seq); + if ((result = alias_property (&props[i], &dd1, &len1)) != 0) + { + TRACE (("plist/alias_propertyseq: invalid property\n")); + goto fail; + } + seq += len1; + } + if (i != pseq->n) + { + TRACE (("plist/alias_propertyseq: buffer too small (contents)\n")); + result = ERR_INVALID; + goto fail; + } + pseq->props = props; + } + *len = align4u((unsigned)(seq - dd->buf)); + return 0; + fail: + os_free (props); + return result; +} + +static void free_propertyseq (_Inout_ nn_propertyseq_t *pseq) +{ + unsigned i; + for (i = 0; i < pseq->n; i++) + { + free_property(&(pseq->props[i])); + } + os_free (pseq->props); + pseq->props = NULL; + pseq->n = 0; +} + +static void unalias_propertyseq (_Inout_ nn_propertyseq_t *pseq, _In_ int bswap) +{ + unsigned i; + nn_property_t *props; + if (pseq->n != 0) + { + props = os_malloc (pseq->n * sizeof (*pseq->props)); + for (i = 0; i < pseq->n; i++) + { + props[i] = pseq->props[i]; + unalias_property (&props[i], bswap); + } + os_free (pseq->props); + pseq->props = props; + } +} + +static void duplicate_propertyseq (_Out_ nn_propertyseq_t *dest, _In_ const nn_propertyseq_t *src) +{ + unsigned i; + dest->n = src->n; + if (dest->n == 0) + { + dest->props = NULL; + return; + } + dest->props = os_malloc (dest->n * sizeof (*dest->props)); + for (i = 0; i < dest->n; i++) + { + duplicate_property(&(dest->props[i]), &(src->props[i])); + } +} + +_Success_(return == 0) +static int validate_binaryproperty (_In_ const struct dd *dd, _Out_ unsigned *len) +{ + struct dd ddV = *dd; + unsigned lenN; + unsigned lenV; + int rc; + + /* Check name. */ + rc = validate_string(dd, &lenN); + if (rc != 0) + { + TRACE (("plist/validate_property: name validation failed\n")); + return rc; + } + lenN = sizeof(unsigned) + /* cdr string len arg + */ + align4u(lenN); /* strlen + possible padding */ + + /* Check value. */ + ddV.buf = dd->buf + lenN; + ddV.bufsz = dd->bufsz - lenN; + rc = validate_octetseq(&ddV, &lenV); + if (rc != 0) + { + TRACE (("plist/validate_property: value validation failed\n")); + return rc; + } + lenV = sizeof(unsigned) + /* cdr sequence len arg + */ + align4u(lenV); /* seqlen + possible padding */ + + *len = lenN + lenV; + + return 0; +} + +_Success_(return == 0) +static int alias_binaryproperty (_Out_ nn_binaryproperty_t *prop, _In_ const struct dd *dd, _Out_ unsigned *len) +{ + struct dd ddV = *dd; + unsigned lenN; + unsigned lenV; + int rc; + + /* Get name */ + rc = alias_string((const unsigned char **)&(prop->name), dd, &lenN); + if (rc != 0) + { + TRACE (("plist/alias_property: invalid name buffer\n")); + return rc; + } + lenN = sizeof(unsigned) + /* cdr string len arg + */ + align4u(lenN); /* strlen + possible padding */ + + /* Get value */ + ddV.buf = dd->buf + lenN; + ddV.bufsz = dd->bufsz - lenN; + rc = alias_octetseq(&(prop->value), &ddV); + if (rc != 0) + { + TRACE (("plist/validate_property: invalid value buffer\n")); + return rc; + } + lenV = sizeof(unsigned) + /* cdr sequence len arg + */ + align4u(prop->value.length); /* seqlen + possible padding */ + + /* We got this from the wire; so it has been propagated. */ + prop->propagate = true; + + *len = lenN + lenV; + + return 0; +} + +static void free_binaryproperty (_Inout_ nn_binaryproperty_t *prop) +{ + os_free(prop->name); + prop->name = NULL; + os_free(prop->value.value); + prop->value.value = NULL; + prop->value.length = 0; +} + +static void unalias_binaryproperty (_Inout_ nn_binaryproperty_t *prop, _In_ int bswap) +{ + unalias_string (&prop->name, bswap); + unalias_octetseq(&prop->value, bswap); +} + +static void duplicate_binaryproperty (_Out_ nn_binaryproperty_t *dest, _In_ const nn_binaryproperty_t *src) +{ + nn_binaryproperty_t tmp = *src; + unalias_binaryproperty(&tmp, -1); + *dest = tmp; +} + +_Success_(return == 0) +static int validate_binarypropertyseq (_In_ const struct dd *dd, _Out_ unsigned *len) +{ + const unsigned char *seq = dd->buf; + const unsigned char *seqend = seq + dd->bufsz; + struct dd dd1 = *dd; + int i, n; + if (dd->bufsz < sizeof (int)) + { + TRACE (("plist/validate_binarypropertyseq: buffer too small (header)\n")); + return ERR_INVALID; + } + memcpy (&n, seq, sizeof (n)); + if (dd->bswap) + n = bswap4 (n); + seq += sizeof (int); + if (n < 0) + { + TRACE (("plist/validate_binarypropertyseq: length %d out of range\n", n)); + return ERR_INVALID; + } + else if (n == 0) + { + /* nothing to check */ + } + else + { + for (i = 0; i < n && seq <= seqend; i++) + { + unsigned len1; + int rc; + dd1.buf = seq; + dd1.bufsz = (unsigned) (seqend - seq); + if ((rc = validate_binaryproperty (&dd1, &len1)) != 0) + { + TRACE (("plist/validate_binarypropertyseq: invalid property\n")); + return rc; + } + seq += len1; + } + if (i < n) + { + TRACE (("plist/validate_binarypropertyseq: buffer too small (contents)\n")); + return ERR_INVALID; + } + } + *len = align4u((unsigned)(seq - dd->buf)); + return 0; +} + +_Success_(return == 0) +static int alias_binarypropertyseq (_Out_ nn_binarypropertyseq_t *pseq, _In_ const struct dd *dd, _Out_ unsigned *len) +{ + /* Not truly an alias: it allocates an array of pointers that alias + the individual components. Also: see validate_binarypropertyseq */ + const unsigned char *seq = dd->buf; + const unsigned char *seqend = seq + dd->bufsz; + struct dd dd1 = *dd; + nn_binaryproperty_t *props; + unsigned i; + int result; + if (dd->bufsz < sizeof (int)) + { + TRACE (("plist/alias_binarypropertyseq: buffer too small (header)\n")); + return ERR_INVALID; + } + + memcpy (&pseq->n, seq, sizeof (pseq->n)); + if (dd->bswap) + pseq->n = bswap4u (pseq->n); + seq += sizeof (unsigned); + if (pseq->n >= UINT_MAX / sizeof(*props)) + { + TRACE (("plist/alias_binarypropertyseq: length %u out of range\n", pseq->n)); + return ERR_INVALID; + } + else if (pseq->n == 0) + { + pseq->props = NULL; + } + else + { + props = os_malloc (pseq->n * sizeof (*props)); + for (i = 0; i < pseq->n && seq <= seqend; i++) + { + unsigned len1; + dd1.buf = seq; + dd1.bufsz = (unsigned) (seqend - seq); + if ((result = alias_binaryproperty (&props[i], &dd1, &len1)) != 0) + { + TRACE (("plist/alias_binarypropertyseq: invalid property\n")); + goto fail; + } + seq += len1; + } + if (i != pseq->n) + { + TRACE (("plist/alias_binarypropertyseq: buffer too small (contents)\n")); + result = ERR_INVALID; + goto fail; + } + pseq->props = props; + } + *len = align4u((unsigned)(seq - dd->buf)); + return 0; + fail: + os_free (props); + return result; +} + +static void free_binarypropertyseq (_Inout_ nn_binarypropertyseq_t *pseq) +{ + unsigned i; + for (i = 0; i < pseq->n; i++) + { + free_binaryproperty(&(pseq->props[i])); + } + os_free (pseq->props); + pseq->props = NULL; + pseq->n = 0; +} + +static void unalias_binarypropertyseq (_Inout_ nn_binarypropertyseq_t *pseq, _In_ int bswap) +{ + unsigned i; + nn_binaryproperty_t *props; + if (pseq->n != 0) + { + props = os_malloc (pseq->n * sizeof (*pseq->props)); + for (i = 0; i < pseq->n; i++) + { + props[i] = pseq->props[i]; + unalias_binaryproperty (&props[i], bswap); + } + os_free (pseq->props); + pseq->props = props; + } +} + +static void duplicate_binarypropertyseq (_Out_ nn_binarypropertyseq_t *dest, _In_ const nn_binarypropertyseq_t *src) +{ + unsigned i; + dest->n = src->n; + if (dest->n == 0) + { + dest->props = NULL; + return; + } + dest->props = os_malloc (dest->n * sizeof (*dest->props)); + for (i = 0; i < dest->n; i++) + { + duplicate_binaryproperty(&(dest->props[i]), &(src->props[i])); + } +} + +static void free_locators (nn_locators_t *locs) +{ + while (locs->first) + { + struct nn_locators_one *l = locs->first; + locs->first = l->next; + os_free (l); + } +} + +static void unalias_locators (nn_locators_t *locs, UNUSED_ARG (int bswap)) +{ + nn_locators_t newlocs; + struct nn_locators_one *lold; + /* Copy it, without reversing the order. On failure, free the copy, + on success overwrite *locs. */ + newlocs.n = locs->n; + newlocs.first = NULL; + newlocs.last = NULL; + for (lold = locs->first; lold != NULL; lold = lold->next) + { + struct nn_locators_one *n; + n = os_malloc (sizeof (*n)); + n->next = NULL; + n->loc = lold->loc; + if (newlocs.first == NULL) + newlocs.first = n; + else + newlocs.last->next = n; + newlocs.last = n; + } + *locs = newlocs; +} + +static void unalias_eotinfo (nn_prismtech_eotinfo_t *txnid, UNUSED_ARG (int bswap)) +{ + if (txnid->n > 0) + { + nn_prismtech_eotgroup_tid_t *vs; + vs = os_malloc (txnid->n * sizeof (*vs)); + memcpy (vs, txnid->tids, txnid->n * sizeof (*vs)); + txnid->tids = vs; + } +} + +static void free_dataholder (_Inout_ nn_dataholder_t *dh) +{ + os_free(dh->class_id); + dh->class_id = NULL; + free_propertyseq(&(dh->properties)); + free_binarypropertyseq(&(dh->binary_properties)); +} + +static void unalias_dataholder (_Inout_ nn_dataholder_t *holder, _In_ int bswap) +{ + unalias_string(&(holder->class_id), bswap); + unalias_propertyseq(&(holder->properties), bswap); + unalias_binarypropertyseq(&(holder->binary_properties), bswap); +} + +static void duplicate_property_qospolicy (_Out_ nn_property_qospolicy_t *dest, _In_ const nn_property_qospolicy_t *src) +{ + duplicate_propertyseq(&(dest->value), &(src->value)); + duplicate_binarypropertyseq(&(dest->binary_value), &(src->binary_value)); +} + +void nn_plist_fini (nn_plist_t *ps) +{ + struct t { uint64_t fl; size_t off; }; + static const struct t simple[] = { + { PP_ENTITY_NAME, offsetof (nn_plist_t, entity_name) }, + { PP_PRISMTECH_NODE_NAME, offsetof (nn_plist_t, node_name) }, + { PP_PRISMTECH_EXEC_NAME, offsetof (nn_plist_t, exec_name) }, + { PP_PRISMTECH_PARTICIPANT_VERSION_INFO, offsetof (nn_plist_t, prismtech_participant_version_info.internals) }, + { PP_PRISMTECH_TYPE_DESCRIPTION, offsetof (nn_plist_t, type_description) }, + { PP_PRISMTECH_EOTINFO, offsetof (nn_plist_t, eotinfo.tids) } + }; + static const struct t locs[] = { + { PP_UNICAST_LOCATOR, offsetof (nn_plist_t, unicast_locators) }, + { PP_MULTICAST_LOCATOR, offsetof (nn_plist_t, multicast_locators) }, + { PP_DEFAULT_UNICAST_LOCATOR, offsetof (nn_plist_t, default_unicast_locators) }, + { PP_DEFAULT_MULTICAST_LOCATOR, offsetof (nn_plist_t, default_multicast_locators) }, + { PP_METATRAFFIC_UNICAST_LOCATOR, offsetof (nn_plist_t, metatraffic_unicast_locators) }, + { PP_METATRAFFIC_MULTICAST_LOCATOR, offsetof (nn_plist_t, metatraffic_multicast_locators) } + }; + static const struct t tokens[] = { + { PP_IDENTITY_TOKEN, offsetof (nn_plist_t, identity_token) }, + { PP_PERMISSIONS_TOKEN, offsetof (nn_plist_t, permissions_token) } + }; + int i; + nn_xqos_fini (&ps->qos); + +/* The compiler doesn't understand how offsetof is used in the arrays. */ +OS_WARNING_MSVC_OFF(6001); + for (i = 0; i < (int) (sizeof (simple) / sizeof (*simple)); i++) + { + if ((ps->present & simple[i].fl) && !(ps->aliased & simple[i].fl)) + { + void **pp = (void **) ((char *) ps + simple[i].off); + os_free (*pp); + } + } + for (i = 0; i < (int) (sizeof (locs) / sizeof (*locs)); i++) + { + if ((ps->present & locs[i].fl) && !(ps->aliased & locs[i].fl)) + free_locators ((nn_locators_t *) ((char *) ps + locs[i].off)); + } + for (i = 0; i < (int) (sizeof (tokens) / sizeof (*tokens)); i++) + { + if ((ps->present & tokens[i].fl) && !(ps->aliased & tokens[i].fl)) + free_dataholder ((nn_token_t *) ((char *) ps + tokens[i].off)); + } +OS_WARNING_MSVC_ON(6001); + + ps->present = 0; +} + +#if 0 /* not currently needed */ +void nn_plist_unalias (nn_plist_t *ps) +{ +#define P(name_, func_, field_) do { \ + if ((ps->present & PP_##name_) && (ps->aliased & PP_##name_)) { \ + unalias_##func_ (&ps->field_, -1); \ + ps->aliased &= ~PP_##name_; \ + } \ + } while (0) + nn_xqos_unalias (&ps->qos); + P (ENTITY_NAME, string, entity_name); + P (UNICAST_LOCATOR, locators, unicast_locators); + P (MULTICAST_LOCATOR, locators, multicast_locators); + P (DEFAULT_UNICAST_LOCATOR, locators, default_unicast_locators); + P (DEFAULT_MULTICAST_LOCATOR, locators, default_multicast_locators); + P (METATRAFFIC_UNICAST_LOCATOR, locators, metatraffic_unicast_locators); + P (METATRAFFIC_MULTICAST_LOCATOR, locators, metatraffic_multicast_locators); + P (PRISMTECH_NODE_NAME, string, node_name); + P (PRISMTECH_EXEC_NAME, string, exec_name); + P (PRISMTECH_TYPE_DESCRIPTION, string, type_description); + P (PRISMTECH_EOTINFO, eotinfo, eotinfo); + P (IDENTITY_TOKEN, dataholder, identity_token); + P (PERMISSIONS_TOKEN, dataholder, permissions_token); +#undef P + if ((ps->present & PP_PRISMTECH_PARTICIPANT_VERSION_INFO) && + (ps->aliased & PP_PRISMTECH_PARTICIPANT_VERSION_INFO)) + { + unalias_string (&ps->prismtech_participant_version_info.internals, -1); + ps->aliased &= ~PP_PRISMTECH_PARTICIPANT_VERSION_INFO; + } + + assert (ps->aliased == 0); +} +#endif + +static int do_octetseq (nn_octetseq_t *dst, uint64_t *present, uint64_t *aliased, uint64_t wanted, uint64_t fl, const struct dd *dd) +{ + int res; + unsigned len; + if (!(wanted & fl)) + return NN_STRICT_P ? validate_octetseq (dd, &len) : 0; + if ((res = alias_octetseq (dst, dd)) >= 0) + { + *present |= fl; + *aliased |= fl; + } + return res; +} + +static int do_blob (nn_octetseq_t *dst, uint64_t *present, uint64_t *aliased, uint64_t wanted, uint64_t fl, const struct dd *dd) +{ + int res; + if (!(wanted & fl)) + return 0; + if ((res = alias_blob (dst, dd)) >= 0) + { + *present |= fl; + *aliased |= fl; + } + return res; +} + +static int do_string (char **dst, uint64_t *present, uint64_t *aliased, uint64_t wanted, uint64_t fl, const struct dd *dd) +{ + int res; + unsigned len; + if (!(wanted & fl)) + return NN_STRICT_P ? validate_string (dd, &len) : 0; + if ((res = alias_string ((const unsigned char **) dst, dd, &len)) >= 0) + { + *present |= fl; + *aliased |= fl; + } + return res; +} + +static int do_stringseq (nn_stringseq_t *dst, uint64_t *present, uint64_t *aliased, uint64_t wanted, uint64_t fl, const struct dd *dd) +{ + int res; + if (!(wanted & fl)) + return NN_STRICT_P ? validate_stringseq (dd) : 0; + if ((res = alias_stringseq (dst, dd)) >= 0) + { + *present |= fl; + *aliased |= fl; + } + return res; +} + +static void bswap_time (nn_ddsi_time_t *t) +{ + t->seconds = bswap4 (t->seconds); + t->fraction = bswap4u (t->fraction); +} + +static int validate_time (const nn_ddsi_time_t *t) +{ + /* Accepter are zero, positive, infinite or invalid as defined in + the DDS 2.1 spec, table 9.4. */ + if (t->seconds >= 0) + return 0; + else if (t->seconds == -1 && t->fraction == UINT32_MAX) + return 0; + else + { + TRACE (("plist/validate_time: invalid timestamp (%08x.%08x)\n", t->seconds, t->fraction)); + return ERR_INVALID; + } +} + +#if DDSI_DURATION_ACCORDING_TO_SPEC +static void bswap_duration (nn_duration_t *d) +{ + /* Why o why is a Duration_t used for some things, and a (DDSI) time + used for other things, where a duration is {sec,nsec} and a time + is {sec,fraction}? */ + d->sec = bswap4 (d->sec); + d->nanosec = bswap4 (d->nanosec); +} + +int validate_duration (const nn_duration_t *d) +{ + /* Accepted are zero, positive, infinite or invalid as defined in + the DDS 1.2 spec. */ + if (d->sec >= 0 && d->nanosec >= 0 && d->nanosec < 1000000000) + return 0; + else if (d->sec == (int) INT32_MAX && d->nanosec == (int) INT32_MAX) + return 0; + else if (d->sec == -1 && d->nanosec == (int) UINT32_MAX) + return 0; + else + { + TRACE (("plist/validate_time: invalid duration (%08x.%08x)\n", d->sec, d->nanosec)); + return ERR_INVALID; + } +} +#else +static void bswap_duration (nn_duration_t *d) +{ + bswap_time (d); +} + +int validate_duration (const nn_duration_t *d) +{ + return validate_time (d); +} +#endif + +static int do_duration (nn_duration_t *q, uint64_t *present, uint64_t fl, const struct dd *dd) +{ + int res; + if (dd->bufsz < sizeof (*q)) + { + TRACE (("plist/do_duration: buffer too small\n")); + return ERR_INVALID; + } + memcpy (q, dd->buf, sizeof (*q)); + if (dd->bswap) + bswap_duration (q); + if ((res = validate_duration (q)) < 0) + return res; + *present |= fl; + return 0; +} + +static void bswap_durability_qospolicy (nn_durability_qospolicy_t *q) +{ + q->kind = bswap4u (q->kind); +} + +int validate_durability_qospolicy (const nn_durability_qospolicy_t *q) +{ + switch (q->kind) + { + case NN_VOLATILE_DURABILITY_QOS: + case NN_TRANSIENT_LOCAL_DURABILITY_QOS: + case NN_TRANSIENT_DURABILITY_QOS: + case NN_PERSISTENT_DURABILITY_QOS: + break; + default: + TRACE (("plist/validate_durability_qospolicy: invalid kind (%d)\n", (int) q->kind)); + return ERR_INVALID; + } + return 0; +} + +static void bswap_history_qospolicy (nn_history_qospolicy_t *q) +{ + q->kind = bswap4u (q->kind); + q->depth = bswap4 (q->depth); +} + +int validate_history_qospolicy (const nn_history_qospolicy_t *q) +{ + /* Validity of history setting and of resource limits are dependent, + but we don't have access to the resource limits here ... the + combination can only be validated once all the qos policies have + been parsed. + + Why is KEEP_LAST n or KEEP_ALL instead of just KEEP_LAST n, with + n possibly unlimited. */ + switch (q->kind) + { + case NN_KEEP_LAST_HISTORY_QOS: + case NN_KEEP_ALL_HISTORY_QOS: + break; + default: + TRACE (("plist/validate_history_qospolicy: invalid kind (%d)\n", (int) q->kind)); + return ERR_INVALID; + } + /* Accept all values for depth if kind = ALL */ + if (q->kind == NN_KEEP_LAST_HISTORY_QOS) + { + if (q->depth < 1) + { + TRACE (("plist/validate_history_qospolicy: invalid depth (%d)\n", (int) q->depth)); + return ERR_INVALID; + } + } + return 0; +} + +static void bswap_resource_limits_qospolicy (nn_resource_limits_qospolicy_t *q) +{ + q->max_samples = bswap4 (q->max_samples); + q->max_instances = bswap4 (q->max_instances); + q->max_samples_per_instance = bswap4 (q->max_samples_per_instance); +} + +int validate_resource_limits_qospolicy (const nn_resource_limits_qospolicy_t *q) +{ + const int unlimited = NN_DDS_LENGTH_UNLIMITED; + /* Note: dependent on history setting as well (see + validate_history_qospolicy). Verifying only the internal + consistency of the resource limits. */ + if (q->max_samples < 1 && q->max_samples != unlimited) + { + TRACE (("plist/validate_resource_limits_qospolicy: max_samples invalid (%d)\n", (int) q->max_samples)); + return ERR_INVALID; + } + if (q->max_instances < 1 && q->max_instances != unlimited) + { + TRACE (("plist/validate_resource_limits_qospolicy: max_instances invalid (%d)\n", (int) q->max_instances)); + return ERR_INVALID; + } + if (q->max_samples_per_instance < 1 && q->max_samples_per_instance != unlimited) + { + TRACE (("plist/validate_resource_limits_qospolicy: max_samples_per_instance invalid (%d)\n", (int) q->max_samples_per_instance)); + return ERR_INVALID; + } + if (q->max_samples != unlimited && q->max_samples_per_instance != unlimited) + { + /* Interpreting 7.1.3.19 as if "unlimited" is meant to mean "don't + care" and any conditions related to it must be ignored. */ + if (q->max_samples < q->max_samples_per_instance) + { + TRACE (("plist/validate_resource_limits_qospolicy: max_samples (%d) and max_samples_per_instance (%d) incompatible\n", (int) q->max_samples, (int) q->max_samples_per_instance)); + return ERR_INVALID; + } + } + return 0; +} + +int validate_history_and_resource_limits (const nn_history_qospolicy_t *qh, const nn_resource_limits_qospolicy_t *qr) +{ + const int unlimited = NN_DDS_LENGTH_UNLIMITED; + int res; + if ((res = validate_history_qospolicy (qh)) < 0) + { + TRACE (("plist/validate_history_and_resource_limits: history policy invalid\n")); + return res; + } + if ((res = validate_resource_limits_qospolicy (qr)) < 0) + { + TRACE (("plist/validate_history_and_resource_limits: resource_limits policy invalid\n")); + return res; + } + switch (qh->kind) + { + case NN_KEEP_ALL_HISTORY_QOS: +#if 0 /* See comment in validate_resource_limits, ref'ing 7.1.3.19 */ + if (qr->max_samples_per_instance != unlimited) + { + TRACE (("plist/validate_history_and_resource_limits: max_samples_per_instance (%d) incompatible with KEEP_ALL policy\n", (int) qr->max_samples_per_instance)); + return ERR_INVALID; + } +#endif + break; + case NN_KEEP_LAST_HISTORY_QOS: + if (qr->max_samples_per_instance != unlimited && qh->depth > qr->max_samples_per_instance) + { + TRACE (("plist/validate_history_and_resource_limits: depth (%d) and max_samples_per_instance (%d) incompatible with KEEP_LAST policy\n", (int) qh->depth, (int) qr->max_samples_per_instance)); + return ERR_INVALID; + } + break; + } + return 0; +} + +static void bswap_durability_service_qospolicy (nn_durability_service_qospolicy_t *q) +{ + bswap_duration (&q->service_cleanup_delay); + bswap_history_qospolicy (&q->history); + bswap_resource_limits_qospolicy (&q->resource_limits); +} + +int validate_durability_service_qospolicy (const nn_durability_service_qospolicy_t *q) +{ + int res; + if ((res = validate_duration (&q->service_cleanup_delay)) < 0) + { + TRACE (("plist/validate_durability_service_qospolicy: duration invalid\n")); + return res; + } + if ((res = validate_history_and_resource_limits (&q->history, &q->resource_limits)) < 0) + { + TRACE (("plist/validate_durability_service_qospolicy: invalid history and/or resource limits\n")); + return res; + } + return 0; +} + +static void bswap_liveliness_qospolicy (nn_liveliness_qospolicy_t *q) +{ + q->kind = bswap4u (q->kind); + bswap_duration (&q->lease_duration); +} + +int validate_liveliness_qospolicy (const nn_liveliness_qospolicy_t *q) +{ + int res; + switch (q->kind) + { + case NN_AUTOMATIC_LIVELINESS_QOS: + case NN_MANUAL_BY_PARTICIPANT_LIVELINESS_QOS: + case NN_MANUAL_BY_TOPIC_LIVELINESS_QOS: + if ((res = validate_duration (&q->lease_duration)) < 0) + TRACE (("plist/validate_liveliness_qospolicy: invalid lease duration\n")); + return res; + default: + TRACE (("plist/validate_liveliness_qospolicy: invalid kind (%d)\n", (int) q->kind)); + return ERR_INVALID; + } +} + +static void bswap_external_reliability_qospolicy (nn_external_reliability_qospolicy_t *qext) +{ + qext->kind = bswap4 (qext->kind); + bswap_duration (&qext->max_blocking_time); +} + +static int validate_xform_reliability_qospolicy (nn_reliability_qospolicy_t *qdst, const nn_external_reliability_qospolicy_t *qext) +{ + int res; + qdst->max_blocking_time = qext->max_blocking_time; + if (NN_PEDANTIC_P) + { + switch (qext->kind) + { + case NN_PEDANTIC_BEST_EFFORT_RELIABILITY_QOS: + qdst->kind = NN_BEST_EFFORT_RELIABILITY_QOS; + return 0; + case NN_PEDANTIC_RELIABLE_RELIABILITY_QOS: + qdst->kind = NN_RELIABLE_RELIABILITY_QOS; + if ((res = validate_duration (&qdst->max_blocking_time)) < 0) + TRACE (("plist/validate_xform_reliability_qospolicy[pedantic]: max_blocking_time invalid\n")); + return res; + default: + TRACE (("plist/validate_xform_reliability_qospolicy[pedantic]: invalid kind (%d)\n", (int) qext->kind)); + return ERR_INVALID; + } + } + else + { + switch (qext->kind) + { + case NN_INTEROP_BEST_EFFORT_RELIABILITY_QOS: + qdst->kind = NN_BEST_EFFORT_RELIABILITY_QOS; + return 0; + case NN_INTEROP_RELIABLE_RELIABILITY_QOS: + qdst->kind = NN_RELIABLE_RELIABILITY_QOS; + if ((res = validate_duration (&qdst->max_blocking_time)) < 0) + TRACE (("plist/validate_xform_reliability_qospolicy[!pedantic]: max_blocking time invalid\n")); + return res; + default: + TRACE (("plist/validate_xform_reliability_qospolicy[!pedantic]: invalid kind (%d)\n", (int) qext->kind)); + return ERR_INVALID; + } + } +} + +static void bswap_destination_order_qospolicy (nn_destination_order_qospolicy_t *q) +{ + q->kind = bswap4u (q->kind); +} + +int validate_destination_order_qospolicy (const nn_destination_order_qospolicy_t *q) +{ + switch (q->kind) + { + case NN_BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS: + case NN_BY_SOURCE_TIMESTAMP_DESTINATIONORDER_QOS: + return 0; + default: + TRACE (("plist/validate_destination_order_qospolicy: invalid kind (%d)\n", (int) q->kind)); + return ERR_INVALID; + } +} + +static void bswap_ownership_qospolicy (nn_ownership_qospolicy_t *q) +{ + q->kind = bswap4u (q->kind); +} + +int validate_ownership_qospolicy (const nn_ownership_qospolicy_t *q) +{ + switch (q->kind) + { + case NN_SHARED_OWNERSHIP_QOS: + case NN_EXCLUSIVE_OWNERSHIP_QOS: + return 0; + default: + TRACE (("plist/validate_ownership_qospolicy: invalid kind (%d)\n", (int) q->kind)); + return ERR_INVALID; + } +} + +static void bswap_ownership_strength_qospolicy (nn_ownership_strength_qospolicy_t *q) +{ + q->value = bswap4 (q->value); +} + +int validate_ownership_strength_qospolicy (UNUSED_ARG (const nn_ownership_strength_qospolicy_t *q)) +{ + return 1; +} + +static void bswap_presentation_qospolicy (nn_presentation_qospolicy_t *q) +{ + q->access_scope = bswap4u (q->access_scope); +} + +int validate_presentation_qospolicy (const nn_presentation_qospolicy_t *q) +{ + switch (q->access_scope) + { + case NN_INSTANCE_PRESENTATION_QOS: + case NN_TOPIC_PRESENTATION_QOS: + case NN_GROUP_PRESENTATION_QOS: + break; + default: + TRACE (("plist/validate_presentation_qospolicy: invalid access_scope (%d)\n", (int) q->access_scope)); + return ERR_INVALID; + } + /* Bools must be 0 or 1, i.e., only the lsb may be set */ + if (q->coherent_access & ~1) + { + TRACE (("plist/validate_presentation_qospolicy: coherent_access invalid (%d)\n", (int) q->coherent_access)); + return ERR_INVALID; + } + if (q->ordered_access & ~1) + { + TRACE (("plist/validate_presentation_qospolicy: ordered_access invalid (%d)\n", (int) q->ordered_access)); + return ERR_INVALID; + } + /* coherent_access & ordered_access are a bit irrelevant for + instance presentation qos, but it appears as if their values are + not prescribed in that case. */ + return 0; +} + +static void bswap_transport_priority_qospolicy (nn_transport_priority_qospolicy_t *q) +{ + q->value = bswap4 (q->value); +} + +int validate_transport_priority_qospolicy (UNUSED_ARG (const nn_transport_priority_qospolicy_t *q)) +{ + return 1; +} + +static int add_locator (nn_locators_t *ls, uint64_t *present, uint64_t wanted, uint64_t fl, const nn_locator_t *loc) +{ + if (wanted & fl) + { + struct nn_locators_one *nloc; + if (!(*present & fl)) + { + ls->n = 0; + ls->first = NULL; + ls->last = NULL; + } + nloc = os_malloc (sizeof (*nloc)); + nloc->loc = *loc; + nloc->next = NULL; + if (ls->first == NULL) + ls->first = nloc; + else + { + assert (ls->last != NULL); + ls->last->next = nloc; + } + ls->last = nloc; + ls->n++; + *present |= fl; + } + return 0; +} + +static int locator_address_prefix12_zero (const nn_locator_t *loc) +{ + /* loc has has 32 bit ints preceding the address, hence address is + 4-byte aligned; reading char* as unsigneds isn't illegal type + punning */ + const unsigned *u = (const unsigned *) loc->address; + return (u[0] == 0 && u[1] == 0 && u[2] == 0); +} + +static int locator_address_zero (const nn_locator_t *loc) +{ + /* see locator_address_prefix12_zero */ + const unsigned *u = (const unsigned *) loc->address; + return (u[0] == 0 && u[1] == 0 && u[2] == 0 && u[3] == 0); +} + +static int do_locator +( + nn_locators_t *ls, + uint64_t *present, + uint64_t wanted, + uint64_t fl, + const struct dd *dd +) +{ + nn_locator_t loc; + + if (dd->bufsz < sizeof (loc)) + { + TRACE (("plist/do_locator: buffer too small\n")); + return ERR_INVALID; + } + memcpy (&loc, dd->buf, sizeof (loc)); + if (dd->bswap) + { + loc.kind = bswap4 (loc.kind); + loc.port = bswap4u (loc.port); + } + switch (loc.kind) + { + case NN_LOCATOR_KIND_UDPv4: + case NN_LOCATOR_KIND_TCPv4: + if (loc.port <= 0 || loc.port > 65535) + { + TRACE (("plist/do_locator[kind=IPv4]: invalid port (%d)\n", (int) loc.port)); + return ERR_INVALID; + } + if (!locator_address_prefix12_zero (&loc)) + { + TRACE (("plist/do_locator[kind=IPv4]: junk in address prefix\n")); + return ERR_INVALID; + } + break; + case NN_LOCATOR_KIND_UDPv6: + case NN_LOCATOR_KIND_TCPv6: + if (loc.port <= 0 || loc.port > 65535) + { + TRACE (("plist/do_locator[kind=IPv6]: invalid port (%d)\n", (int) loc.port)); + return ERR_INVALID; + } + break; + case NN_LOCATOR_KIND_UDPv4MCGEN: { + const nn_udpv4mcgen_address_t *x = (const nn_udpv4mcgen_address_t *) loc.address; + if (config.useIpv6 || config.tcp_enable) + return 0; + if (loc.port <= 0 || loc.port > 65536) + { + TRACE (("plist/do_locator[kind=IPv4MCGEN]: invalid port (%d)\n", (int) loc.port)); + return ERR_INVALID; + } + if ((int)x->base + x->count >= 28 || x->count == 0 || x->idx >= x->count) + { + TRACE (("plist/do_locator[kind=IPv4MCGEN]: invalid base/count/idx (%u,%u,%u)\n", x->base, x->count, x->idx)); + return ERR_INVALID; + } + break; + } + case NN_LOCATOR_KIND_INVALID: + if (!locator_address_zero (&loc)) + { + TRACE (("plist/do_locator[kind=INVALID]: junk in address\n")); + return ERR_INVALID; + } + if (loc.port != 0) + { + TRACE (("plist/do_locator[kind=INVALID]: junk in port\n")); + return ERR_INVALID; + } + /* silently dropped correctly formatted "invalid" locators. */ + return 0; + case NN_LOCATOR_KIND_RESERVED: + /* silently dropped "reserved" locators. */ + return 0; + default: + TRACE (("plist/do_locator: invalid kind (%d)\n", (int) loc.kind)); + return NN_PEDANTIC_P ? ERR_INVALID : 0; + } + return add_locator (ls, present, wanted, fl, &loc); +} + +static void locator_from_ipv4address_port (nn_locator_t *loc, const nn_ipv4address_t *a, const nn_port_t *p) +{ + loc->kind = gv.m_factory->m_connless ? NN_LOCATOR_KIND_UDPv4 : NN_LOCATOR_KIND_TCPv4; + loc->port = *p; + memset (loc->address, 0, 12); + memcpy (loc->address + 12, a, 4); +} + +static int do_ipv4address (nn_plist_t *dest, nn_ipaddress_params_tmp_t *dest_tmp, uint64_t wanted, unsigned fl_tmp, const struct dd *dd) +{ + nn_ipv4address_t *a; + nn_port_t *p; + nn_locators_t *ls; + unsigned fl1_tmp; + uint64_t fldest; + if (dd->bufsz < sizeof (*a)) + { + TRACE (("plist/do_ipv4address: buffer too small\n")); + return ERR_INVALID; + } + switch (fl_tmp) + { + case PPTMP_MULTICAST_IPADDRESS: + a = &dest_tmp->multicast_ipaddress; + p = NULL; /* don't know which port to use ... */ + fl1_tmp = 0; + fldest = PP_MULTICAST_LOCATOR; + ls = &dest->multicast_locators; + break; + case PPTMP_DEFAULT_UNICAST_IPADDRESS: + a = &dest_tmp->default_unicast_ipaddress; + p = &dest_tmp->default_unicast_port; + fl1_tmp = PPTMP_DEFAULT_UNICAST_PORT; + fldest = PP_DEFAULT_UNICAST_LOCATOR; + ls = &dest->unicast_locators; + break; + case PPTMP_METATRAFFIC_UNICAST_IPADDRESS: + a = &dest_tmp->metatraffic_unicast_ipaddress; + p = &dest_tmp->metatraffic_unicast_port; + fl1_tmp = PPTMP_METATRAFFIC_UNICAST_PORT; + fldest = PP_METATRAFFIC_UNICAST_LOCATOR; + ls = &dest->metatraffic_unicast_locators; + break; + case PPTMP_METATRAFFIC_MULTICAST_IPADDRESS: + a = &dest_tmp->metatraffic_multicast_ipaddress; + p = &dest_tmp->metatraffic_multicast_port; + fl1_tmp = PPTMP_METATRAFFIC_MULTICAST_PORT; + fldest = PP_METATRAFFIC_MULTICAST_LOCATOR; + ls = &dest->metatraffic_multicast_locators; + break; + default: + abort (); + } + memcpy (a, dd->buf, sizeof (*a)); + dest_tmp->present |= fl_tmp; + + /* PPTMP_MULTICAST_IPADDRESS must fail because we don't have a port. + (There are of course other ways of failing ...) Option 1: set + fl1 to a value to bit that's never set; option 2: explicit check. + Since this code hardly ever gets executed, use option 2. */ + + if (fl1_tmp && ((dest_tmp->present & (fl_tmp | fl1_tmp)) == (fl_tmp | fl1_tmp))) + { + /* If port already known, add corresponding locator and discard + both address & port from the set of present plist: this + allows adding another pair. */ + + nn_locator_t loc; + locator_from_ipv4address_port (&loc, a, p); + dest_tmp->present &= ~(fl_tmp | fl1_tmp); + return add_locator (ls, &dest->present, wanted, fldest, &loc); + } + else + { + return 0; + } +} + +static int do_port (nn_plist_t *dest, nn_ipaddress_params_tmp_t *dest_tmp, uint64_t wanted, unsigned fl_tmp, const struct dd *dd) +{ + nn_ipv4address_t *a; + nn_port_t *p; + nn_locators_t *ls; + uint64_t fldest; + unsigned fl1_tmp; + if (dd->bufsz < sizeof (*p)) + { + TRACE (("plist/do_port: buffer too small\n")); + return ERR_INVALID; + } + switch (fl_tmp) + { + case PPTMP_DEFAULT_UNICAST_PORT: + a = &dest_tmp->default_unicast_ipaddress; + p = &dest_tmp->default_unicast_port; + fl1_tmp = PPTMP_DEFAULT_UNICAST_IPADDRESS; + fldest = PP_DEFAULT_UNICAST_LOCATOR; + ls = &dest->unicast_locators; + break; + case PPTMP_METATRAFFIC_UNICAST_PORT: + a = &dest_tmp->metatraffic_unicast_ipaddress; + p = &dest_tmp->metatraffic_unicast_port; + fl1_tmp = PPTMP_METATRAFFIC_UNICAST_IPADDRESS; + fldest = PP_METATRAFFIC_UNICAST_LOCATOR; + ls = &dest->metatraffic_unicast_locators; + break; + case PPTMP_METATRAFFIC_MULTICAST_PORT: + a = &dest_tmp->metatraffic_multicast_ipaddress; + p = &dest_tmp->metatraffic_multicast_port; + fl1_tmp = PPTMP_METATRAFFIC_MULTICAST_IPADDRESS; + fldest = PP_METATRAFFIC_MULTICAST_LOCATOR; + ls = &dest->metatraffic_multicast_locators; + break; + default: + abort (); + } + memcpy (p, dd->buf, sizeof (*p)); + if (dd->bswap) + *p = bswap4u (*p); + if (*p <= 0 || *p > 65535) + { + TRACE (("plist/do_port: invalid port (%d)\n", (int) *p)); + return ERR_INVALID; + } + dest_tmp->present |= fl_tmp; + if ((dest_tmp->present & (fl_tmp | fl1_tmp)) == (fl_tmp | fl1_tmp)) + { + /* If port already known, add corresponding locator and discard + both address & port from the set of present plist: this + allows adding another pair. */ + nn_locator_t loc; + locator_from_ipv4address_port (&loc, a, p); + dest_tmp->present &= ~(fl_tmp | fl1_tmp); + return add_locator (ls, &dest->present, wanted, fldest, &loc); + } + else + { + return 0; + } +} + +static int valid_participant_guid (const nn_guid_t *g, UNUSED_ARG (const struct dd *dd)) +{ + /* All 0 is GUID_UNKNOWN, which is a defined GUID */ + if (g->prefix.u[0] == 0 && g->prefix.u[1] == 0 && g->prefix.u[2] == 0) + { + if (g->entityid.u == 0) + return 0; + else + { + TRACE (("plist/valid_participant_guid: prefix is 0 but entityid is not (%u)\n", g->entityid.u)); + return ERR_INVALID; + } + } + else if (g->entityid.u == NN_ENTITYID_PARTICIPANT) + { + return 0; + } + else + { + TRACE (("plist/valid_participant_guid: entityid not a participant entityid (%u)\n", g->entityid.u)); + return ERR_INVALID; + } +} + +static int valid_group_guid (const nn_guid_t *g, UNUSED_ARG (const struct dd *dd)) +{ + /* All 0 is GUID_UNKNOWN, which is a defined GUID */ + if (g->prefix.u[0] == 0 && g->prefix.u[1] == 0 && g->prefix.u[2] == 0) + { + if (g->entityid.u == 0) + return 0; + else + { + TRACE (("plist/valid_group_guid: prefix is 0 but entityid is not (%u)\n", g->entityid.u)); + return ERR_INVALID; + } + } + else if (g->entityid.u != 0) + { + /* accept any entity id */ + return 0; + } + else + { + TRACE (("plist/valid_group_guid: entityid is 0\n")); + return ERR_INVALID; + } +} + +static int valid_endpoint_guid (const nn_guid_t *g, const struct dd *dd) +{ + /* All 0 is GUID_UNKNOWN, which is a defined GUID */ + if (g->prefix.u[0] == 0 && g->prefix.u[1] == 0 && g->prefix.u[2] == 0) + { + if (g->entityid.u == 0) + return 0; + else + { + TRACE (("plist/valid_endpoint_guid: prefix is 0 but entityid is not (%x)\n", g->entityid.u)); + return ERR_INVALID; + } + } + switch (g->entityid.u & NN_ENTITYID_SOURCE_MASK) + { + case NN_ENTITYID_SOURCE_USER: + switch (g->entityid.u & NN_ENTITYID_KIND_MASK) + { + case NN_ENTITYID_KIND_WRITER_WITH_KEY: + case NN_ENTITYID_KIND_WRITER_NO_KEY: + case NN_ENTITYID_KIND_READER_NO_KEY: + case NN_ENTITYID_KIND_READER_WITH_KEY: + return 0; + default: + if (protocol_version_is_newer (dd->protocol_version)) + return 0; + else + { + TRACE (("plist/valid_endpoint_guid[src=USER,proto=%u.%u]: invalid kind (%x)\n", + dd->protocol_version.major, dd->protocol_version.minor, + g->entityid.u & NN_ENTITYID_KIND_MASK)); + return ERR_INVALID; + } + } + case NN_ENTITYID_SOURCE_BUILTIN: + switch (g->entityid.u) + { + case NN_ENTITYID_SEDP_BUILTIN_TOPIC_WRITER: + case NN_ENTITYID_SEDP_BUILTIN_TOPIC_READER: + case NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_WRITER: + case NN_ENTITYID_SEDP_BUILTIN_PUBLICATIONS_READER: + case NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_WRITER: + case NN_ENTITYID_SEDP_BUILTIN_SUBSCRIPTIONS_READER: + case NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER: + case NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_READER: + case NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_WRITER: + case NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_READER: + return 0; + default: + if (protocol_version_is_newer (dd->protocol_version)) + return 0; + else + { + TRACE (("plist/valid_endpoint_guid[src=BUILTIN,proto=%u.%u]: invalid entityid (%x)\n", + dd->protocol_version.major, dd->protocol_version.minor, g->entityid.u)); + return ERR_INVALID; + } + } + case NN_ENTITYID_SOURCE_VENDOR: + /* vendor specific: always ok, unless vendor is PrismTech, 'cos + we don't do that! (FIXME, might be worthwhile to be less + strict, or add an implementation version number here) */ + if (!is_own_vendor (dd->vendorid) || protocol_version_is_newer (dd->protocol_version)) + return 0; + else + { + switch (g->entityid.u) + { + case NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_WRITER: + case NN_ENTITYID_SEDP_BUILTIN_CM_PARTICIPANT_READER: + case NN_ENTITYID_SEDP_BUILTIN_CM_PUBLISHER_WRITER: + case NN_ENTITYID_SEDP_BUILTIN_CM_PUBLISHER_READER: + case NN_ENTITYID_SEDP_BUILTIN_CM_SUBSCRIBER_WRITER: + case NN_ENTITYID_SEDP_BUILTIN_CM_SUBSCRIBER_READER: + return 0; + default: + if (protocol_version_is_newer (dd->protocol_version)) + return 0; + else + { + TRACE (("plist/valid_endpoint_guid[src=VENDOR,proto=%u.%u]: invalid entityid (%x)\n", + dd->protocol_version.major, dd->protocol_version.minor, g->entityid.u)); + return 0; + } + } + } + default: + TRACE (("plist/valid_endpoint_guid: invalid source (%x)\n", g->entityid.u)); + return ERR_INVALID; + } +} + +static int do_guid (nn_guid_t *dst, uint64_t *present, uint64_t fl, int (*valid) (const nn_guid_t *g, const struct dd *dd), const struct dd *dd) +{ + if (dd->bufsz < sizeof (*dst)) + { + TRACE (("plist/do_guid: buffer too small\n")); + return ERR_INVALID; + } + memcpy (dst, dd->buf, sizeof (*dst)); + *dst = nn_ntoh_guid (*dst); + if (valid (dst, dd) < 0) + { + if (fl == PP_PARTICIPANT_GUID && vendor_is_twinoaks (dd->vendorid) && + dst->entityid.u == 0 && ! NN_STRICT_P) + { + NN_WARNING ("plist(vendor %u.%u): rewriting invalid participant guid %x:%x:%x:%x\n", + dd->vendorid.id[0], dd->vendorid.id[1], + dst->prefix.u[0], dst->prefix.u[1], dst->prefix.u[2], dst->entityid.u); + dst->entityid.u = NN_ENTITYID_PARTICIPANT; + } + else + { + return ERR_INVALID; + } + } + *present |= fl; + return 0; +} + + +static void bswap_prismtech_participant_version_info (nn_prismtech_participant_version_info_t *pvi) +{ + int i; + pvi->version = bswap4u (pvi->version); + pvi->flags = bswap4u (pvi->flags); + for (i = 0; i < 3; i++) + pvi->unused[i] = bswap4u (pvi->unused[i]); +} + +static int do_prismtech_participant_version_info (nn_prismtech_participant_version_info_t *pvi, uint64_t *present, uint64_t *aliased, const struct dd *dd) +{ + if (!vendor_is_prismtech (dd->vendorid)) + return 0; + else if (dd->bufsz < NN_PRISMTECH_PARTICIPANT_VERSION_INFO_FIXED_CDRSIZE) + { + TRACE (("plist/do_prismtech_participant_version_info[pid=PRISMTECH_PARTICIPANT_VERSION_INFO]: buffer too small\n")); + return ERR_INVALID; + } + else + { + int res; + unsigned sz = NN_PRISMTECH_PARTICIPANT_VERSION_INFO_FIXED_CDRSIZE - sizeof(unsigned); + unsigned *pu = (unsigned *)dd->buf; + unsigned len; + struct dd dd1 = *dd; + + memcpy (pvi, dd->buf, sz); + if (dd->bswap) + bswap_prismtech_participant_version_info(pvi); + + dd1.buf = (unsigned char *) &pu[5]; + dd1.bufsz = dd->bufsz - sz; + if ((res = alias_string ((const unsigned char **) &pvi->internals, &dd1, &len)) >= 0) { + *present |= PP_PRISMTECH_PARTICIPANT_VERSION_INFO; + *aliased |= PP_PRISMTECH_PARTICIPANT_VERSION_INFO; + res = 0; + } + + return res; + } +} + + + +static int do_subscription_keys_qospolicy (nn_subscription_keys_qospolicy_t *q, uint64_t *present, uint64_t *aliased, uint64_t fl, const struct dd *dd) +{ + struct dd dd1; + int res; + if (dd->bufsz < 4) + { + TRACE (("plist/do_subscription_keys: buffer too small\n")); + return ERR_INVALID; + } + q->use_key_list = (unsigned char) dd->buf[0]; + if (q->use_key_list != 0 && q->use_key_list != 1) + { + TRACE (("plist/do_subscription_keys: invalid use_key_list (%d)\n", (int) q->use_key_list)); + return ERR_INVALID; + } + dd1 = *dd; + dd1.buf += 4; + dd1.bufsz -= 4; + if ((res = alias_stringseq (&q->key_list, &dd1)) >= 0) + { + *present |= fl; + *aliased |= fl; + } + return res; +} + +static int unalias_subscription_keys_qospolicy (nn_subscription_keys_qospolicy_t *q, int bswap) +{ + return unalias_stringseq (&q->key_list, bswap); +} + +static int do_reader_lifespan_qospolicy (nn_reader_lifespan_qospolicy_t *q, uint64_t *present, uint64_t fl, const struct dd *dd) +{ + int res; + if (dd->bufsz < sizeof (*q)) + { + TRACE (("plist/do_reader_lifespan: buffer too small\n")); + return ERR_INVALID; + } + *q = *((nn_reader_lifespan_qospolicy_t *) dd->buf); + if (dd->bswap) + bswap_duration (&q->duration); + if (q->use_lifespan != 0 && q->use_lifespan != 1) + { + TRACE (("plist/do_reader_lifespan: invalid use_lifespan (%d)\n", (int) q->use_lifespan)); + return ERR_INVALID; + } + if ((res = validate_duration (&q->duration)) >= 0) + *present |= fl; + return res; +} + +static int do_entity_factory_qospolicy (nn_entity_factory_qospolicy_t *q, uint64_t *present, uint64_t fl, const struct dd *dd) +{ + if (dd->bufsz < sizeof (*q)) + { + TRACE (("plist/do_entity_factory: buffer too small\n")); + return ERR_INVALID; + } + q->autoenable_created_entities = dd->buf[0]; + if (q->autoenable_created_entities != 0 && q->autoenable_created_entities != 1) + { + TRACE (("plist/do_entity_factory: invalid autoenable_created_entities (%d)\n", (int) q->autoenable_created_entities)); + return ERR_INVALID; + } + *present |= fl; + return 0; +} + +int validate_reader_data_lifecycle (const nn_reader_data_lifecycle_qospolicy_t *q) +{ + if (validate_duration (&q->autopurge_nowriter_samples_delay) < 0 || + validate_duration (&q->autopurge_disposed_samples_delay) < 0) + { + TRACE (("plist/init_one_parameter[pid=PRISMTECH_READER_DATA_LIFECYCLE]: invalid autopurge_nowriter_sample_delay or autopurge_disposed_samples_delay\n")); + return ERR_INVALID; + } + if (q->autopurge_dispose_all != 0 && q->autopurge_dispose_all != 1) + { + TRACE (("plist/init_one_parameter[pid=PRISMTECH_READER_DATA_LIFECYCLE]: invalid autopurge_dispose_all\n")); + return ERR_INVALID; + } + if (q->enable_invalid_samples != 0 && q->enable_invalid_samples != 1) + { + TRACE (("plist/init_one_parameter[pid=PRISMTECH_READER_DATA_LIFECYCLE]: invalid enable_invalid_samples\n")); + return ERR_INVALID; + } + /* Don't check consistency between enable_invalid_samples and invalid_samples_mode (yet) */ + switch (q->invalid_sample_visibility) + { + case NN_NO_INVALID_SAMPLE_VISIBILITY_QOS: + case NN_MINIMUM_INVALID_SAMPLE_VISIBILITY_QOS: + case NN_ALL_INVALID_SAMPLE_VISIBILITY_QOS: + break; + default: + TRACE (("plist/init_one_parameter[pid=PRISMTECH_READER_DATA_LIFECYCLE]: invalid invalid_sample_visibility\n")); + return ERR_INVALID; + } + return 0; +} + +static int do_reader_data_lifecycle_v0 (nn_reader_data_lifecycle_qospolicy_t *q, const struct dd *dd) +{ + memcpy (q, dd->buf, 2 * sizeof (nn_duration_t)); + q->autopurge_dispose_all = 0; + q->enable_invalid_samples = 1; + q->invalid_sample_visibility = NN_MINIMUM_INVALID_SAMPLE_VISIBILITY_QOS; + if (dd->bswap) + { + bswap_duration (&q->autopurge_nowriter_samples_delay); + bswap_duration (&q->autopurge_disposed_samples_delay); + } + return validate_reader_data_lifecycle (q); +} + +static int do_reader_data_lifecycle_v1 (nn_reader_data_lifecycle_qospolicy_t *q, const struct dd *dd) +{ + memcpy (q, dd->buf, sizeof (*q)); + if (dd->bswap) + { + bswap_duration (&q->autopurge_nowriter_samples_delay); + bswap_duration (&q->autopurge_disposed_samples_delay); + q->invalid_sample_visibility = (nn_invalid_sample_visibility_kind_t) bswap4u ((unsigned) q->invalid_sample_visibility); + } + return validate_reader_data_lifecycle (q); +} + +_Success_(return == 0) +static int do_dataholder (_Out_ nn_dataholder_t *dh, _Inout_ uint64_t *present, _Inout_ uint64_t *aliased, _In_ uint64_t wanted, _In_ uint64_t fl, _In_ const struct dd *dd) +{ + struct dd ddtmp = *dd; + unsigned len; + int res; + + memset(dh, 0, sizeof(nn_dataholder_t)); + + if (!(wanted & fl)) + { + res = 0; + if (NN_STRICT_P) + { + /* check class_id */ + if ((res = validate_string (&ddtmp, &len)) == 0) + { + len = sizeof(unsigned) + /* cdr string len arg + */ + align4u(len); /* strlen + possible padding */ + /* check properties */ + ddtmp.buf = &(dd->buf[len]); + ddtmp.bufsz = dd->bufsz - len; + if ((res = validate_propertyseq (&ddtmp, &len)) == 0) + { + /* check binary properties */ + ddtmp.buf = &(ddtmp.buf[len]); + ddtmp.bufsz = ddtmp.bufsz - len; + if ((res = validate_binarypropertyseq (&ddtmp, &len)) != 0) + { + TRACE (("plist/do_dataholder: invalid binary_property_seq\n")); + } + } + else + { + TRACE (("plist/do_dataholder: invalid property_seq\n")); + } + } + else + { + TRACE (("plist/do_dataholder: invalid class_id\n")); + } + } + return res; + } + + /* get class_id */ + res = alias_string((const unsigned char **)&(dh->class_id), dd, &len /* strlen */); + if (res != 0) + { + TRACE (("plist/do_dataholder: invalid class_id\n")); + return res; + } + len = sizeof(unsigned) + /* cdr string len arg + */ + align4u(len); /* strlen + possible padding */ + + /* get properties */ + ddtmp.buf = &(dd->buf[len]); + ddtmp.bufsz = dd->bufsz - len; + res = alias_propertyseq(&(dh->properties), &ddtmp, &len /* complete length */); + if (res != 0) + { + TRACE (("plist/do_dataholder: invalid property_seq\n")); + return res; + } + + /* get binary properties */ + ddtmp.buf = &(ddtmp.buf[len]); + ddtmp.bufsz = ddtmp.bufsz - len; + res = alias_binarypropertyseq(&(dh->binary_properties), &ddtmp, &len /* complete length */); + if (res != 0) + { + TRACE (("plist/do_dataholder: invalid binary_property_seq\n")); + return res; + } + + *present |= fl; + *aliased |= fl; + + return 0; +} + +static int init_one_parameter +( + nn_plist_t *dest, + nn_ipaddress_params_tmp_t *dest_tmp, + uint64_t pwanted, + uint64_t qwanted, + unsigned short pid, + const struct dd *dd +) +{ + int res; + switch (pid) + { + case PID_PAD: + case PID_SENTINEL: + return 0; + + /* Extended QoS data: */ +#define Q(NAME_, name_) case PID_##NAME_: \ + if (dd->bufsz < sizeof (nn_##name_##_qospolicy_t)) \ + { \ + TRACE (("plist/init_one_parameter[pid=%s]: buffer too small\n", #NAME_)); \ + return ERR_INVALID; \ + } \ + else \ + { \ + nn_##name_##_qospolicy_t *q = &dest->qos.name_; \ + memcpy (q, dd->buf, sizeof (*q)); \ + if (dd->bswap) bswap_##name_##_qospolicy (q); \ + if ((res = validate_##name_##_qospolicy (q)) < 0) \ + return res; \ + dest->qos.present |= QP_##NAME_; \ + } \ + return 0 + Q (DURABILITY, durability); + Q (DURABILITY_SERVICE, durability_service); + Q (LIVELINESS, liveliness); + Q (DESTINATION_ORDER, destination_order); + Q (HISTORY, history); + Q (RESOURCE_LIMITS, resource_limits); + Q (OWNERSHIP, ownership); + Q (OWNERSHIP_STRENGTH, ownership_strength); + Q (PRESENTATION, presentation); + Q (TRANSPORT_PRIORITY, transport_priority); +#undef Q + + /* PID_RELIABILITY handled differently because it (formally, for + static typing reasons) has a different type on the network + than internally, with the transformation between the two + dependent on wheter we are being pedantic. If that weren't + the case, it would've been an ordinary Q (RELIABILITY, + reliability). */ + case PID_RELIABILITY: + if (dd->bufsz < sizeof (nn_external_reliability_qospolicy_t)) + { + TRACE (("plist/init_one_parameter[pid=RELIABILITY]: buffer too small\n")); + return ERR_INVALID; + } + else + { + nn_reliability_qospolicy_t *q = &dest->qos.reliability; + nn_external_reliability_qospolicy_t qext; + memcpy (&qext, dd->buf, sizeof (qext)); + if (dd->bswap) + bswap_external_reliability_qospolicy (&qext); + if ((res = validate_xform_reliability_qospolicy (q, &qext)) < 0) + return res; + dest->qos.present |= QP_RELIABILITY; + } + return 0; + + case PID_TOPIC_NAME: + return do_string (&dest->qos.topic_name, &dest->qos.present, &dest->qos.aliased, qwanted, QP_TOPIC_NAME, dd); + case PID_TYPE_NAME: + return do_string (&dest->qos.type_name, &dest->qos.present, &dest->qos.aliased, qwanted, QP_TYPE_NAME, dd); + + case PID_USER_DATA: + return do_octetseq (&dest->qos.user_data, &dest->qos.present, &dest->qos.aliased, qwanted, QP_USER_DATA, dd); + case PID_GROUP_DATA: + return do_octetseq (&dest->qos.group_data, &dest->qos.present, &dest->qos.aliased, qwanted, QP_GROUP_DATA, dd); + case PID_TOPIC_DATA: + return do_octetseq (&dest->qos.topic_data, &dest->qos.present, &dest->qos.aliased, qwanted, QP_TOPIC_DATA, dd); + + case PID_DEADLINE: + return do_duration (&dest->qos.deadline.deadline, &dest->qos.present, QP_DEADLINE, dd); + case PID_LATENCY_BUDGET: + return do_duration (&dest->qos.latency_budget.duration, &dest->qos.present, QP_LATENCY_BUDGET, dd); + case PID_LIFESPAN: + return do_duration (&dest->qos.lifespan.duration, &dest->qos.present, QP_LIFESPAN, dd); + case PID_TIME_BASED_FILTER: + return do_duration (&dest->qos.time_based_filter.minimum_separation, &dest->qos.present, QP_TIME_BASED_FILTER, dd); + + case PID_PARTITION: + return do_stringseq (&dest->qos.partition, &dest->qos.present, &dest->qos.aliased, qwanted, QP_PARTITION, dd); + + case PID_PRISMTECH_READER_DATA_LIFECYCLE: /* PrismTech specific */ + { + int ret; + if (!vendor_is_prismtech (dd->vendorid)) + return 0; + if (dd->bufsz >= sizeof (nn_reader_data_lifecycle_qospolicy_t)) + ret = do_reader_data_lifecycle_v1 (&dest->qos.reader_data_lifecycle, dd); + else if (dd->bufsz >= 2 * sizeof (nn_duration_t)) + ret = do_reader_data_lifecycle_v0 (&dest->qos.reader_data_lifecycle, dd); + else + { + TRACE (("plist/init_one_parameter[pid=PRISMTECH_READER_DATA_LIFECYCLE]: buffer too small\n")); + ret = ERR_INVALID; + } + if (ret >= 0) + dest->qos.present |= QP_PRISMTECH_READER_DATA_LIFECYCLE; + return ret; + } + case PID_PRISMTECH_WRITER_DATA_LIFECYCLE: /* PrismTech specific */ + if (!vendor_is_prismtech (dd->vendorid)) + return 0; + else + { + nn_writer_data_lifecycle_qospolicy_t *q = &dest->qos.writer_data_lifecycle; + if (dd->bufsz < 1) + { + TRACE (("plist/init_one_parameter[pid=PRISMTECH_WRITER_DATA_LIFECYCLE]: buffer too small\n")); + return ERR_INVALID; + } + else if (dd->bufsz < sizeof (*q)) + { + /* Spec form, with just autodispose_unregistered_instances */ + q->autodispose_unregistered_instances = dd->buf[0]; + q->autounregister_instance_delay = nn_to_ddsi_duration (T_NEVER); + q->autopurge_suspended_samples_delay = nn_to_ddsi_duration (T_NEVER); + } + else + { + memcpy (q, dd->buf, sizeof (*q)); + if (dd->bswap) + { + bswap_duration (&q->autounregister_instance_delay); + bswap_duration (&q->autopurge_suspended_samples_delay); + } + } + if (q->autodispose_unregistered_instances & ~1) + { + TRACE (("plist/init_one_parameter[pid=PRISMTECH_WRITER_DATA_LIFECYCLE]: invalid autodispose_unregistered_instances (%d)\n", (int) q->autodispose_unregistered_instances)); + return ERR_INVALID; + } + if (validate_duration (&q->autounregister_instance_delay) < 0 || + validate_duration (&q->autopurge_suspended_samples_delay) < 0) + { + TRACE (("plist/init_one_parameter[pid=PRISMTECH_WRITER_DATA_LIFECYCLE]: invalid autounregister_instance_delay or autopurge_suspended_samples_delay\n")); + return ERR_INVALID; + } + dest->qos.present |= QP_PRISMTECH_WRITER_DATA_LIFECYCLE; + return 0; + } + + case PID_PRISMTECH_RELAXED_QOS_MATCHING: + if (!vendor_is_prismtech (dd->vendorid)) + return 0; + else if (dd->bufsz < sizeof (dest->qos.relaxed_qos_matching)) + { + TRACE (("plist/init_one_parameter[pid=PRISMTECH_RELAXED_QOS_MATCHING]: buffer too small\n")); + return ERR_INVALID; + } + else + { + nn_relaxed_qos_matching_qospolicy_t *rqm = &dest->qos.relaxed_qos_matching; + memcpy (rqm, dd->buf, sizeof (*rqm)); + if (rqm->value != 0 && rqm->value != 1) + { + TRACE (("plist/init_one_parameter[pid=PRISMTECH_RELAXED_QOS_MATCHING]: invalid\n")); + return ERR_INVALID; + } + dest->qos.present |= QP_PRISMTECH_RELAXED_QOS_MATCHING; + return 0; + } + + case PID_PRISMTECH_SYNCHRONOUS_ENDPOINT: /* PrismTech specific */ + if (!vendor_is_prismtech (dd->vendorid)) + return 0; + else if (dd->bufsz < sizeof (dest->qos.synchronous_endpoint)) + { + TRACE (("plist/init_one_parameter[pid=PRISMTECH_SYNCHRONOUS_ENDPOINT]: buffer too small\n")); + return ERR_INVALID; + } + else + { + nn_synchronous_endpoint_qospolicy_t *q = &dest->qos.synchronous_endpoint; + memcpy (q, dd->buf, sizeof (*q)); + if (q->value != 0 && q->value != 1) + { + TRACE (("plist/init_one_parameter[pid=PRISMTECH_SYNCHRONOUS_ENDPOINT]: invalid value for synchronous flag\n")); + return ERR_INVALID; + } + dest->qos.present |= QP_PRISMTECH_SYNCHRONOUS_ENDPOINT; + return 0; + } + + /* Other plist */ + case PID_PROTOCOL_VERSION: + if (dd->bufsz < sizeof (nn_protocol_version_t)) + { + TRACE (("plist/init_one_parameter[pid=PROTOCOL_VERSION]: buffer too small\n")); + return ERR_INVALID; + } + memcpy (&dest->protocol_version, dd->buf, sizeof (dest->protocol_version)); + if (NN_STRICT_P && + (dest->protocol_version.major != dd->protocol_version.major || + dest->protocol_version.minor != dd->protocol_version.minor)) + { + /* Not accepting a submessage advertising a protocol version + other than that advertised by the message header, unless I + have good reason to, at least not when being strict. */ + TRACE (("plist/init_one_parameter[pid=PROTOCOL_VERSION,mode=STRICT]: version (%u.%u) mismatch with message (%u.%u)\n", + dest->protocol_version.major, dest->protocol_version.minor, + dd->protocol_version.major, dd->protocol_version.minor)); + return ERR_INVALID; + } + dest->present |= PP_PROTOCOL_VERSION; + return 0; + + case PID_VENDORID: + if (dd->bufsz < sizeof (nn_vendorid_t)) + return ERR_INVALID; + memcpy (&dest->vendorid, dd->buf, sizeof (dest->vendorid)); + if (NN_STRICT_P && + (dest->vendorid.id[0] != dd->vendorid.id[0] || + dest->vendorid.id[1] != dd->vendorid.id[1])) + { + /* see PROTOCOL_VERSION */ + TRACE (("plist/init_one_parameter[pid=VENDORID,mode=STRICT]: vendor (%u.%u) mismatch with message (%u.%u)\n", + dest->vendorid.id[0], dest->vendorid.id[1], dd->vendorid.id[0], dd->vendorid.id[1])); + return ERR_INVALID; + } + dest->present |= PP_VENDORID; + return 0; + + /* Locators: there may be lists, so we have to allocate memory for them */ +#define XL(NAME_, name_) case PID_##NAME_##_LOCATOR: return do_locator (&dest->name_##_locators, &dest->present, pwanted, PP_##NAME_##_LOCATOR, dd) + XL (UNICAST, unicast); + XL (MULTICAST, multicast); + XL (DEFAULT_UNICAST, default_unicast); + XL (DEFAULT_MULTICAST, default_multicast); + XL (METATRAFFIC_UNICAST, metatraffic_unicast); + XL (METATRAFFIC_MULTICAST, metatraffic_multicast); +#undef XL + + /* IPADDRESS + PORT entries are a nuisance ... I'd prefer + converting them to locators right away, so that the rest of + the code only has to deal with locators, but that is + impossible because the locators require both the address & + the port to be known. + + The wireshark dissector suggests IPvAdress_t is just the 32 + bits of the IP address but it doesn't say so anywhere + ... Similarly for ports, but contrary to the expections they + seem to be 32-bits, too. Apparently in host-endianness. + + And, to be honest, I have no idea what port to use for + MULTICAST_IPADDRESS ... */ +#define XA(NAME_) case PID_##NAME_##_IPADDRESS: return do_ipv4address (dest, dest_tmp, pwanted, PPTMP_##NAME_##_IPADDRESS, dd) +#define XP(NAME_) case PID_##NAME_##_PORT: return do_port (dest, dest_tmp, pwanted, PPTMP_##NAME_##_PORT, dd) + XA (MULTICAST); + XA (DEFAULT_UNICAST); + XP (DEFAULT_UNICAST); + XA (METATRAFFIC_UNICAST); + XP (METATRAFFIC_UNICAST); + XA (METATRAFFIC_MULTICAST); + XP (METATRAFFIC_MULTICAST); +#undef XP +#undef XA + + case PID_EXPECTS_INLINE_QOS: + if (dd->bufsz < sizeof (dest->expects_inline_qos)) + { + TRACE (("plist/init_one_parameter[pid=EXPECTS_INLINE_QOS]: buffer too small\n")); + return ERR_INVALID; + } + dest->expects_inline_qos = dd->buf[0]; + /* boolean: only lsb may be set */ + if (dest->expects_inline_qos & ~1) + { + TRACE (("plist/init_one_parameter[pid=EXPECTS_INLINE_QOS]: invalid expects_inline_qos (%d)\n", + (int) dest->expects_inline_qos)); + return ERR_INVALID; + } + dest->present |= PP_EXPECTS_INLINE_QOS; + return 0; + + case PID_PARTICIPANT_MANUAL_LIVELINESS_COUNT: + /* Spec'd as "incremented monotonically" (DDSI 2.1, table 8.13), + but 32 bits signed is not such a smart choice for that. We'll + simply accept any value. */ + if (dd->bufsz < sizeof (dest->participant_manual_liveliness_count)) + { + TRACE (("plist/init_one_parameter[pid=PARTICIPANT_MANUAL_LIVELINESS_COUNT]: buffer too small\n")); + return ERR_INVALID; + } + memcpy (&dest->participant_manual_liveliness_count, dd->buf, sizeof (dest->participant_manual_liveliness_count)); + if (dd->bswap) + dest->participant_manual_liveliness_count = bswap4 (dest->participant_manual_liveliness_count); + dest->present |= PP_PARTICIPANT_MANUAL_LIVELINESS_COUNT; + return 0; + + case PID_PARTICIPANT_LEASE_DURATION: + return do_duration (&dest->participant_lease_duration, &dest->present, PP_PARTICIPANT_LEASE_DURATION, dd); + + case PID_CONTENT_FILTER_PROPERTY: + /* FIXME */ + return 0; + + case PID_PARTICIPANT_GUID: + return do_guid (&dest->participant_guid, &dest->present, PP_PARTICIPANT_GUID, valid_participant_guid, dd); + + case PID_GROUP_GUID: + return do_guid (&dest->group_guid, &dest->present, PP_GROUP_GUID, valid_group_guid, dd); + + case PID_PARTICIPANT_ENTITYID: + case PID_GROUP_ENTITYID: + /* DDSI 2.1 table 9.13: reserved for future use */ + return 0; + + case PID_PARTICIPANT_BUILTIN_ENDPOINTS: + /* FIXME: I assume it is the same as the BUILTIN_ENDPOINT_SET, + which is the set that DDSI2 has been using so far. */ + /* FALLS THROUGH */ + case PID_BUILTIN_ENDPOINT_SET: + if (dd->bufsz < sizeof (dest->builtin_endpoint_set)) + { + TRACE (("plist/init_one_parameter[pid=BUILTIN_ENDPOINT_SET(%u)]: buffer too small\n", pid)); + return ERR_INVALID; + } + memcpy (&dest->builtin_endpoint_set, dd->buf, sizeof (dest->builtin_endpoint_set)); + if (dd->bswap) + dest->builtin_endpoint_set = bswap4u (dest->builtin_endpoint_set); + if (NN_STRICT_P && !protocol_version_is_newer (dd->protocol_version) && + (dest->builtin_endpoint_set & ~(NN_DISC_BUILTIN_ENDPOINT_PARTICIPANT_ANNOUNCER | + NN_DISC_BUILTIN_ENDPOINT_PARTICIPANT_DETECTOR | + NN_DISC_BUILTIN_ENDPOINT_PUBLICATION_ANNOUNCER | + NN_DISC_BUILTIN_ENDPOINT_PUBLICATION_DETECTOR | + NN_DISC_BUILTIN_ENDPOINT_SUBSCRIPTION_ANNOUNCER | + NN_DISC_BUILTIN_ENDPOINT_SUBSCRIPTION_DETECTOR | + NN_DISC_BUILTIN_ENDPOINT_TOPIC_ANNOUNCER | + NN_DISC_BUILTIN_ENDPOINT_TOPIC_DETECTOR | + /* undefined ones: */ + NN_DISC_BUILTIN_ENDPOINT_PARTICIPANT_PROXY_ANNOUNCER | + NN_DISC_BUILTIN_ENDPOINT_PARTICIPANT_PROXY_DETECTOR | + NN_DISC_BUILTIN_ENDPOINT_PARTICIPANT_STATE_ANNOUNCER | + NN_DISC_BUILTIN_ENDPOINT_PARTICIPANT_STATE_DETECTOR | + /* defined ones again: */ + NN_BUILTIN_ENDPOINT_PARTICIPANT_MESSAGE_DATA_WRITER | + NN_BUILTIN_ENDPOINT_PARTICIPANT_MESSAGE_DATA_READER)) != 0) + { + TRACE (("plist/init_one_parameter[pid=BUILTIN_ENDPOINT_SET(%u),mode=STRICT,proto=%u.%u]: invalid set (0x%x)\n", + pid, dd->protocol_version.major, dd->protocol_version.minor, dest->builtin_endpoint_set)); + return ERR_INVALID; + } + dest->present |= PP_BUILTIN_ENDPOINT_SET; + return 0; + + case PID_PRISMTECH_BUILTIN_ENDPOINT_SET: + if (!vendor_is_prismtech (dd->vendorid)) + return 0; + else if (dd->bufsz < sizeof (dest->prismtech_builtin_endpoint_set)) + { + TRACE (("plist/init_one_parameter[pid=PRISMTECH_BUILTIN_ENDPOINT_SET(%u)]: buffer too small\n", pid)); + return ERR_INVALID; + } + else + { + memcpy (&dest->prismtech_builtin_endpoint_set, dd->buf, sizeof (dest->prismtech_builtin_endpoint_set)); + if (dd->bswap) + dest->prismtech_builtin_endpoint_set = bswap4u (dest->prismtech_builtin_endpoint_set); + dest->present |= PP_PRISMTECH_BUILTIN_ENDPOINT_SET; + } + return 0; + + case PID_PROPERTY_LIST: + case PID_TYPE_MAX_SIZE_SERIALIZED: + /* FIXME */ + return 0; + + case PID_ENTITY_NAME: + return do_string (&dest->entity_name, &dest->present, &dest->aliased, pwanted, PP_ENTITY_NAME, dd); + + case PID_KEYHASH: + if (dd->bufsz < sizeof (dest->keyhash)) + { + TRACE (("plist/init_one_parameter[pid=KEYHASH]: buffer too small\n")); + return ERR_INVALID; + } + memcpy (&dest->keyhash, dd->buf, sizeof (dest->keyhash)); + dest->present |= PP_KEYHASH; + return 0; + + case PID_STATUSINFO: + if (dd->bufsz < sizeof (dest->statusinfo)) + { + TRACE (("plist/init_one_parameter[pid=STATUSINFO]: buffer too small\n")); + return ERR_INVALID; + } + memcpy (&dest->statusinfo, dd->buf, sizeof (dest->statusinfo)); + dest->statusinfo = fromBE4u (dest->statusinfo); + if (NN_STRICT_P && !protocol_version_is_newer (dd->protocol_version) && + (dest->statusinfo & ~NN_STATUSINFO_STANDARDIZED)) + { + /* Spec says I may not interpret the reserved bits. But no-one + may use them in this version of the specification */ + TRACE (("plist/init_one_parameter[pid=STATUSINFO,mode=STRICT,proto=%u.%u]: invalid statusinfo (0x%x)\n", + dd->protocol_version.major, dd->protocol_version.minor, dest->statusinfo)); + return ERR_INVALID; + } + /* Clear all bits we don't understand, then add the extended bits if present */ + dest->statusinfo &= NN_STATUSINFO_STANDARDIZED; + if (dd->bufsz >= 2 * sizeof (dest->statusinfo) && vendor_is_opensplice(dd->vendorid)) + { + uint32_t statusinfox; + Q_STATIC_ASSERT_CODE (sizeof(statusinfox) == sizeof(dest->statusinfo)); + memcpy (&statusinfox, dd->buf + sizeof (dest->statusinfo), sizeof (statusinfox)); + statusinfox = fromBE4u (statusinfox); + if (statusinfox & NN_STATUSINFOX_OSPL_AUTO) + dest->statusinfo |= NN_STATUSINFO_OSPL_AUTO; + } + dest->present |= PP_STATUSINFO; + return 0; + + case PID_COHERENT_SET: + if (dd->bufsz < sizeof (dest->coherent_set_seqno)) + { + TRACE (("plist/init_one_parameter[pid=COHERENT_SET]: buffer too small\n")); + return ERR_INVALID; + } + else + { + nn_sequence_number_t *q = &dest->coherent_set_seqno; + seqno_t seqno; + memcpy (q, dd->buf, sizeof (*q)); + if (dd->bswap) + { + q->high = bswap4 (q->high); + q->low = bswap4u (q->low); + } + seqno = fromSN(dest->coherent_set_seqno); + if (seqno <= 0 && seqno != NN_SEQUENCE_NUMBER_UNKNOWN) + { + TRACE (("plist/init_one_parameter[pid=COHERENT_SET]: invalid sequence number (%" PRId64 ")\n", seqno)); + return ERR_INVALID; + } + dest->present |= PP_COHERENT_SET; + return 0; + } + + case PID_CONTENT_FILTER_INFO: + case PID_DIRECTED_WRITE: + case PID_ORIGINAL_WRITER_INFO: + /* FIXME */ + return 0; + + case PID_ENDPOINT_GUID: + if (NN_PEDANTIC_P && !protocol_version_is_newer (dd->protocol_version)) + { + /* ENDPOINT_GUID is not specified in the 2.1 standard, so + reject it: in (really) strict mode we do not accept + undefined things, even though we are -arguably- supposed to + ignore it. */ + TRACE (("plist/init_one_parameter[pid=ENDPOINT_GUID,mode=PEDANTIC,proto=%u.%u]: undefined pid\n", + dd->protocol_version.major, dd->protocol_version.minor)); + return ERR_INVALID; + } + return do_guid (&dest->endpoint_guid, &dest->present, PP_ENDPOINT_GUID, valid_endpoint_guid, dd); + + case PID_PRISMTECH_ENDPOINT_GUID: /* case PID_RTI_TYPECODE: */ + if (vendor_is_prismtech (dd->vendorid)) + { + /* PrismTech specific variant of ENDPOINT_GUID, for strict compliancy */ + return do_guid (&dest->endpoint_guid, &dest->present, PP_ENDPOINT_GUID, valid_endpoint_guid, dd); + } + else if (vendor_is_rti (dd->vendorid)) + { + /* For RTI it is a typecode */ + return do_blob (&dest->qos.rti_typecode, &dest->qos.present, &dest->qos.aliased, qwanted, QP_RTI_TYPECODE, dd); + + } + else + { + return 0; + } + + + case PID_PRISMTECH_PARTICIPANT_VERSION_INFO: + return do_prismtech_participant_version_info(&dest->prismtech_participant_version_info, &dest->present, &dest->aliased, dd); + + case PID_PRISMTECH_SUBSCRIPTION_KEYS: + if (!vendor_is_prismtech (dd->vendorid)) + return 0; + return do_subscription_keys_qospolicy (&dest->qos.subscription_keys, &dest->qos.present, &dest->qos.aliased, QP_PRISMTECH_SUBSCRIPTION_KEYS, dd); + + case PID_PRISMTECH_READER_LIFESPAN: + if (!vendor_is_prismtech (dd->vendorid)) + return 0; + return do_reader_lifespan_qospolicy (&dest->qos.reader_lifespan, &dest->qos.present, QP_PRISMTECH_READER_LIFESPAN, dd); + + + case PID_PRISMTECH_ENTITY_FACTORY: + if (!vendor_is_prismtech (dd->vendorid)) + return 0; + return do_entity_factory_qospolicy (&dest->qos.entity_factory, &dest->qos.present, QP_PRISMTECH_ENTITY_FACTORY, dd); + + case PID_PRISMTECH_NODE_NAME: + if (!vendor_is_prismtech (dd->vendorid)) + return 0; + return do_string (&dest->node_name, &dest->present, &dest->aliased, pwanted, PP_PRISMTECH_NODE_NAME, dd); + + case PID_PRISMTECH_EXEC_NAME: + if (!vendor_is_prismtech (dd->vendorid)) + return 0; + return do_string (&dest->exec_name, &dest->present, &dest->aliased, pwanted, PP_PRISMTECH_EXEC_NAME, dd); + + case PID_PRISMTECH_SERVICE_TYPE: + if (!vendor_is_prismtech (dd->vendorid)) + return 0; + if (dd->bufsz < sizeof (dest->service_type)) + { + TRACE (("plist/init_one_parameter[pid=PRISMTECH_SERVICE_TYPE]: buffer too small\n")); + return ERR_INVALID; + } + memcpy (&dest->service_type, dd->buf, sizeof (dest->service_type)); + if (dd->bswap) + dest->service_type = bswap4u (dest->service_type); + dest->present |= PP_PRISMTECH_SERVICE_TYPE; + return 0; + + case PID_PRISMTECH_PROCESS_ID: + if (!vendor_is_prismtech (dd->vendorid)) + return 0; + if (dd->bufsz < sizeof (dest->process_id)) + { + TRACE (("plist/init_one_parameter[pid=PRISMTECH_PROCESS_ID]: buffer too small\n")); + return ERR_INVALID; + } + memcpy (&dest->process_id, dd->buf, sizeof (dest->process_id)); + if (dd->bswap) + dest->process_id = bswap4u (dest->process_id); + dest->present |= PP_PRISMTECH_PROCESS_ID; + return 0; + + case PID_PRISMTECH_TYPE_DESCRIPTION: + if (!vendor_is_prismtech (dd->vendorid)) + return 0; + return do_string (&dest->type_description, &dest->present, &dest->aliased, pwanted, PP_PRISMTECH_TYPE_DESCRIPTION, dd); + + case PID_PRISMTECH_EOTINFO: + if (!vendor_is_opensplice (dd->vendorid)) + return 0; + else if (dd->bufsz < 2*sizeof (uint32_t)) + { + TRACE (("plist/init_one_parameter[pid=PRISMTECH_EOTINFO]: buffer too small (1)\n")); + return ERR_INVALID; + } + else + { + nn_prismtech_eotinfo_t *q = &dest->eotinfo; + uint32_t i; + q->transactionId = ((const uint32_t *) dd->buf)[0]; + q->n = ((const uint32_t *) dd->buf)[1]; + if (dd->bswap) + { + q->n = bswap4u (q->n); + q->transactionId = bswap4u (q->transactionId); + } + if (q->n > (dd->bufsz - 2*sizeof (uint32_t)) / sizeof (nn_prismtech_eotgroup_tid_t)) + { + TRACE (("plist/init_one_parameter[pid=PRISMTECH_EOTINFO]: buffer too small (2)\n")); + return ERR_INVALID; + } + if (q->n == 0) + q->tids = NULL; + else + q->tids = (nn_prismtech_eotgroup_tid_t *) (dd->buf + 2*sizeof (uint32_t)); + for (i = 0; i < q->n; i++) + { + q->tids[i].writer_entityid.u = fromBE4u (q->tids[i].writer_entityid.u); + if (dd->bswap) + q->tids[i].transactionId = bswap4u (q->tids[i].transactionId); + } + dest->present |= PP_PRISMTECH_EOTINFO; + dest->aliased |= PP_PRISMTECH_EOTINFO; + if (config.enabled_logcats & LC_PLIST) + { + TRACE_PLIST (("eotinfo: txn %u {", q->transactionId)); + for (i = 0; i < q->n; i++) + TRACE_PLIST ((" %x:%u", q->tids[i].writer_entityid.u, q->tids[i].transactionId)); + TRACE_PLIST ((" }\n")); + } + return 0; + } + +#ifdef DDSI_INCLUDE_SSM + case PID_READER_FAVOURS_SSM: + if (dd->bufsz < sizeof (dest->reader_favours_ssm)) + { + TRACE (("plist/init_one_parameter[pid=READER_FAVOURS_SSM]: buffer too small\n")); + return ERR_INVALID; + } + else + { + nn_reader_favours_ssm_t *rfssm = &dest->reader_favours_ssm; + memcpy (rfssm, dd->buf, sizeof (*rfssm)); + if (dd->bswap) + rfssm->state = bswap4u (rfssm->state); + if (rfssm->state != 0 && rfssm->state != 1) + { + TRACE (("plist/init_one_parameter[pid=READER_FAVOURS_SSM]: unsupported value: %u\n", rfssm->state)); + rfssm->state = 0; + } + dest->present |= PP_READER_FAVOURS_SSM; + return 0; + } +#endif + + case PID_IDENTITY_TOKEN: + return do_dataholder (&dest->identity_token, &dest->present, &dest->aliased, pwanted, PP_IDENTITY_TOKEN, dd); + + case PID_PERMISSIONS_TOKEN: + return do_dataholder (&dest->permissions_token, &dest->present, &dest->aliased, pwanted, PP_PERMISSIONS_TOKEN, dd); + + /* Deprecated ones (used by RTI, but not relevant to DDSI) */ + case PID_PERSISTENCE: + case PID_TYPE_CHECKSUM: + case PID_TYPE2_NAME: + case PID_TYPE2_CHECKSUM: + case PID_EXPECTS_ACK: + case PID_MANAGER_KEY: + case PID_SEND_QUEUE_SIZE: + case PID_RELIABILITY_ENABLED: + case PID_VARGAPPS_SEQUENCE_NUMBER_LAST: + case PID_RECV_QUEUE_SIZE: + case PID_RELIABILITY_OFFERED: + return 0; + + default: + /* Ignore unrecognised parameters (disregarding vendor-specific + ones, of course) if the protocol version is newer than the + one implemented, and fail it if it isn't. I know all RFPs say + to be tolerant in what is accepted, but that is where the + bugs & the buffer overflows originate! */ + if (pid & PID_UNRECOGNIZED_INCOMPATIBLE_FLAG) + dest->present |= PP_INCOMPATIBLE; + else if (pid & PID_VENDORSPECIFIC_FLAG) + return 0; + else if (!protocol_version_is_newer (dd->protocol_version) && NN_STRICT_P) + { + TRACE (("plist/init_one_parameter[pid=%u,mode=STRICT,proto=%u.%u]: undefined paramter id\n", + pid, dd->protocol_version.major, dd->protocol_version.minor)); + return ERR_INVALID; + } + else + return 0; + } + + assert (0); + TRACE (("plist/init_one_parameter: can't happen\n")); + return ERR_INVALID; +} + +static void default_resource_limits (nn_resource_limits_qospolicy_t *q) +{ + q->max_instances = NN_DDS_LENGTH_UNLIMITED; + q->max_samples = NN_DDS_LENGTH_UNLIMITED; + q->max_samples_per_instance = NN_DDS_LENGTH_UNLIMITED; +} + +static void default_history (nn_history_qospolicy_t *q) +{ + q->kind = NN_KEEP_LAST_HISTORY_QOS; + q->depth = 1; +} + +void nn_plist_init_empty (nn_plist_t *dest) +{ +#ifndef NDEBUG + memset (dest, 0, sizeof (*dest)); +#endif + dest->present = dest->aliased = 0; + nn_xqos_init_empty (&dest->qos); +} + +void nn_plist_mergein_missing (nn_plist_t *a, const nn_plist_t *b) +{ + /* Adds entries's from B to A (duplicating memory) (only those not + present in A, obviously) */ + + /* Simple ones (that don't need memory): everything but topic, type, + partition, {group,topic|user} data */ +#define CQ(fl_, name_) do { \ + if (!(a->present & PP_##fl_) && (b->present & PP_##fl_)) { \ + a->name_ = b->name_; \ + a->present |= PP_##fl_; \ + } \ + } while (0) + CQ (PROTOCOL_VERSION, protocol_version); + CQ (VENDORID, vendorid); + CQ (EXPECTS_INLINE_QOS, expects_inline_qos); + CQ (PARTICIPANT_MANUAL_LIVELINESS_COUNT, participant_manual_liveliness_count); + CQ (PARTICIPANT_BUILTIN_ENDPOINTS, participant_builtin_endpoints); + CQ (PARTICIPANT_LEASE_DURATION, participant_lease_duration); + CQ (PARTICIPANT_GUID, participant_guid); + CQ (ENDPOINT_GUID, endpoint_guid); + CQ (GROUP_GUID, group_guid); + CQ (BUILTIN_ENDPOINT_SET, builtin_endpoint_set); + CQ (KEYHASH, keyhash); + CQ (STATUSINFO, statusinfo); + CQ (COHERENT_SET, coherent_set_seqno); + CQ (PRISMTECH_SERVICE_TYPE, service_type); + CQ (PRISMTECH_PROCESS_ID, process_id); + CQ (PRISMTECH_BUILTIN_ENDPOINT_SET, prismtech_builtin_endpoint_set); +#ifdef DDSI_INCLUDE_SSM + CQ (READER_FAVOURS_SSM, reader_favours_ssm); +#endif +#undef CQ + + /* For allocated ones it is Not strictly necessary to use tmp, as + a->name_ may only be interpreted if the present flag is set, but + this keeps a clean on failure and may thereby save us from a + nasty surprise. */ +#define CQ(fl_, name_, type_, tmp_type_) do { \ + if (!(a->present & PP_##fl_) && (b->present & PP_##fl_)) { \ + tmp_type_ tmp = b->name_; \ + unalias_##type_ (&tmp, -1); \ + a->name_ = tmp; \ + a->present |= PP_##fl_; \ + } \ + } while (0) + CQ (UNICAST_LOCATOR, unicast_locators, locators, nn_locators_t); + CQ (MULTICAST_LOCATOR, unicast_locators, locators, nn_locators_t); + CQ (DEFAULT_UNICAST_LOCATOR, default_unicast_locators, locators, nn_locators_t); + CQ (DEFAULT_MULTICAST_LOCATOR, default_multicast_locators, locators, nn_locators_t); + CQ (METATRAFFIC_UNICAST_LOCATOR, metatraffic_unicast_locators, locators, nn_locators_t); + CQ (METATRAFFIC_MULTICAST_LOCATOR, metatraffic_multicast_locators, locators, nn_locators_t); + CQ (ENTITY_NAME, entity_name, string, char *); + CQ (PRISMTECH_NODE_NAME, node_name, string, char *); + CQ (PRISMTECH_EXEC_NAME, exec_name, string, char *); + CQ (PRISMTECH_TYPE_DESCRIPTION, type_description, string, char *); + CQ (PRISMTECH_EOTINFO, eotinfo, eotinfo, nn_prismtech_eotinfo_t); + CQ (IDENTITY_TOKEN, identity_token, dataholder, nn_token_t); + CQ (PERMISSIONS_TOKEN, permissions_token, dataholder, nn_token_t); +#undef CQ + if (!(a->present & PP_PRISMTECH_PARTICIPANT_VERSION_INFO) && + (b->present & PP_PRISMTECH_PARTICIPANT_VERSION_INFO)) + { + nn_prismtech_participant_version_info_t tmp = b->prismtech_participant_version_info; + unalias_string (&tmp.internals, -1); + a->prismtech_participant_version_info = tmp; + a->present |= PP_PRISMTECH_PARTICIPANT_VERSION_INFO; + } + + nn_xqos_mergein_missing (&a->qos, &b->qos); +} + +void nn_plist_copy (nn_plist_t *dst, const nn_plist_t *src) +{ + nn_plist_init_empty (dst); + nn_plist_mergein_missing (dst, src); +} + +nn_plist_t *nn_plist_dup (const nn_plist_t *src) +{ + nn_plist_t *dst; + dst = os_malloc (sizeof (*dst)); + nn_plist_copy (dst, src); + assert (dst->aliased == 0); + return dst; +} + +static int final_validation (nn_plist_t *dest) +{ + /* Resource limits & history are related, so if only one is given, + set the other to the default, claim it has been provided & + validate the combination. They can't be changed afterward, so + this is a reasonable interpretation. */ + if ((dest->qos.present & QP_HISTORY) && !(dest->qos.present & QP_RESOURCE_LIMITS)) + { + default_resource_limits (&dest->qos.resource_limits); + dest->qos.present |= QP_RESOURCE_LIMITS; + } + if (!(dest->qos.present & QP_HISTORY) && (dest->qos.present & QP_RESOURCE_LIMITS)) + { + default_history (&dest->qos.history); + dest->qos.present |= QP_HISTORY; + } + if (dest->qos.present & (QP_HISTORY | QP_RESOURCE_LIMITS)) + { + int res; + assert ((dest->qos.present & (QP_HISTORY | QP_RESOURCE_LIMITS)) == (QP_HISTORY | QP_RESOURCE_LIMITS)); + if ((res = validate_history_and_resource_limits (&dest->qos.history, &dest->qos.resource_limits)) < 0) + return res; + } + return 0; +} + +int nn_plist_init_frommsg +( + nn_plist_t *dest, + char **nextafterplist, + uint64_t pwanted, + uint64_t qwanted, + const nn_plist_src_t *src +) +{ + const unsigned char *pl; + struct dd dd; + nn_ipaddress_params_tmp_t dest_tmp; + +#ifndef NDEBUG + memset (dest, 0, sizeof (*dest)); +#endif + + if (nextafterplist) + *nextafterplist = NULL; + dd.protocol_version = src->protocol_version; + dd.vendorid = src->vendorid; + switch (src->encoding) + { + case PL_CDR_LE: +#if OS_ENDIANNESS == OS_LITTLE_ENDIAN + dd.bswap = 0; +#else + dd.bswap = 1; +#endif + break; + case PL_CDR_BE: +#if OS_ENDIANNESS == OS_LITTLE_ENDIAN + dd.bswap = 1; +#else + dd.bswap = 0; +#endif + break; + default: + NN_WARNING ("plist(vendor %u.%u): unknown encoding (%d)\n", + src->vendorid.id[0], src->vendorid.id[1], src->encoding); + return ERR_INVALID; + } + nn_plist_init_empty (dest); + dest->unalias_needs_bswap = dd.bswap; + dest_tmp.present = 0; + + TRACE_PLIST (("NN_PLIST_INIT (bswap %d)\n", dd.bswap)); + + pl = src->buf; + while (pl + sizeof (nn_parameter_t) <= src->buf + src->bufsz) + { + nn_parameter_t *par = (nn_parameter_t *) pl; + nn_parameterid_t pid; + unsigned short length; + int res; + /* swapping header partially based on wireshark dissector + output, partially on intuition, and in a small part based on + the spec */ + pid = (nn_parameterid_t) (dd.bswap ? bswap2u (par->parameterid) : par->parameterid); + length = (unsigned short) (dd.bswap ? bswap2u (par->length) : par->length); + if (pid == PID_SENTINEL) + { + /* Sentinel terminates list, the length is ignored, DDSI + 9.4.2.11. */ + TRACE_PLIST (("%4x PID %x\n", (unsigned) (pl - src->buf), pid)); + if ((res = final_validation (dest)) < 0) + { + nn_plist_fini (dest); + return ERR_INVALID; + } + else + { + pl += sizeof (*par); + if (nextafterplist) + *nextafterplist = (char *) pl; + return 0; + } + } + if (length > src->bufsz - sizeof (*par) - (unsigned) (pl - src->buf)) + { + NN_WARNING ("plist(vendor %u.%u): parameter length %u out of bounds\n", + src->vendorid.id[0], src->vendorid.id[1], length); + nn_plist_fini (dest); + return ERR_INVALID; + } + if ((length % 4) != 0) /* DDSI 9.4.2.11 */ + { + NN_WARNING ("plist(vendor %u.%u): parameter length %u mod 4 != 0\n", + src->vendorid.id[0], src->vendorid.id[1], length); + nn_plist_fini (dest); + return ERR_INVALID; + } + + if (config.enabled_logcats & LC_PLIST) + { + TRACE_PLIST (("%4x PID %x len %u ", (unsigned) (pl - src->buf), pid, length)); + log_octetseq(LC_PLIST, length, (const unsigned char *) (par + 1)); + TRACE_PLIST (("\n")); + } + + dd.buf = (const unsigned char *) (par + 1); + dd.bufsz = (unsigned) length; + if ((res = init_one_parameter (dest, &dest_tmp, pwanted, qwanted, pid, &dd)) < 0) + { + /* make sure we print a trace message on error */ + TRACE (("plist(vendor %u.%u): failed at pid=%u\n", src->vendorid.id[0], src->vendorid.id[1], pid)); + nn_plist_fini (dest); + return res; + } + pl += sizeof (*par) + length; + } + /* If we get here, that means we reached the end of the message + without encountering a sentinel. That is an error */ + NN_WARNING ("plist(vendor %u.%u): invalid parameter list: sentinel missing\n", + src->vendorid.id[0], src->vendorid.id[1]); + nn_plist_fini (dest); + return ERR_INVALID; +} + +unsigned char *nn_plist_quickscan (struct nn_rsample_info *dest, const struct nn_rmsg *rmsg, const nn_plist_src_t *src) +{ + /* Sets a few fields in dest, returns address of first byte + following parameter list, or NULL on error. Most errors will go + undetected, unlike nn_plist_init_frommsg(). */ + const unsigned char *pl; + + dest->statusinfo = 0; + dest->pt_wr_info_zoff = NN_OFF_TO_ZOFF (0); + dest->complex_qos = 0; + switch (src->encoding) + { + case PL_CDR_LE: +#if OS_ENDIANNESS == OS_LITTLE_ENDIAN + dest->bswap = 0; +#else + dest->bswap = 1; +#endif + break; + case PL_CDR_BE: +#if OS_ENDIANNESS == OS_LITTLE_ENDIAN + dest->bswap = 1; +#else + dest->bswap = 0; +#endif + break; + default: + NN_WARNING ("plist(vendor %u.%u): quickscan: unknown encoding (%d)\n", + src->vendorid.id[0], src->vendorid.id[1], src->encoding); + return NULL; + } + TRACE_PLIST (("NN_PLIST_QUICKSCAN (bswap %d)\n", dest->bswap)); + pl = src->buf; + while (pl + sizeof (nn_parameter_t) <= src->buf + src->bufsz) + { + nn_parameter_t *par = (nn_parameter_t *) pl; + nn_parameterid_t pid; + uint16_t length; + pid = (nn_parameterid_t) (dest->bswap ? bswap2u (par->parameterid) : par->parameterid); + length = (uint16_t) (dest->bswap ? bswap2u (par->length) : par->length); + pl += sizeof (*par); + if (pid == PID_SENTINEL) + return (unsigned char *) pl; + if (length > src->bufsz - (size_t)(pl - src->buf)) + { + NN_WARNING ("plist(vendor %u.%u): quickscan: parameter length %u out of bounds\n", + src->vendorid.id[0], src->vendorid.id[1], length); + return NULL; + } + if ((length % 4) != 0) /* DDSI 9.4.2.11 */ + { + NN_WARNING ("plist(vendor %u.%u): quickscan: parameter length %u mod 4 != 0\n", + src->vendorid.id[0], src->vendorid.id[1], length); + return NULL; + } + switch (pid) + { + case PID_PAD: + break; + case PID_KEYHASH: + if (length < sizeof (dest->keyhash)) + { + TRACE (("plist(vendor %u.%u): quickscan(PID_KEYHASH): buffer too small\n", + src->vendorid.id[0], src->vendorid.id[1])); + return NULL; + } + memcpy (&dest->keyhash, pl, sizeof (dest->keyhash)); + dest->hashash = 1; + break; + case PID_STATUSINFO: + if (length < 4) + { + TRACE (("plist(vendor %u.%u): quickscan(PID_STATUSINFO): buffer too small\n", + src->vendorid.id[0], src->vendorid.id[1])); + return NULL; + } + else + { + unsigned stinfo = fromBE4u (*((unsigned *) pl)); + unsigned stinfox = (length < 8 || !vendor_is_opensplice(src->vendorid)) ? 0 : fromBE4u (*((unsigned *) pl + 1)); +#if (NN_STATUSINFO_DISPOSE | NN_STATUSINFO_UNREGISTER) != 3 +#error "expected dispose/unregister to be in lowest 2 bits" +#endif + dest->statusinfo = stinfo & 3u; + if ((stinfo & ~3u) || stinfox) + dest->complex_qos = 1; + } + break; + default: + TRACE_PLIST (("(pid=%x complex_qos=1)", pid)); + dest->complex_qos = 1; + break; + } + pl += length; + } + /* If we get here, that means we reached the end of the message + without encountering a sentinel. That is an error */ + NN_WARNING ("plist(vendor %u.%u): quickscan: invalid parameter list: sentinel missing\n", + src->vendorid.id[0], src->vendorid.id[1]); + return NULL; +} + + +void nn_xqos_init_empty (nn_xqos_t *dest) +{ +#ifndef NDEBUG + memset (dest, 0, sizeof (*dest)); +#endif + dest->present = dest->aliased = 0; +} + +int nn_plist_init_default_participant (nn_plist_t *plist) +{ + nn_plist_init_empty (plist); + plist->qos.present |= QP_PRISMTECH_ENTITY_FACTORY; + plist->qos.entity_factory.autoenable_created_entities = 0; + return 0; +} + +static void xqos_init_default_common (nn_xqos_t *xqos) +{ + nn_xqos_init_empty (xqos); + + xqos->present |= QP_PARTITION; + xqos->partition.n = 0; + xqos->partition.strs = NULL; + + xqos->present |= QP_PRESENTATION; + xqos->presentation.access_scope = NN_INSTANCE_PRESENTATION_QOS; + xqos->presentation.coherent_access = 0; + xqos->presentation.ordered_access = 0; + + xqos->present |= QP_DURABILITY; + xqos->durability.kind = NN_VOLATILE_DURABILITY_QOS; + + xqos->present |= QP_DEADLINE; + xqos->deadline.deadline = nn_to_ddsi_duration (T_NEVER); + + xqos->present |= QP_LATENCY_BUDGET; + xqos->latency_budget.duration = nn_to_ddsi_duration (0); + + xqos->present |= QP_LIVELINESS; + xqos->liveliness.kind = NN_AUTOMATIC_LIVELINESS_QOS; + xqos->liveliness.lease_duration = nn_to_ddsi_duration (T_NEVER); + + xqos->present |= QP_DESTINATION_ORDER; + xqos->destination_order.kind = NN_BY_RECEPTION_TIMESTAMP_DESTINATIONORDER_QOS; + + xqos->present |= QP_HISTORY; + xqos->history.kind = NN_KEEP_LAST_HISTORY_QOS; + xqos->history.depth = 1; + + xqos->present |= QP_RESOURCE_LIMITS; + xqos->resource_limits.max_samples = NN_DDS_LENGTH_UNLIMITED; + xqos->resource_limits.max_instances = NN_DDS_LENGTH_UNLIMITED; + xqos->resource_limits.max_samples_per_instance = NN_DDS_LENGTH_UNLIMITED; + + xqos->present |= QP_TRANSPORT_PRIORITY; + xqos->transport_priority.value = 0; + + xqos->present |= QP_OWNERSHIP; + xqos->ownership.kind = NN_SHARED_OWNERSHIP_QOS; + + xqos->present |= QP_PRISMTECH_RELAXED_QOS_MATCHING; + xqos->relaxed_qos_matching.value = 0; + + xqos->present |= QP_PRISMTECH_SYNCHRONOUS_ENDPOINT; + xqos->synchronous_endpoint.value = 0; +} + +void nn_xqos_init_default_reader (nn_xqos_t *xqos) +{ + xqos_init_default_common (xqos); + + xqos->present |= QP_RELIABILITY; + xqos->reliability.kind = NN_BEST_EFFORT_RELIABILITY_QOS; + + xqos->present |= QP_TIME_BASED_FILTER; + xqos->time_based_filter.minimum_separation = nn_to_ddsi_duration (0); + + xqos->present |= QP_PRISMTECH_READER_DATA_LIFECYCLE; + xqos->reader_data_lifecycle.autopurge_nowriter_samples_delay = nn_to_ddsi_duration (T_NEVER); + xqos->reader_data_lifecycle.autopurge_disposed_samples_delay = nn_to_ddsi_duration (T_NEVER); + xqos->reader_data_lifecycle.autopurge_dispose_all = 0; + xqos->reader_data_lifecycle.enable_invalid_samples = 1; + xqos->reader_data_lifecycle.invalid_sample_visibility = NN_MINIMUM_INVALID_SAMPLE_VISIBILITY_QOS; + + xqos->present |= QP_PRISMTECH_READER_LIFESPAN; + xqos->reader_lifespan.use_lifespan = 0; + xqos->reader_lifespan.duration = nn_to_ddsi_duration (T_NEVER); + + + xqos->present |= QP_PRISMTECH_SUBSCRIPTION_KEYS; + xqos->subscription_keys.use_key_list = 0; + xqos->subscription_keys.key_list.n = 0; + xqos->subscription_keys.key_list.strs = NULL; +} + +void nn_xqos_init_default_writer (nn_xqos_t *xqos) +{ + xqos_init_default_common (xqos); + + xqos->present |= QP_DURABILITY_SERVICE; + xqos->durability_service.service_cleanup_delay = nn_to_ddsi_duration (0); + xqos->durability_service.history.kind = NN_KEEP_LAST_HISTORY_QOS; + xqos->durability_service.history.depth = 1; + xqos->durability_service.resource_limits.max_samples = NN_DDS_LENGTH_UNLIMITED; + xqos->durability_service.resource_limits.max_instances = NN_DDS_LENGTH_UNLIMITED; + xqos->durability_service.resource_limits.max_samples_per_instance = NN_DDS_LENGTH_UNLIMITED; + + xqos->present |= QP_RELIABILITY; + xqos->reliability.kind = NN_RELIABLE_RELIABILITY_QOS; + xqos->reliability.max_blocking_time = nn_to_ddsi_duration (100 * T_MILLISECOND); + + xqos->present |= QP_OWNERSHIP_STRENGTH; + xqos->ownership_strength.value = 0; + + xqos->present |= QP_TRANSPORT_PRIORITY; + xqos->transport_priority.value = 0; + + xqos->present |= QP_LIFESPAN; + xqos->lifespan.duration = nn_to_ddsi_duration (T_NEVER); + + xqos->present |= QP_PRISMTECH_WRITER_DATA_LIFECYCLE; + xqos->writer_data_lifecycle.autodispose_unregistered_instances = 1; + xqos->writer_data_lifecycle.autounregister_instance_delay = nn_to_ddsi_duration (T_NEVER); + xqos->writer_data_lifecycle.autopurge_suspended_samples_delay = nn_to_ddsi_duration (T_NEVER); +} + +void nn_xqos_init_default_writer_noautodispose (nn_xqos_t *xqos) +{ + nn_xqos_init_default_writer (xqos); + xqos->writer_data_lifecycle.autodispose_unregistered_instances = 0; +} + +void nn_xqos_init_default_topic (nn_xqos_t *xqos) +{ + xqos_init_default_common (xqos); + + xqos->present |= QP_DURABILITY_SERVICE; + xqos->durability_service.service_cleanup_delay = nn_to_ddsi_duration (0); + xqos->durability_service.history.kind = NN_KEEP_LAST_HISTORY_QOS; + xqos->durability_service.history.depth = 1; + xqos->durability_service.resource_limits.max_samples = NN_DDS_LENGTH_UNLIMITED; + xqos->durability_service.resource_limits.max_instances = NN_DDS_LENGTH_UNLIMITED; + xqos->durability_service.resource_limits.max_samples_per_instance = NN_DDS_LENGTH_UNLIMITED; + + xqos->present |= QP_RELIABILITY; + xqos->reliability.kind = NN_BEST_EFFORT_RELIABILITY_QOS; + xqos->reliability.max_blocking_time = nn_to_ddsi_duration (100 * T_MILLISECOND); + + xqos->present |= QP_TRANSPORT_PRIORITY; + xqos->transport_priority.value = 0; + + xqos->present |= QP_LIFESPAN; + xqos->lifespan.duration = nn_to_ddsi_duration (T_NEVER); + + xqos->present |= QP_PRISMTECH_SUBSCRIPTION_KEYS; + xqos->subscription_keys.use_key_list = 0; + xqos->subscription_keys.key_list.n = 0; + xqos->subscription_keys.key_list.strs = NULL; +} + +void nn_xqos_init_default_subscriber (nn_xqos_t *xqos) +{ + nn_xqos_init_empty (xqos); + + xqos->present |= QP_PRISMTECH_ENTITY_FACTORY; + xqos->entity_factory.autoenable_created_entities = 1; + + + xqos->present |= QP_PARTITION; + xqos->partition.n = 0; + xqos->partition.strs = NULL; +} + +void nn_xqos_init_default_publisher (nn_xqos_t *xqos) +{ + nn_xqos_init_empty (xqos); + + xqos->present |= QP_PRISMTECH_ENTITY_FACTORY; + xqos->entity_factory.autoenable_created_entities = 1; + + xqos->present |= QP_PARTITION; + xqos->partition.n = 0; + xqos->partition.strs = NULL; +} + +void nn_xqos_mergein_missing (nn_xqos_t *a, const nn_xqos_t *b) +{ + /* Adds QoS's from B to A (duplicating memory) (only those not + present in A, obviously) */ + + /* Simple ones (that don't need memory): everything but topic, type, + partition, {group,topic|user} data */ +#define CQ(fl_, name_) do { \ + if (!(a->present & QP_##fl_) && (b->present & QP_##fl_)) { \ + a->name_ = b->name_; \ + a->present |= QP_##fl_; \ + } \ + } while (0) + CQ (PRESENTATION, presentation); + CQ (DURABILITY, durability); + CQ (DURABILITY_SERVICE, durability_service); + CQ (DEADLINE, deadline); + CQ (LATENCY_BUDGET, latency_budget); + CQ (LIVELINESS, liveliness); + CQ (RELIABILITY, reliability); + CQ (DESTINATION_ORDER, destination_order); + CQ (HISTORY, history); + CQ (RESOURCE_LIMITS, resource_limits); + CQ (TRANSPORT_PRIORITY, transport_priority); + CQ (LIFESPAN, lifespan); + CQ (OWNERSHIP, ownership); + CQ (OWNERSHIP_STRENGTH, ownership_strength); + CQ (TIME_BASED_FILTER, time_based_filter); + CQ (PRISMTECH_READER_DATA_LIFECYCLE, reader_data_lifecycle); + CQ (PRISMTECH_WRITER_DATA_LIFECYCLE, writer_data_lifecycle); + CQ (PRISMTECH_RELAXED_QOS_MATCHING, relaxed_qos_matching); + CQ (PRISMTECH_READER_LIFESPAN, reader_lifespan); + CQ (PRISMTECH_ENTITY_FACTORY, entity_factory); + CQ (PRISMTECH_SYNCHRONOUS_ENDPOINT, synchronous_endpoint); +#undef CQ + + /* For allocated ones it is Not strictly necessary to use tmp, as + a->name_ may only be interpreted if the present flag is set, but + this keeps a clean on failure and may thereby save us from a + nasty surprise. */ +#define CQ(fl_, name_, type_, tmp_type_) do { \ + if (!(a->present & QP_##fl_) && (b->present & QP_##fl_)) { \ + tmp_type_ tmp = b->name_; \ + unalias_##type_ (&tmp, -1); \ + a->name_ = tmp; \ + a->present |= QP_##fl_; \ + } \ + } while (0) + CQ (GROUP_DATA, group_data, octetseq, nn_octetseq_t); + CQ (TOPIC_DATA, topic_data, octetseq, nn_octetseq_t); + CQ (USER_DATA, user_data, octetseq, nn_octetseq_t); + CQ (TOPIC_NAME, topic_name, string, char *); + CQ (TYPE_NAME, type_name, string, char *); + CQ (RTI_TYPECODE, rti_typecode, octetseq, nn_octetseq_t); +#undef CQ + if (!(a->present & QP_PRISMTECH_SUBSCRIPTION_KEYS) && (b->present & QP_PRISMTECH_SUBSCRIPTION_KEYS)) + { + a->subscription_keys.use_key_list = b->subscription_keys.use_key_list; + duplicate_stringseq (&a->subscription_keys.key_list, &b->subscription_keys.key_list); + a->present |= QP_PRISMTECH_SUBSCRIPTION_KEYS; + } + if (!(a->present & QP_PARTITION) && (b->present & QP_PARTITION)) + { + duplicate_stringseq (&a->partition, &b->partition); + a->present |= QP_PARTITION; + } + if (!(a->present & QP_PROPERTY) && (b->present & QP_PROPERTY)) + { + duplicate_property_qospolicy (&a->property, &b->property); + a->present |= QP_PROPERTY; + } +} + +void nn_xqos_copy (nn_xqos_t *dst, const nn_xqos_t *src) +{ + nn_xqos_init_empty (dst); + nn_xqos_mergein_missing (dst, src); +} + +void nn_xqos_unalias (nn_xqos_t *xqos) +{ + TRACE_PLIST (("NN_XQOS_UNALIAS\n")); +#define Q(name_, func_, field_) do { \ + if ((xqos->present & QP_##name_) && (xqos->aliased & QP_##name_)) { \ + unalias_##func_ (&xqos->field_, -1); \ + xqos->aliased &= ~QP_##name_; \ + } \ + } while (0) + Q (GROUP_DATA, octetseq, group_data); + Q (TOPIC_DATA, octetseq, topic_data); + Q (USER_DATA, octetseq, user_data); + Q (TOPIC_NAME, string, topic_name); + Q (TYPE_NAME, string, type_name); + Q (PARTITION, stringseq, partition); + Q (PRISMTECH_SUBSCRIPTION_KEYS, subscription_keys_qospolicy, subscription_keys); + Q (RTI_TYPECODE, octetseq, rti_typecode); +#undef Q + assert (xqos->aliased == 0); +} + +void nn_xqos_fini (nn_xqos_t *xqos) +{ + struct t { uint64_t fl; size_t off; }; + static const struct t qos_simple[] = { + { QP_GROUP_DATA, offsetof (nn_xqos_t, group_data.value) }, + { QP_TOPIC_DATA, offsetof (nn_xqos_t, topic_data.value) }, + { QP_USER_DATA, offsetof (nn_xqos_t, user_data.value) }, + { QP_TOPIC_NAME, offsetof (nn_xqos_t, topic_name) }, + { QP_TYPE_NAME, offsetof (nn_xqos_t, type_name) }, + { QP_RTI_TYPECODE, offsetof (nn_xqos_t, rti_typecode.value) } + }; + int i; + TRACE_PLIST (("NN_XQOS_FINI\n")); + for (i = 0; i < (int) (sizeof (qos_simple) / sizeof (*qos_simple)); i++) + { + if ((xqos->present & qos_simple[i].fl) && !(xqos->aliased & qos_simple[i].fl)) + { + void **pp = (void **) ((char *) xqos + qos_simple[i].off); + TRACE_PLIST (("NN_XQOS_FINI free %p\n", *pp)); + os_free (*pp); + } + } + if (xqos->present & QP_PARTITION) + { + if (!(xqos->aliased & QP_PARTITION)) + { + free_stringseq (&xqos->partition); + } + else + { + /* until proper message buffers arrive */ + TRACE_PLIST (("NN_XQOS_FINI free %p\n", xqos->partition.strs)); + os_free (xqos->partition.strs); + } + } + if (xqos->present & QP_PRISMTECH_SUBSCRIPTION_KEYS) + { + if (!(xqos->aliased & QP_PRISMTECH_SUBSCRIPTION_KEYS)) + free_stringseq (&xqos->subscription_keys.key_list); + else + { + /* until proper message buffers arrive */ + TRACE_PLIST (("NN_XQOS_FINI free %p\n", xqos->subscription_keys.key_list.strs)); + os_free (xqos->subscription_keys.key_list.strs); + } + } + if (xqos->present & QP_PROPERTY) + { + if (!(xqos->aliased & QP_PROPERTY)) + { + free_propertyseq(&xqos->property.value); + free_binarypropertyseq(&xqos->property.binary_value); + } + } + xqos->present = 0; +} + +nn_xqos_t * nn_xqos_dup (const nn_xqos_t *src) +{ + nn_xqos_t *dst = os_malloc (sizeof (*dst)); + nn_xqos_copy (dst, src); + assert (dst->aliased == 0); + return dst; +} + +static int octetseqs_differ (const nn_octetseq_t *a, const nn_octetseq_t *b) +{ + return (a->length != b->length || memcmp (a->value, b->value, a->length) != 0); +} + +static int durations_differ (const nn_duration_t *a, const nn_duration_t *b) +{ +#if DDSI_DURATION_ACCORDING_TO_SPEC + return (a->sec != b->sec || a->nanosec != b->nanosec); +#else + return (a->seconds != b->seconds || a->fraction != b->fraction); +#endif +} + +static int stringseqs_differ (const nn_stringseq_t *a, const nn_stringseq_t *b) +{ + unsigned i; + if (a->n != b->n) + return 1; + for (i = 0; i < a->n; i++) + if (strcmp (a->strs[i], b->strs[i])) + return 1; + return 0; +} + +static int histories_differ (const nn_history_qospolicy_t *a, const nn_history_qospolicy_t *b) +{ + return (a->kind != b->kind || (a->kind == NN_KEEP_LAST_HISTORY_QOS && a->depth != b->depth)); +} + +static int resource_limits_differ (const nn_resource_limits_qospolicy_t *a, const nn_resource_limits_qospolicy_t *b) +{ + return (a->max_samples != b->max_samples || a->max_instances != b->max_instances || + a->max_samples_per_instance != b->max_samples_per_instance); +} + +static int partition_is_default (const nn_partition_qospolicy_t *a) +{ + unsigned i; + for (i = 0; i < a->n; i++) + if (strcmp (a->strs[i], "") != 0) + return 0; + return 1; +} + +static int partitions_equal_n2 (const nn_partition_qospolicy_t *a, const nn_partition_qospolicy_t *b) +{ + unsigned i, j; + for (i = 0; i < a->n; i++) + { + for (j = 0; j < b->n; j++) + if (strcmp (a->strs[i], b->strs[j]) == 0) + break; + if (j == b->n) + return 0; + } + return 1; +} + +static int partitions_equal_nlogn (const nn_partition_qospolicy_t *a, const nn_partition_qospolicy_t *b) +{ + char *statictab[8], **tab; + int equal = 1; + unsigned i; + + if (a->n <= (int) (sizeof (statictab) / sizeof (*statictab))) + tab = statictab; + else + tab = os_malloc (a->n * sizeof (*tab)); + + for (i = 0; i < a->n; i++) + tab[i] = a->strs[i]; + qsort (tab, a->n, sizeof (*tab), (int (*) (const void *, const void *)) strcmp); + for (i = 0; i < b->n; i++) + if (os_bsearch (b->strs[i], tab, a->n, sizeof (*tab), (int (*) (const void *, const void *)) strcmp) == NULL) + { + equal = 0; + break; + } + if (tab != statictab) + os_free (tab); + return equal; +} + +static int partitions_equal (const nn_partition_qospolicy_t *a, const nn_partition_qospolicy_t *b) +{ + /* Return true iff (the set a->strs) equals (the set b->strs); that + is, order doesn't matter. One could argue that "**" and "*" are + equal, but we're not that precise here. */ + int b_is_def; + + if (a->n == 1 && b->n == 1) + return (strcmp (a->strs[0], b->strs[0]) == 0); + /* not the trivial case */ + b_is_def = partition_is_default (b); + if (partition_is_default (a)) + return b_is_def; + else if (b_is_def) + return 0; + + /* Neither is default, go the expensive route. Which one depends + on the actual number of partitions and both variants are written + assuming that |A| >= |B|. */ + if (a->n < b->n) + { + const nn_partition_qospolicy_t *x = a; + a = b; + b = x; + } + if (a->n * b->n < 10) + { + /* for small sets, the quadratic version should be the fastest, + the number has been pulled from thin air */ + return partitions_equal_n2 (a, b); + } + else + { + /* for larger sets, the n log(n) version should win */ + return partitions_equal_nlogn (a, b); + } +} + +uint64_t nn_xqos_delta (const nn_xqos_t *a, const nn_xqos_t *b, uint64_t mask) +{ + /* Returns QP_... set for RxO settings where a differs from b; if + present in a but not in b (or in b but not in a) it counts as a + difference. */ + uint64_t delta = (a->present ^ b->present) & mask; + uint64_t check = (a->present & b->present) & mask; + if (check & QP_TOPIC_NAME) { + if (strcmp (a->topic_name, b->topic_name)) + delta |= QP_TOPIC_NAME; + } + if (check & QP_TYPE_NAME) { + if (strcmp (a->type_name, b->type_name)) + delta |= QP_TYPE_NAME; + } + if (check & QP_PRESENTATION) { + if (a->presentation.access_scope != b->presentation.access_scope || + a->presentation.coherent_access != b->presentation.coherent_access || + a->presentation.ordered_access != b->presentation.ordered_access) + delta |= QP_PRESENTATION; + } + if (check & QP_PARTITION) { + if (!partitions_equal (&a->partition, &b->partition)) + delta |= QP_PARTITION; + } + if (check & QP_GROUP_DATA) { + if (octetseqs_differ (&a->group_data, &b->group_data)) + delta |= QP_GROUP_DATA; + } + if (check & QP_TOPIC_DATA) { + if (octetseqs_differ (&a->topic_data, &b->topic_data)) + delta |= QP_TOPIC_DATA; + } + if (check & QP_DURABILITY) { + if (a->durability.kind != b->durability.kind) + delta |= QP_DURABILITY; + } + if (check & QP_DURABILITY_SERVICE) + { + const nn_durability_service_qospolicy_t *qa = &a->durability_service; + const nn_durability_service_qospolicy_t *qb = &b->durability_service; + if (durations_differ (&qa->service_cleanup_delay, &qb->service_cleanup_delay) || + histories_differ (&qa->history, &qb->history) || + resource_limits_differ (&qa->resource_limits, &qb->resource_limits)) + delta |= QP_DURABILITY_SERVICE; + } + if (check & QP_DEADLINE) { + if (durations_differ (&a->deadline.deadline, &b->deadline.deadline)) + delta |= QP_DEADLINE; + } + if (check & QP_LATENCY_BUDGET) { + if (durations_differ (&a->latency_budget.duration, &b->latency_budget.duration)) + delta |= QP_LATENCY_BUDGET; + } + if (check & QP_LIVELINESS) { + if (a->liveliness.kind != b->liveliness.kind || + durations_differ (&a->liveliness.lease_duration, &b->liveliness.lease_duration)) + delta |= QP_LIVELINESS; + } + if (check & QP_RELIABILITY) { + if (a->reliability.kind != b->reliability.kind || + durations_differ (&a->reliability.max_blocking_time, &b->reliability.max_blocking_time)) + delta |= QP_RELIABILITY; + } + if (check & QP_DESTINATION_ORDER) { + if (a->destination_order.kind != b->destination_order.kind) + delta |= QP_DESTINATION_ORDER; + } + if (check & QP_HISTORY) { + if (histories_differ (&a->history, &b->history)) + delta |= QP_HISTORY; + } + if (check & QP_RESOURCE_LIMITS) { + if (resource_limits_differ (&a->resource_limits, &b->resource_limits)) + delta |= QP_RESOURCE_LIMITS; + } + if (check & QP_TRANSPORT_PRIORITY) { + if (a->transport_priority.value != b->transport_priority.value) + delta |= QP_TRANSPORT_PRIORITY; + } + if (check & QP_LIFESPAN) { + if (durations_differ (&a->lifespan.duration, &b->lifespan.duration)) + delta |= QP_LIFESPAN; + } + if (check & QP_USER_DATA) { + if (octetseqs_differ (&a->user_data, &b->user_data)) + delta |= QP_USER_DATA; + } + if (check & QP_OWNERSHIP) { + if (a->ownership.kind != b->ownership.kind) + delta |= QP_OWNERSHIP; + } + if (check & QP_OWNERSHIP_STRENGTH) { + if (a->ownership_strength.value != b->ownership_strength.value) + delta |= QP_OWNERSHIP_STRENGTH; + } + if (check & QP_TIME_BASED_FILTER) { + if (durations_differ (&a->time_based_filter.minimum_separation, &b->time_based_filter.minimum_separation)) + delta |= QP_TIME_BASED_FILTER; + } + if (check & QP_PRISMTECH_READER_DATA_LIFECYCLE) { + if (durations_differ (&a->reader_data_lifecycle.autopurge_disposed_samples_delay, + &b->reader_data_lifecycle.autopurge_disposed_samples_delay) || + durations_differ (&a->reader_data_lifecycle.autopurge_nowriter_samples_delay, + &b->reader_data_lifecycle.autopurge_nowriter_samples_delay) || + a->reader_data_lifecycle.autopurge_dispose_all != b->reader_data_lifecycle.autopurge_dispose_all || + a->reader_data_lifecycle.enable_invalid_samples != b->reader_data_lifecycle.enable_invalid_samples || + a->reader_data_lifecycle.invalid_sample_visibility != b->reader_data_lifecycle.invalid_sample_visibility) + delta |= QP_PRISMTECH_READER_DATA_LIFECYCLE; + } + if (check & QP_PRISMTECH_WRITER_DATA_LIFECYCLE) { + if (a->writer_data_lifecycle.autodispose_unregistered_instances != + b->writer_data_lifecycle.autodispose_unregistered_instances || + durations_differ (&a->writer_data_lifecycle.autopurge_suspended_samples_delay, + &b->writer_data_lifecycle.autopurge_suspended_samples_delay) || + durations_differ (&a->writer_data_lifecycle.autounregister_instance_delay, + &b->writer_data_lifecycle.autounregister_instance_delay)) + delta |= QP_PRISMTECH_WRITER_DATA_LIFECYCLE; + } + if (check & QP_PRISMTECH_RELAXED_QOS_MATCHING) { + if (a->relaxed_qos_matching.value != + b->relaxed_qos_matching.value) + delta |= QP_PRISMTECH_RELAXED_QOS_MATCHING; + } + if (check & QP_PRISMTECH_READER_LIFESPAN) { + /* Note: the conjunction need not test both a & b for having use_lifespan set */ + if (a->reader_lifespan.use_lifespan != b->reader_lifespan.use_lifespan || + (a->reader_lifespan.use_lifespan && b->reader_lifespan.use_lifespan && + durations_differ (&a->reader_lifespan.duration, &b->reader_lifespan.duration))) + delta |= QP_PRISMTECH_READER_LIFESPAN; + } + if (check & QP_PRISMTECH_SUBSCRIPTION_KEYS) { + /* Note: the conjunction need not test both a & b for having use_lifespan set */ + if (a->subscription_keys.use_key_list != b->subscription_keys.use_key_list || + (a->subscription_keys.use_key_list && b->subscription_keys.use_key_list && + stringseqs_differ (&a->subscription_keys.key_list, &b->subscription_keys.key_list))) + delta |= QP_PRISMTECH_SUBSCRIPTION_KEYS; + } + if (check & QP_PRISMTECH_ENTITY_FACTORY) { + if (a->entity_factory.autoenable_created_entities != + b->entity_factory.autoenable_created_entities) + delta |= QP_PRISMTECH_ENTITY_FACTORY; + } + if (check & QP_PRISMTECH_SYNCHRONOUS_ENDPOINT) { + if (a->synchronous_endpoint.value != b->synchronous_endpoint.value) + delta |= QP_PRISMTECH_SYNCHRONOUS_ENDPOINT; + } + if (check & QP_RTI_TYPECODE) { + if (octetseqs_differ (&a->rti_typecode, &b->rti_typecode)) + delta |= QP_RTI_TYPECODE; + } + return delta; +} + +/*************************/ + +void nn_xqos_addtomsg (struct nn_xmsg *m, const nn_xqos_t *xqos, uint64_t wanted) +{ + /* Returns new nn_xmsg pointer (currently, reallocs may happen) */ + + uint64_t w = xqos->present & wanted; + char *tmp; +#define SIMPLE(name_, field_) \ + do { \ + if (w & QP_##name_) { \ + tmp = nn_xmsg_addpar (m, PID_##name_, sizeof (xqos->field_)); \ + *((nn_##field_##_qospolicy_t *) tmp) = xqos->field_; \ + } \ + } while (0) +#define FUNC_BY_REF(name_, field_, func_) \ + do { \ + if (w & QP_##name_) { \ + nn_xmsg_addpar_##func_ (m, PID_##name_, &xqos->field_); \ + } \ + } while (0) +#define FUNC_BY_VAL(name_, field_, func_) \ + do { \ + if (w & QP_##name_) { \ + nn_xmsg_addpar_##func_ (m, PID_##name_, xqos->field_); \ + } \ + } while (0) + + FUNC_BY_VAL (TOPIC_NAME, topic_name, string); + FUNC_BY_VAL (TYPE_NAME, type_name, string); + SIMPLE (PRESENTATION, presentation); + FUNC_BY_REF (PARTITION, partition, stringseq); + FUNC_BY_REF (GROUP_DATA, group_data, octetseq); + FUNC_BY_REF (TOPIC_DATA, topic_data, octetseq); + SIMPLE (DURABILITY, durability); + SIMPLE (DURABILITY_SERVICE, durability_service); + SIMPLE (DEADLINE, deadline); + SIMPLE (LATENCY_BUDGET, latency_budget); + SIMPLE (LIVELINESS, liveliness); + FUNC_BY_REF (RELIABILITY, reliability, reliability); + SIMPLE (DESTINATION_ORDER, destination_order); + SIMPLE (HISTORY, history); + SIMPLE (RESOURCE_LIMITS, resource_limits); + SIMPLE (TRANSPORT_PRIORITY, transport_priority); + SIMPLE (LIFESPAN, lifespan); + FUNC_BY_REF (USER_DATA, user_data, octetseq); + SIMPLE (OWNERSHIP, ownership); + SIMPLE (OWNERSHIP_STRENGTH, ownership_strength); + SIMPLE (TIME_BASED_FILTER, time_based_filter); + SIMPLE (PRISMTECH_READER_DATA_LIFECYCLE, reader_data_lifecycle); + SIMPLE (PRISMTECH_WRITER_DATA_LIFECYCLE, writer_data_lifecycle); + SIMPLE (PRISMTECH_RELAXED_QOS_MATCHING, relaxed_qos_matching); + SIMPLE (PRISMTECH_READER_LIFESPAN, reader_lifespan); + FUNC_BY_REF (PRISMTECH_SUBSCRIPTION_KEYS, subscription_keys, subscription_keys); + SIMPLE (PRISMTECH_ENTITY_FACTORY, entity_factory); + SIMPLE (PRISMTECH_SYNCHRONOUS_ENDPOINT, synchronous_endpoint); + FUNC_BY_REF (RTI_TYPECODE, rti_typecode, octetseq); +#undef FUNC_BY_REF +#undef FUNC_BY_VAL +#undef SIMPLE +} + +static void add_locators (struct nn_xmsg *m, uint64_t present, uint64_t flag, const nn_locators_t *ls, unsigned pid) +{ + const struct nn_locators_one *l; + if (present & flag) + { + for (l = ls->first; l != NULL; l = l->next) + { + char *tmp = nn_xmsg_addpar (m, pid, sizeof (nn_locator_t)); + memcpy (tmp, &l->loc, sizeof (nn_locator_t)); + } + } +} + +void nn_plist_addtomsg (struct nn_xmsg *m, const nn_plist_t *ps, uint64_t pwanted, uint64_t qwanted) +{ + /* Returns new nn_xmsg pointer (currently, reallocs may happen), or NULL + on out-of-memory. (In which case the original nn_xmsg is freed, cos + that is then required anyway */ + uint64_t w = ps->present & pwanted; + char *tmp; +#define SIMPLE_TYPE(name_, field_, type_) \ + do { \ + if (w & PP_##name_) { \ + tmp = nn_xmsg_addpar (m, PID_##name_, sizeof (ps->field_)); \ + *((type_ *) tmp) = ps->field_; \ + } \ + } while (0) +#define FUNC_BY_VAL(name_, field_, func_) \ + do { \ + if (w & PP_##name_) { \ + nn_xmsg_addpar_##func_ (m, PID_##name_, ps->field_); \ + } \ + } while (0) +#define FUNC_BY_REF(name_, field_, func_) \ + do { \ + if (w & PP_##name_) { \ + nn_xmsg_addpar_##func_ (m, PID_##name_, &ps->field_); \ + } \ + } while (0) + + nn_xqos_addtomsg (m, &ps->qos, qwanted); + SIMPLE_TYPE (PROTOCOL_VERSION, protocol_version, nn_protocol_version_t); + SIMPLE_TYPE (VENDORID, vendorid, nn_vendorid_t); + + add_locators (m, ps->present, PP_UNICAST_LOCATOR, &ps->unicast_locators, PID_UNICAST_LOCATOR); + add_locators (m, ps->present, PP_MULTICAST_LOCATOR, &ps->multicast_locators, PID_MULTICAST_LOCATOR); + add_locators (m, ps->present, PP_DEFAULT_UNICAST_LOCATOR, &ps->default_unicast_locators, PID_DEFAULT_UNICAST_LOCATOR); + add_locators (m, ps->present, PP_DEFAULT_MULTICAST_LOCATOR, &ps->default_multicast_locators, PID_DEFAULT_MULTICAST_LOCATOR); + add_locators (m, ps->present, PP_METATRAFFIC_UNICAST_LOCATOR, &ps->metatraffic_unicast_locators, PID_METATRAFFIC_UNICAST_LOCATOR); + add_locators (m, ps->present, PP_METATRAFFIC_MULTICAST_LOCATOR, &ps->metatraffic_multicast_locators, PID_METATRAFFIC_MULTICAST_LOCATOR); + + SIMPLE_TYPE (EXPECTS_INLINE_QOS, expects_inline_qos, unsigned char); + SIMPLE_TYPE (PARTICIPANT_LEASE_DURATION, participant_lease_duration, nn_duration_t); + FUNC_BY_REF (PARTICIPANT_GUID, participant_guid, guid); + SIMPLE_TYPE (BUILTIN_ENDPOINT_SET, builtin_endpoint_set, unsigned); + SIMPLE_TYPE (KEYHASH, keyhash, nn_keyhash_t); + if (w & PP_STATUSINFO) + nn_xmsg_addpar_statusinfo (m, ps->statusinfo); + SIMPLE_TYPE (COHERENT_SET, coherent_set_seqno, nn_sequence_number_t); + if (! NN_PEDANTIC_P) + FUNC_BY_REF (ENDPOINT_GUID, endpoint_guid, guid); + else + { + if (w & PP_ENDPOINT_GUID) + { + nn_xmsg_addpar_guid (m, PID_PRISMTECH_ENDPOINT_GUID, &ps->endpoint_guid); + } + } + FUNC_BY_REF (GROUP_GUID, group_guid, guid); + SIMPLE_TYPE (PRISMTECH_BUILTIN_ENDPOINT_SET, prismtech_builtin_endpoint_set, unsigned); + FUNC_BY_REF (PRISMTECH_PARTICIPANT_VERSION_INFO, prismtech_participant_version_info, parvinfo); + FUNC_BY_VAL (ENTITY_NAME, entity_name, string); + FUNC_BY_VAL (PRISMTECH_NODE_NAME, node_name, string); + FUNC_BY_VAL (PRISMTECH_EXEC_NAME, exec_name, string); + SIMPLE_TYPE (PRISMTECH_PROCESS_ID, process_id, unsigned); + SIMPLE_TYPE (PRISMTECH_SERVICE_TYPE, service_type, unsigned); + FUNC_BY_VAL (PRISMTECH_TYPE_DESCRIPTION, type_description, string); + FUNC_BY_REF (PRISMTECH_EOTINFO, eotinfo, eotinfo); + FUNC_BY_REF (IDENTITY_TOKEN, identity_token, dataholder); + FUNC_BY_REF (PERMISSIONS_TOKEN, permissions_token, dataholder); +#ifdef DDSI_INCLUDE_SSM + SIMPLE_TYPE (READER_FAVOURS_SSM, reader_favours_ssm, nn_reader_favours_ssm_t); +#endif +#undef FUNC_BY_REF +#undef FUNC_BY_VAL +#undef SIMPLE +} + +/*************************/ + +static unsigned isprint_runlen (unsigned n, const unsigned char *xs) +{ + unsigned m; + for (m = 0; m < n && xs[m] != '"' && isprint (xs[m]); m++) + ; + return m; +} + + +static void log_octetseq (logcat_t cat, unsigned n, const unsigned char *xs) +{ + unsigned i = 0; + while (i < n) + { + unsigned m = isprint_runlen(n - i, xs); + if (m >= 4) + { + nn_log (cat, "%s\"%*.*s\"", i == 0 ? "" : ",", m, m, xs); + xs += m; + i += m; + } + else + { + if (m == 0) + m = 1; + while (m--) + { + nn_log (cat, "%s%u", i == 0 ? "" : ",", *xs++); + i++; + } + } + } +} + +void nn_log_xqos (logcat_t cat, const nn_xqos_t *xqos) +{ + uint64_t p = xqos->present; + const char *prefix = ""; +#define LOGB0(fmt_) nn_log (cat, "%s" fmt_, prefix) +#define LOGB1(fmt_, arg0_) nn_log (cat, "%s" fmt_, prefix, arg0_) +#define LOGB2(fmt_, arg0_, arg1_) nn_log (cat, "%s" fmt_, prefix, arg0_, arg1_) +#define LOGB3(fmt_, arg0_, arg1_, arg2_) nn_log (cat, "%s" fmt_, prefix, arg0_, arg1_, arg2_) +#define LOGB4(fmt_, arg0_, arg1_, arg2_, arg3_) nn_log (cat, "%s" fmt_, prefix, arg0_, arg1_, arg2_, arg3_) +#define LOGB5(fmt_, arg0_, arg1_, arg2_, arg3_, arg4_) nn_log (cat, "%s" fmt_, prefix, arg0_, arg1_, arg2_, arg3_, arg4_) +#define DO(name_, body_) do { if (p & QP_##name_) { { body_ } prefix = ","; } } while (0) + +#if DDSI_DURATION_ACCORDING_TO_SPEC +#define FMT_DUR "%d.%09d" +#define PRINTARG_DUR(d) (d).sec, (d).nanosec +#else +#define FMT_DUR "%d.%09d" +#define PRINTARG_DUR(d) (d).seconds, (int) ((d).fraction/4.294967296) +#endif + + DO (TOPIC_NAME, { LOGB1 ("topic=%s", xqos->topic_name); }); + DO (TYPE_NAME, { LOGB1 ("type=%s", xqos->type_name); }); + DO (PRESENTATION, { LOGB3 ("presentation=%d:%u:%u", xqos->presentation.access_scope, xqos->presentation.coherent_access, xqos->presentation.ordered_access); }); + DO (PARTITION, { + unsigned i; + LOGB0 ("partition={"); + for (i = 0; i < xqos->partition.n; i++) { + nn_log (cat, "%s%s", (i == 0) ? "" : ",", xqos->partition.strs[i]); + } + nn_log (cat, "}"); + }); + DO (GROUP_DATA, { + LOGB1 ("group_data=%u<", xqos->group_data.length); + log_octetseq (cat, xqos->group_data.length, xqos->group_data.value); + nn_log (cat, ">"); + }); + DO (TOPIC_DATA, { + LOGB1 ("topic_data=%u<", xqos->topic_data.length); + log_octetseq (cat, xqos->topic_data.length, xqos->topic_data.value); + nn_log (cat, ">"); + }); + DO (DURABILITY, { LOGB1 ("durability=%d", xqos->durability.kind); }); + DO (DURABILITY_SERVICE, { + LOGB0 ("durability_service="); + nn_log (cat, FMT_DUR, PRINTARG_DUR (xqos->durability_service.service_cleanup_delay)); + nn_log (cat, ":{%d:%d}", xqos->durability_service.history.kind, xqos->durability_service.history.depth); + nn_log (cat, ":{%d:%d:%d}", xqos->durability_service.resource_limits.max_samples, xqos->durability_service.resource_limits.max_instances, xqos->durability_service.resource_limits.max_samples_per_instance); + }); + DO (DEADLINE, { LOGB1 ("deadline="FMT_DUR, PRINTARG_DUR (xqos->deadline.deadline)); }); + DO (LATENCY_BUDGET, { LOGB1 ("latency_budget="FMT_DUR, PRINTARG_DUR (xqos->latency_budget.duration)); }); + DO (LIVELINESS, { LOGB2 ("liveliness=%d:"FMT_DUR, xqos->liveliness.kind, PRINTARG_DUR (xqos->liveliness.lease_duration)); }); + DO (RELIABILITY, { LOGB2 ("reliability=%d:"FMT_DUR, xqos->reliability.kind, PRINTARG_DUR (xqos->reliability.max_blocking_time)); }); + DO (DESTINATION_ORDER, { LOGB1 ("destination_order=%d", xqos->destination_order.kind); }); + DO (HISTORY, { LOGB2 ("history=%d:%d", xqos->history.kind, xqos->history.depth); }); + DO (RESOURCE_LIMITS, { LOGB3 ("resource_limits=%d:%d:%d", xqos->resource_limits.max_samples, xqos->resource_limits.max_instances, xqos->resource_limits.max_samples_per_instance); }); + DO (TRANSPORT_PRIORITY, { LOGB1 ("transport_priority=%d", xqos->transport_priority.value); }); + DO (LIFESPAN, { LOGB1 ("lifespan="FMT_DUR, PRINTARG_DUR (xqos->lifespan.duration)); }); + DO (USER_DATA, { + LOGB1 ("user_data=%u<", xqos->user_data.length); + log_octetseq (cat, xqos->user_data.length, xqos->user_data.value); + nn_log (cat, ">"); + }); + DO (OWNERSHIP, { LOGB1 ("ownership=%d", xqos->ownership.kind); }); + DO (OWNERSHIP_STRENGTH, { LOGB1 ("ownership_strength=%d", xqos->ownership_strength.value); }); + DO (TIME_BASED_FILTER, { LOGB1 ("time_based_filter="FMT_DUR, PRINTARG_DUR (xqos->time_based_filter.minimum_separation)); }); + DO (PRISMTECH_READER_DATA_LIFECYCLE, { LOGB5 ("reader_data_lifecycle="FMT_DUR":"FMT_DUR":%u:%u:%d", PRINTARG_DUR (xqos->reader_data_lifecycle.autopurge_nowriter_samples_delay), PRINTARG_DUR (xqos->reader_data_lifecycle.autopurge_disposed_samples_delay), xqos->reader_data_lifecycle.autopurge_dispose_all, xqos->reader_data_lifecycle.enable_invalid_samples, (int) xqos->reader_data_lifecycle.invalid_sample_visibility); }); + DO (PRISMTECH_WRITER_DATA_LIFECYCLE, { + LOGB3 ("writer_data_lifecycle={%u,"FMT_DUR","FMT_DUR"}", + xqos->writer_data_lifecycle.autodispose_unregistered_instances, + PRINTARG_DUR (xqos->writer_data_lifecycle.autounregister_instance_delay), + PRINTARG_DUR (xqos->writer_data_lifecycle.autopurge_suspended_samples_delay)); }); + DO (PRISMTECH_RELAXED_QOS_MATCHING, { LOGB1 ("relaxed_qos_matching=%u", xqos->relaxed_qos_matching.value); }); + DO (PRISMTECH_READER_LIFESPAN, { LOGB2 ("reader_lifespan={%u,"FMT_DUR"}", xqos->reader_lifespan.use_lifespan, PRINTARG_DUR (xqos->reader_lifespan.duration)); }); + DO (PRISMTECH_SUBSCRIPTION_KEYS, { + unsigned i; + LOGB1 ("subscription_keys={%u,{", xqos->subscription_keys.use_key_list); + for (i = 0; i < xqos->subscription_keys.key_list.n; i++) { + nn_log (cat, "%s%s", (i == 0) ? "" : ",", xqos->subscription_keys.key_list.strs[i]); + } + nn_log (cat, "}}"); + }); + DO (PRISMTECH_ENTITY_FACTORY, { LOGB1 ("entity_factory=%u", xqos->entity_factory.autoenable_created_entities); }); + DO (PRISMTECH_SYNCHRONOUS_ENDPOINT, { LOGB1 ("synchronous_endpoint=%u", xqos->synchronous_endpoint.value); }); + DO (PROPERTY, { + unsigned i; + LOGB0 ("property={{"); + for (i = 0; i < xqos->property.value.n; i++) { + nn_log (cat, "(\"%s\",\"%s\",%d)", + xqos->property.value.props[i].name ? xqos->property.value.props[i].name : "nil", + xqos->property.value.props[i].value ? xqos->property.value.props[i].value : "nil", + (int)xqos->property.value.props[i].propagate); + } + nn_log (cat, "},{"); + for (i = 0; i < xqos->property.binary_value.n; i++) { + nn_log (cat, "(\"%s\",<", + xqos->property.binary_value.props[i].name ? xqos->property.binary_value.props[i].name : "nil"); + log_octetseq (cat, xqos->property.binary_value.props[i].value.length, xqos->property.binary_value.props[i].value.value); + nn_log (cat, ">,%d)", + (int)xqos->property.binary_value.props[i].propagate); + } + nn_log (cat, "}}"); + }); + DO (RTI_TYPECODE, { + LOGB1 ("rti_typecode=%u<", xqos->rti_typecode.length); + log_octetseq (cat, xqos->rti_typecode.length, xqos->rti_typecode.value); + nn_log (cat, ">"); + }); + +#undef PRINTARG_DUR +#undef FMT_DUR +#undef DO +#undef LOGB5 +#undef LOGB4 +#undef LOGB3 +#undef LOGB2 +#undef LOGB1 +#undef LOGB0 +} diff --git a/src/core/ddsi/src/q_qosmatch.c b/src/core/ddsi/src/q_qosmatch.c new file mode 100644 index 0000000..0ab9009 --- /dev/null +++ b/src/core/ddsi/src/q_qosmatch.c @@ -0,0 +1,222 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include + +#include "ddsi/q_time.h" +#include "ddsi/q_xqos.h" +#include "ddsi/q_misc.h" +#include "ddsi/q_qosmatch.h" + +int is_wildcard_partition (const char *str) +{ + return strchr (str, '*') || strchr (str, '?'); +} + +static int partition_patmatch_p (const char *pat, const char *name) +{ + /* pat may be a wildcard expression, name must not be */ + if (!is_wildcard_partition (pat)) + /* no wildcard in pat => must equal name */ + return (strcmp (pat, name) == 0); + else if (is_wildcard_partition (name)) + /* (we know: wildcard in pat) => wildcard in name => no match */ + return 0; + else + return ddsi2_patmatch (pat, name); +} + +static int partitions_match_default (const nn_xqos_t *x) +{ + unsigned i; + if (!(x->present & QP_PARTITION) || x->partition.n == 0) + return 1; + for (i = 0; i < x->partition.n; i++) + if (partition_patmatch_p (x->partition.strs[i], "")) + return 1; + return 0; +} + +int partitions_match_p (const nn_xqos_t *a, const nn_xqos_t *b) +{ + if (!(a->present & QP_PARTITION) || a->partition.n == 0) + return partitions_match_default (b); + else if (!(b->present & QP_PARTITION) || b->partition.n == 0) + return partitions_match_default (a); + else + { + unsigned i, j; + for (i = 0; i < a->partition.n; i++) + for (j = 0; j < b->partition.n; j++) + { + if (partition_patmatch_p (a->partition.strs[i], b->partition.strs[j]) || + partition_patmatch_p (b->partition.strs[j], a->partition.strs[i])) + return 1; + } + return 0; + } +} + +int partition_match_based_on_wildcard_in_left_operand (const nn_xqos_t *a, const nn_xqos_t *b, const char **realname) +{ + assert (partitions_match_p (a, b)); + if (!(a->present & QP_PARTITION) || a->partition.n == 0) + { + return 0; + } + else if (!(b->present & QP_PARTITION) || b->partition.n == 0) + { + /* Either A explicitly includes the default partition, or it is a + wildcard that matches it */ + unsigned i; + for (i = 0; i < a->partition.n; i++) + if (strcmp (a->partition.strs[i], "") == 0) + return 0; + *realname = ""; + return 1; + } + else + { + unsigned i, j; + int maybe_yes = 0; + for (i = 0; i < a->partition.n; i++) + for (j = 0; j < b->partition.n; j++) + { + if (partition_patmatch_p (a->partition.strs[i], b->partition.strs[j])) + { + if (!is_wildcard_partition (a->partition.strs[i])) + return 0; + else + { + *realname = b->partition.strs[j]; + maybe_yes = 1; + } + } + } + return maybe_yes; + } +} + +static int ddsi_duration_is_lt (nn_duration_t a0, nn_duration_t b0) +{ + /* inf counts as <= inf */ + const int64_t a = nn_from_ddsi_duration (a0); + const int64_t b = nn_from_ddsi_duration (b0); + if (a == T_NEVER) + return 0; + else if (b == T_NEVER) + return 1; + else + return a < b; +} + +/* Duplicates of DDS policy ids to avoid inclusion of actual definitions */ + +#define Q_INVALID_QOS_POLICY_ID 0 +#define Q_USERDATA_QOS_POLICY_ID 1 +#define Q_DURABILITY_QOS_POLICY_ID 2 +#define Q_PRESENTATION_QOS_POLICY_ID 3 +#define Q_DEADLINE_QOS_POLICY_ID 4 +#define Q_LATENCYBUDGET_QOS_POLICY_ID 5 +#define Q_OWNERSHIP_QOS_POLICY_ID 6 +#define Q_OWNERSHIPSTRENGTH_QOS_POLICY_ID 7 +#define Q_LIVELINESS_QOS_POLICY_ID 8 +#define Q_TIMEBASEDFILTER_QOS_POLICY_ID 9 +#define Q_PARTITION_QOS_POLICY_ID 10 +#define Q_RELIABILITY_QOS_POLICY_ID 11 +#define Q_DESTINATIONORDER_QOS_POLICY_ID 12 +#define Q_HISTORY_QOS_POLICY_ID 13 +#define Q_RESOURCELIMITS_QOS_POLICY_ID 14 +#define Q_ENTITYFACTORY_QOS_POLICY_ID 15 +#define Q_WRITERDATALIFECYCLE_QOS_POLICY_ID 16 +#define Q_READERDATALIFECYCLE_QOS_POLICY_ID 17 +#define Q_TOPICDATA_QOS_POLICY_ID 18 +#define Q_GROUPDATA_QOS_POLICY_ID 19 +#define Q_TRANSPORTPRIORITY_QOS_POLICY_ID 20 +#define Q_LIFESPAN_QOS_POLICY_ID 21 +#define Q_DURABILITYSERVICE_QOS_POLICY_ID 22 + +int32_t qos_match_p (const nn_xqos_t *rd, const nn_xqos_t *wr) +{ +#ifndef NDEBUG + unsigned musthave = (QP_RXO_MASK | QP_PARTITION | QP_TOPIC_NAME | QP_TYPE_NAME); + assert ((rd->present & musthave) == musthave); + assert ((wr->present & musthave) == musthave); +#endif + if (strcmp (rd->topic_name, wr->topic_name) != 0) + { + return Q_INVALID_QOS_POLICY_ID; + } + if (strcmp (rd->type_name, wr->type_name) != 0) + { + return Q_INVALID_QOS_POLICY_ID; + } + if (rd->relaxed_qos_matching.value || wr->relaxed_qos_matching.value) + { + if (rd->reliability.kind != wr->reliability.kind) + { + return Q_RELIABILITY_QOS_POLICY_ID; + } + } + else + { + if (rd->reliability.kind > wr->reliability.kind) + { + return Q_RELIABILITY_QOS_POLICY_ID; + } + if (rd->durability.kind > wr->durability.kind) + { + return Q_DURABILITY_QOS_POLICY_ID; + } + if (rd->presentation.access_scope > wr->presentation.access_scope) + { + return Q_PRESENTATION_QOS_POLICY_ID; + } + if (rd->presentation.coherent_access > wr->presentation.coherent_access) + { + return Q_PRESENTATION_QOS_POLICY_ID; + } + if (rd->presentation.ordered_access > wr->presentation.ordered_access) + { + return Q_PRESENTATION_QOS_POLICY_ID; + } + if (ddsi_duration_is_lt (rd->deadline.deadline, wr->deadline.deadline)) + { + return Q_DEADLINE_QOS_POLICY_ID; + } + if (ddsi_duration_is_lt (rd->latency_budget.duration, wr->latency_budget.duration)) + { + return Q_LATENCYBUDGET_QOS_POLICY_ID; + } + if (rd->ownership.kind != wr->ownership.kind) + { + return Q_OWNERSHIP_QOS_POLICY_ID; + } + if (rd->liveliness.kind > wr->liveliness.kind) + { + return Q_LIVELINESS_QOS_POLICY_ID; + } + if (ddsi_duration_is_lt (rd->liveliness.lease_duration, wr->liveliness.lease_duration)) + { + return Q_LIVELINESS_QOS_POLICY_ID; + } + if (rd->destination_order.kind > wr->destination_order.kind) + { + return Q_DESTINATIONORDER_QOS_POLICY_ID; + } + } + if (!partitions_match_p (rd, wr)) + { + return Q_PARTITION_QOS_POLICY_ID; + } + return -1; +} diff --git a/src/core/ddsi/src/q_radmin.c b/src/core/ddsi/src/q_radmin.c new file mode 100644 index 0000000..65e7cd1 --- /dev/null +++ b/src/core/ddsi/src/q_radmin.c @@ -0,0 +1,2680 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include +#include + +#if HAVE_VALGRIND && ! defined (NDEBUG) +#include +#define USE_VALGRIND 1 +#else +#define USE_VALGRIND 0 +#endif + +#include "os/os.h" + +#include "util/ut_avl.h" +#include "ddsi/q_protocol.h" +#include "ddsi/q_rtps.h" +#include "ddsi/q_misc.h" + +#include "ddsi/q_config.h" +#include "ddsi/q_log.h" + +#include "ddsi/q_align.h" +#include "ddsi/q_plist.h" +#include "ddsi/q_unused.h" +#include "ddsi/q_radmin.h" +#include "ddsi/q_bitset.h" +#include "ddsi/q_thread.h" +#include "ddsi/q_globals.h" /* for mattr, cattr */ + +#include "ddsi/sysdeps.h" + +/* Avoiding all nn_log-related activities when LC_RADMIN is not set + (and it hardly ever is, as it is not even included in "trace") + saves a couple of % CPU on a high-rate subscriber - that's worth + it. So we need a macro & a support function. */ +static int trace_radmin (const char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + nn_vlog (LC_RADMIN, fmt, ap); + va_end (ap); + return 0; +} +#define TRACE_RADMIN(args) ((config.enabled_logcats & LC_RADMIN) ? (trace_radmin args) : 0) + +/* OVERVIEW ------------------------------------------------------------ + + The receive path of DDSI2 has any number of receive threads that + accept data from sockets and (synchronously) push it up the + protocol stack, potentially offloading processing to other threads + at some point. In particular, delivery of data can safely be + offloaded. + + Each receive thread MUST process each message synchronously to the + point where all additional indexing and other administrative data + derived from the message has been stored in memory. This storage + is _always_ adjacent to the message that caused it. Also, once it + finishes processing a message, the reference count of that message + may not be incremented anymore. + + In practice that means the receive thread can do everything by + itself (handling acks and heartbeats, handling discovery, + delivering data to the kernel), or it can offload everything but + defragmentation and reordering. + + The data structures and functions in this file are all concerned + with the storage of messages in buffers, organising their parts + into ordered chains of fragments of (DDS) samples, reordering them + into chains of consecutive samples, and queueing these chains for + further processing. + + Storage is organised in the following hierarchy; rdata is included + because it is is very intimately involved with the reference + counting. For the indexing structures for defragmenting and + reordering messages, see RDATA, DEFRAG and REORDER below. + + nn_rbufpool + + One or more rbufs. Currently, an rbufpool is owned by + a single receive thread, and only this thread may + allocate memory from the rbufs contained in the pool + and increment reference counts to the messages in it, + while all threads may decrement these reference counts + / release memory from it. + + (It is probably better to share the pool amongst all + threads and make the rbuf the thing owned by this + thread; and in fact the buffer pool isn't really + necessary 'cos they handle multiple messages and + therefore the malloc/free overhead is negligible. It + does provide a convenient location for storing some + constant data.) + + nn_rbuf + + Largish buffer for receiving several UDP packets and + for storing partially decoded and indexing information + directly following the packet. + + nn_rmsg + + One message in an rbuf; the layout for one message is + rmsg, raw udp packet, decoder stuff mixed with rdata, + defragmentation and message reordering state. One + rbuf can contain many messages. + + nn_rdata + + Represents one Data/DataFrag submessage. These + contain some administrative data & point to the + corresponding part of the message, and are referenced + by the defragmentation and reordering (defrag, reorder) + tables and the delivery queues. + + Each rmsg contains a reference count tracking all references to all + rdatas contained in that message. All data for one message in the + rbuf (raw data, decoder info, &c.) is dependent on the refcount of + the rmsg: once that reference count goes to zero _all_ dependent + stuff becomes invalid immediately. + + As noted, the receive thread that owns the rbuf is the only one + allowed to add data to it, which implies that this thread must do + all defragmenting and reordering synchronously. Delivery can be + offloaded to another thread, and it remains to be seen which thread + is best used for deserializing the data. + + The main advantage of restricting the adding of data to the buffer + to the buffer's owning thread is that it allows us to simply append + decoding information to the message as it becomes available while + processing the message, without risking interference from another + thread. This includes decoded parameter lists/inline QoS settings, + defragmenting information, &c. + + Once the synchronous processing of a message (a UDP packet) is + completed, every adminstrative thing related to that message is + contained in a single block of memory, and can be released very + easily, regardless of whether the rbuf is a circular buffer, has a + minimalistic heap inside it, or is simply discarded when the end is + reached. + + Each rdata (submessage) that has been delivered (or need never be + delivered) is not referenced anywhere and will therefore not + contribute to rmsg::refcount, so once all rdatas of an rmsg have + been delivered, rmsg::refcount will drop to 0. If all submessages + are processed by the receive thread, or delivery is delegated to + other threads that happen to finish doing so before the receive + thread is done processing the message, the message can be discarded + trivially by not even updating the memory allocation info in the + rbuf. + + Just creating an rdata is not sufficient reason for the reference + count in the corresponding rmsg to be incremented: that happens + once the defragmenter decides to not throw it away (either because + it stores it or because it returns it for forwarding to reordering + or delivery). (Which is possible because both defragmentation and + reordering are synchronous.) + + While synchronously processing the message, the reference count is + biased by 2**31 just so we can detect some illegal activities. + Furthermore, while still synchronous, each rdata contributes the + number of actual references to the message plus 2**20 to the + refcount. This second bias allows delaying accounting for the + actual references until after processing all reorder admins, saving + us from having to update them potentially many times. + + The space needed for processing a message is limited: a UDP packet + is never larger than 64kB (and it seems very unwise to actually use + such large packets!), and there is only a finite amount of data + that gets added to it while interpreting the message. Although the + exact amount is not yet known, it seems very unlikely that the + decoding data for one packet would exceed 64kB size, though one had + better be careful just in case. So a maximum RMSG size of 128kB + and an RBUF size of 1MB should be quite reasonable. + + Sequence of operations: + + receive_thread () + { + ... + rbpool = nn_rbufpool_new (1MB, 128kB) + ... + + while ... + rmsg = nn_rmsg_new (rbpool) + actualsize = recvfrom (rmsg.payload, 64kB) + nn_rmsg_setsize (rmsg, actualsize) + process (rmsg) + nn_rmsg_commit (rmsg) + + ... ensure no references to any buffer in rbpool exist ... + nn_rbufpool_free (rbpool) + ... + } + + If there are no outstanding references to the message, commit() + simply discards it and new() returns the same address next time + round. + + Processing of a single message in process() is roughly as follows: + + for rdata in each Data/DataFrag submessage in rmsg + sampleinfo.seq = XX; + sampleinfo.fragsize = XX; + sampleinfo.size = XX; + sampleinfo.(others) = XX if first fragment, else not important + sample = nn_defrag_rsample (pwr->defrag, rdata, &sampleinfo) + if sample + fragchain = nn_rsample_fragchain (sample) + refcount_adjust = 0; + + if send-to-proxy-writer-reorder + if nn_reorder_rsample (&sc, pwr->reorder, sample, &refcount_adjust) + == DELIVER + deliver-to-group (pwr, sc) + else + for (m in out-of-sync-reader-matches) + sample' = nn_reorder_rsample_dup (rmsg, sample) + if nn_reorder_rsample (&sc, m->reorder, sample, &refcount_adjust) + == DELIVER + deliver-to-reader (m->reader, sc) + + nn_fragchain_adjust_refcount (fragchain, refcount_adjust) + fi + rof + + Where deliver-to-x() must of course decrement refcounts after + delivery when done, using nn_fragchain_unref(). See also REORDER + for the subtleties of the refcount game. + + Note that there is an alternative to all this trickery with + fragment chains and deserializing off these fragments chains: + allocating sufficient memory upon reception of the first fragment, + and then just memcpy'ing the bytes in, with a simple bitmask to + keep track of which fragments have been received and which have not + yet been. + + _The_ argument against that is a very unreliable network with huge + messages: the way we do it here never needs more than a constant + factor over what is actually received, whereas the simple + alternative would blow up nearly instantaneously. Maybe not if you + drop samples halfway through defragmenting aggressively, but then + you can't get anything through anymore if there are multiple + writers. + + Gaps and Heartbeats prune the defragmenting index and are (when + needed) stored as intervals of specially marked rdatas in the + reordering indices. + + The procedure for a Gap is: + + for a Gap [a,b] in rmsg + defrag_notegap (a, b+1) + refcount_adjust = 0 + gap = nn_rdata_newgap (rmsg); + if nn_reorder_gap (&sc, reorder, gap, a, b+1, &refcount_adjust) + deliver-to-group (pwr, sc) + for (m in out-of-sync-reader-matches) + if nn_reorder_gap (&sc, m->reorder, gap, a, b+1, &refcount_adjust) + deliver-to-reader (m->reader, sc) + nn_fragchain_adjust_refcount (gap, refcount_adjust) + + Note that a Gap always gets processed both by the primary and by + the secondary reorder admins. This is because it covers a range. + + A heartbeat is similar, except that a heartbeat [a,b] results in a + gap [1,a-1]. */ + +/* RBUFPOOL ------------------------------------------------------------ */ + +struct nn_rbufpool { + /* An rbuf pool is owned by a receive thread, and that thread is the + only allocating rmsgs from the rbufs in the pool. Any thread may + be releasing buffers to the pool as they become empty. + + Currently, we only have maintain a current rbuf, which gets + replaced when allocating a new one from it fails. Any rbufs that + are released are freed completely if different from the current + one. + + Could trivially be done lockless, except that it requires + compare-and-swap, and we don't have that. But it hardly ever + happens anyway. */ + os_mutex lock; + struct nn_rbuf *current; + uint32_t rbuf_size; + uint32_t max_rmsg_size; +#ifndef NDEBUG + /* Thread that owns this pool, so we can check that no other thread + is calling functions only the owner may use. */ + os_threadId owner_tid; +#endif +}; + +static struct nn_rbuf *nn_rbuf_alloc_new (struct nn_rbufpool *rbufpool); +static void nn_rbuf_release (struct nn_rbuf *rbuf); + +static uint32_t align8uint32 (uint32_t x) +{ + return (x + 7u) & (uint32_t)-8; +} + +#ifndef NDEBUG +#define ASSERT_RBUFPOOL_OWNER(rbp) (assert (os_threadEqual (os_threadIdSelf (), (rbp)->owner_tid))) +#else +#define ASSERT_RBUFPOOL_OWNER(rbp) ((void) (0)) +#endif + +static uint32_t max_uint32 (uint32_t a, uint32_t b) +{ + return a >= b ? a : b; +} + +static uint32_t max_rmsg_size_w_hdr (uint32_t max_rmsg_size) +{ + /* rbuf_alloc allocates max_rmsg_size, which is actually max + _payload_ size (this is so 64kB max_rmsg_size always suffices for + a UDP packet, regardless of internal structure). We use it for + nn_rmsg and nn_rmsg_chunk, but the difference in size is + negligible really. So in the interest of simplicity, we always + allocate for the worst case, and may waste a few bytes here or + there. */ + return + max_uint32 ((uint32_t) offsetof (struct nn_rmsg, chunk.u.payload), + (uint32_t) offsetof (struct nn_rmsg_chunk, u.payload)) + + max_rmsg_size; +} + +struct nn_rbufpool *nn_rbufpool_new (uint32_t rbuf_size, uint32_t max_rmsg_size) +{ + struct nn_rbufpool *rbp; + + assert (max_rmsg_size > 0); + assert (rbuf_size >= max_rmsg_size_w_hdr (max_rmsg_size)); + + if ((rbp = os_malloc (sizeof (*rbp))) == NULL) + goto fail_rbp; +#ifndef NDEBUG + rbp->owner_tid = os_threadIdSelf (); +#endif + + os_mutexInit (&rbp->lock); + + rbp->rbuf_size = rbuf_size; + rbp->max_rmsg_size = max_rmsg_size; + +#if USE_VALGRIND + VALGRIND_CREATE_MEMPOOL (rbp, 0, 0); +#endif + + if ((rbp->current = nn_rbuf_alloc_new (rbp)) == NULL) + goto fail_rbuf; + return rbp; + + fail_rbuf: +#if USE_VALGRIND + VALGRIND_DESTROY_MEMPOOL (rbp); +#endif + os_mutexDestroy (&rbp->lock); + os_free (rbp); + fail_rbp: + return NULL; +} + +void nn_rbufpool_setowner (UNUSED_ARG_NDEBUG (struct nn_rbufpool *rbp), UNUSED_ARG_NDEBUG (os_threadId tid)) +{ +#ifndef NDEBUG + rbp->owner_tid = tid; +#endif +} + +void nn_rbufpool_free (struct nn_rbufpool *rbp) +{ +#if 0 + /* Anyone may free it: I want to be able to stop the receive + threads, then stop all other asynchronous processing, then clear + out the buffers. That's is the only way to verify that the + reference counts are all 0, as they should be. */ + ASSERT_RBUFPOOL_OWNER (rbp); +#endif + nn_rbuf_release (rbp->current); +#if USE_VALGRIND + VALGRIND_DESTROY_MEMPOOL (rbp); +#endif + os_mutexDestroy (&rbp->lock); + os_free (rbp); +} + +/* RBUF ---------------------------------------------------------------- */ + +struct nn_rbuf { + os_atomic_uint32_t n_live_rmsg_chunks; + uint32_t size; + uint32_t max_rmsg_size; + struct nn_rbufpool *rbufpool; + + /* Allocating sequentially, releasing in random order, not bothering + to reuse memory as soon as it becomes available again. I think + this will have to change eventually, but this is the easiest + approach. Changes would be confined rmsg_new and rmsg_free. */ + unsigned char *freeptr; + + union { + /* raw data array, nn_rbuf::size bytes long in reality */ + unsigned char raw[1]; + + /* to ensure reasonable alignment of raw[] */ + int64_t l; + double d; + void *p; + } u; +}; + +static struct nn_rbuf *nn_rbuf_alloc_new (struct nn_rbufpool *rbufpool) +{ + struct nn_rbuf *rb; + ASSERT_RBUFPOOL_OWNER (rbufpool); + + if ((rb = os_malloc (offsetof (struct nn_rbuf, u.raw) + rbufpool->rbuf_size)) == NULL) + return NULL; +#if USE_VALGRIND + VALGRIND_MAKE_MEM_NOACCESS (rb->u.raw, rbufpool->rbuf_size); +#endif + + rb->rbufpool = rbufpool; + os_atomic_st32 (&rb->n_live_rmsg_chunks, 1); + rb->size = rbufpool->rbuf_size; + rb->max_rmsg_size = rbufpool->max_rmsg_size; + rb->freeptr = rb->u.raw; + TRACE_RADMIN (("rbuf_alloc_new(%p) = %p\n", rbufpool, rb)); + return rb; +} + +static struct nn_rbuf *nn_rbuf_new (struct nn_rbufpool *rbufpool) +{ + struct nn_rbuf *rb; + assert (rbufpool->current); + ASSERT_RBUFPOOL_OWNER (rbufpool); + if ((rb = nn_rbuf_alloc_new (rbufpool)) != NULL) + { + os_mutexLock (&rbufpool->lock); + nn_rbuf_release (rbufpool->current); + rbufpool->current = rb; + os_mutexUnlock (&rbufpool->lock); + } + return rb; +} + +static void nn_rbuf_release (struct nn_rbuf *rbuf) +{ + struct nn_rbufpool *rbp = rbuf->rbufpool; + TRACE_RADMIN (("rbuf_release(%p) pool %p current %p\n", rbuf, rbp, rbp->current)); + if (os_atomic_dec32_ov (&rbuf->n_live_rmsg_chunks) == 1) + { + TRACE_RADMIN (("rbuf_release(%p) free\n", rbuf)); + os_free (rbuf); + } +} + +/* RMSG ---------------------------------------------------------------- */ + +/* There are at most 64kB / 32B = 2**11 rdatas in one rmsg, because an + rmsg is limited to 64kB and a Data submessage is at least 32B bytes + in size. With 1 bit taken for committed/uncommitted (needed for + debugging purposes only), there's room for up to 2**20 out-of-sync + readers matched to one proxy writer. I believe it sufficiently + unlikely that anyone will ever attempt to have 1 million readers on + one node to one topic/partition ... */ +#define RMSG_REFCOUNT_UNCOMMITTED_BIAS (1u << 31) +#define RMSG_REFCOUNT_RDATA_BIAS (1u << 20) +#ifndef NDEBUG +#define ASSERT_RMSG_UNCOMMITTED(rmsg) (assert (os_atomic_ld32 (&(rmsg)->refcount) >= RMSG_REFCOUNT_UNCOMMITTED_BIAS)) +#else +#define ASSERT_RMSG_UNCOMMITTED(rmsg) ((void) 0) +#endif + +static void *nn_rbuf_alloc (struct nn_rbufpool *rbufpool) +{ + /* Note: only one thread calls nn_rmsg_new on a pool */ + uint32_t asize = max_rmsg_size_w_hdr (rbufpool->max_rmsg_size); + struct nn_rbuf *rb; + TRACE_RADMIN (("rmsg_rbuf_alloc(%p, %u)\n", (void *) rbufpool, asize)); + ASSERT_RBUFPOOL_OWNER (rbufpool); + rb = rbufpool->current; + assert (rb != NULL); + assert (rb->freeptr >= rb->u.raw); + assert (rb->freeptr <= rb->u.raw + rb->size); + + if ((uint32_t) (rb->u.raw + rb->size - rb->freeptr) < asize) + { + /* not enough space left for new rmsg */ + if ((rb = nn_rbuf_new (rbufpool)) == NULL) + return NULL; + + /* a new one should have plenty of space */ + assert ((uint32_t) (rb->u.raw + rb->size - rb->freeptr) >= asize); + } + + TRACE_RADMIN (("rmsg_rbuf_alloc(%p, %u) = %p\n", (void *) rbufpool, asize, (void *) rb->freeptr)); +#if USE_VALGRIND + VALGRIND_MEMPOOL_ALLOC (rbufpool, rb->freeptr, asize); +#endif + return rb->freeptr; +} + +static void init_rmsg_chunk (struct nn_rmsg_chunk *chunk, struct nn_rbuf *rbuf) +{ + chunk->rbuf = rbuf; + chunk->next = NULL; + chunk->size = 0; + os_atomic_inc32 (&rbuf->n_live_rmsg_chunks); +} + +struct nn_rmsg *nn_rmsg_new (struct nn_rbufpool *rbufpool) +{ + /* Note: only one thread calls nn_rmsg_new on a pool */ + struct nn_rmsg *rmsg; + TRACE_RADMIN (("rmsg_new(%p)\n", rbufpool)); + + rmsg = nn_rbuf_alloc (rbufpool); + if (rmsg == NULL) + return NULL; + + /* Reference to this rmsg, undone by rmsg_commit(). */ + os_atomic_st32 (&rmsg->refcount, RMSG_REFCOUNT_UNCOMMITTED_BIAS); + /* Initial chunk */ + init_rmsg_chunk (&rmsg->chunk, rbufpool->current); + rmsg->lastchunk = &rmsg->chunk; + /* Incrementing freeptr happens in commit(), so that discarding the + message is really simple. */ + TRACE_RADMIN (("rmsg_new(%p) = %p\n", rbufpool, rmsg)); + return rmsg; +} + +void nn_rmsg_setsize (struct nn_rmsg *rmsg, uint32_t size) +{ + uint32_t size8 = align8uint32 (size); + TRACE_RADMIN (("rmsg_setsize(%p, %u => %u)\n", rmsg, size, size8)); + ASSERT_RBUFPOOL_OWNER (rmsg->chunk.rbuf->rbufpool); + ASSERT_RMSG_UNCOMMITTED (rmsg); + assert (os_atomic_ld32 (&rmsg->refcount) == RMSG_REFCOUNT_UNCOMMITTED_BIAS); + assert (rmsg->chunk.size == 0); + assert (size8 <= rmsg->chunk.rbuf->max_rmsg_size); + assert (rmsg->lastchunk == &rmsg->chunk); + rmsg->chunk.size = size8; +#if USE_VALGRIND + VALGRIND_MEMPOOL_CHANGE (rmsg->chunk.rbuf->rbufpool, rmsg, rmsg, offsetof (struct nn_rmsg, chunk.u.payload) + rmsg->chunk.size); +#endif +} + +void nn_rmsg_free (struct nn_rmsg *rmsg) +{ + /* Note: any thread may call rmsg_free. + + FIXME: note that we could optimise by moving rbuf->freeptr back + in (the likely to be fairly normal) case free space follows this + rmsg. Except that that would require synchronising new() and + free() which we don't do currently. And ideally, you'd use + compare-and-swap for this. */ + struct nn_rmsg_chunk *c; + TRACE_RADMIN (("rmsg_free(%p)\n", rmsg)); + assert (os_atomic_ld32 (&rmsg->refcount) == 0); + c = &rmsg->chunk; + while (c) + { + struct nn_rbuf *rbuf = c->rbuf; + struct nn_rmsg_chunk *c1 = c->next; +#if USE_VALGRIND + if (c == &rmsg->chunk) { + VALGRIND_MEMPOOL_FREE (rbuf->rbufpool, rmsg); + } else { + VALGRIND_MEMPOOL_FREE (rbuf->rbufpool, c); + } +#endif + assert (os_atomic_ld32 (&rbuf->n_live_rmsg_chunks) > 0); + nn_rbuf_release (rbuf); + c = c1; + } +} + +static void commit_rmsg_chunk (struct nn_rmsg_chunk *chunk) +{ + struct nn_rbuf *rbuf = chunk->rbuf; + TRACE_RADMIN (("commit_rmsg_chunk(%p)\n", chunk)); + rbuf->freeptr = chunk->u.payload + chunk->size; +} + +void nn_rmsg_commit (struct nn_rmsg *rmsg) +{ + /* Note: only one thread calls rmsg_commit -- the one that created + it in the first place. + + If there are no outstanding references, we can simply reuse the + memory. This happens, e.g., when the message is invalid, doesn't + contain anything processed asynchronously, or the scheduling + happens to be such that any asynchronous activities have + completed before we got to commit. */ + struct nn_rmsg_chunk *chunk = rmsg->lastchunk; + TRACE_RADMIN (("rmsg_commit(%p) refcount 0x%x last-chunk-size %u\n", + rmsg, rmsg->refcount, chunk->size)); + ASSERT_RBUFPOOL_OWNER (chunk->rbuf->rbufpool); + ASSERT_RMSG_UNCOMMITTED (rmsg); + assert (chunk->size <= chunk->rbuf->max_rmsg_size); + assert ((chunk->size % 8) == 0); + assert (os_atomic_ld32 (&rmsg->refcount) >= RMSG_REFCOUNT_UNCOMMITTED_BIAS); + assert (os_atomic_ld32 (&rmsg->chunk.rbuf->n_live_rmsg_chunks) > 0); + assert (os_atomic_ld32 (&chunk->rbuf->n_live_rmsg_chunks) > 0); + assert (chunk->rbuf->rbufpool->current == chunk->rbuf); + if (os_atomic_sub32_nv (&rmsg->refcount, RMSG_REFCOUNT_UNCOMMITTED_BIAS) == 0) + nn_rmsg_free (rmsg); + else + { + /* Other references exist, so either stored in defrag, reorder + and/or delivery queue */ + TRACE_RADMIN (("rmsg_commit(%p) => keep\n", rmsg)); + commit_rmsg_chunk (chunk); + } +} + +static void nn_rmsg_addbias (struct nn_rmsg *rmsg) +{ + /* Note: only the receive thread that owns the receive pool may + increase the reference count, and only while it is still + uncommitted. + + However, other threads (e.g., delivery threads) may have been + triggered already, so the increment must be done atomically. */ + TRACE_RADMIN (("rmsg_addbias(%p)\n", rmsg)); + ASSERT_RBUFPOOL_OWNER (rmsg->chunk.rbuf->rbufpool); + ASSERT_RMSG_UNCOMMITTED (rmsg); + os_atomic_add32 (&rmsg->refcount, RMSG_REFCOUNT_RDATA_BIAS); +} + +static void nn_rmsg_rmbias_and_adjust (struct nn_rmsg *rmsg, int adjust) +{ + /* This can happen to any rmsg referenced by an sample still + progressing through the pipeline, but only by the receive + thread. Can't require it to be uncommitted. */ + uint32_t sub; + TRACE_RADMIN (("rmsg_rmbias_and_adjust(%p, %d)\n", rmsg, adjust)); + ASSERT_RBUFPOOL_OWNER (rmsg->chunk.rbuf->rbufpool); + assert (adjust >= 0); + assert ((uint32_t) adjust < RMSG_REFCOUNT_RDATA_BIAS); + sub = RMSG_REFCOUNT_RDATA_BIAS - (uint32_t) adjust; + assert (os_atomic_ld32 (&rmsg->refcount) >= sub); + if (os_atomic_sub32_nv (&rmsg->refcount, sub) == 0) + nn_rmsg_free (rmsg); +} + +static void nn_rmsg_rmbias_anythread (struct nn_rmsg *rmsg) +{ + /* For removing garbage when freeing a nn_defrag. */ + uint32_t sub = RMSG_REFCOUNT_RDATA_BIAS; + TRACE_RADMIN (("rmsg_rmbias_anythread(%p)\n", rmsg)); + assert (os_atomic_ld32 (&rmsg->refcount) >= sub); + if (os_atomic_sub32_nv (&rmsg->refcount, sub) == 0) + nn_rmsg_free (rmsg); +} +static void nn_rmsg_unref (struct nn_rmsg *rmsg) +{ + TRACE_RADMIN (("rmsg_unref(%p)\n", rmsg)); + assert (os_atomic_ld32 (&rmsg->refcount) > 0); + if (os_atomic_dec32_ov (&rmsg->refcount) == 1) + nn_rmsg_free (rmsg); +} + +void *nn_rmsg_alloc (struct nn_rmsg *rmsg, uint32_t size) +{ + struct nn_rmsg_chunk *chunk = rmsg->lastchunk; + struct nn_rbuf *rbuf = chunk->rbuf; + uint32_t size8 = align8uint32 (size); + void *ptr; + TRACE_RADMIN (("rmsg_alloc(%p, %u => %u)\n", rmsg, size, size8)); + ASSERT_RBUFPOOL_OWNER (rbuf->rbufpool); + ASSERT_RMSG_UNCOMMITTED (rmsg); + assert ((chunk->size % 8) == 0); + assert (size8 <= rbuf->max_rmsg_size); + + if (chunk->size + size8 > rbuf->max_rmsg_size) + { + struct nn_rbufpool *rbufpool = rbuf->rbufpool; + struct nn_rmsg_chunk *newchunk; + TRACE_RADMIN (("rmsg_alloc(%p, %u) limit hit - new chunk\n", rmsg, size)); + commit_rmsg_chunk (chunk); + newchunk = nn_rbuf_alloc (rbufpool); + if (newchunk == NULL) + { + NN_WARNING ("nn_rmsg_alloc: can't allocate more memory (%u bytes) ... giving up\n", size); + return NULL; + } + init_rmsg_chunk (newchunk, rbufpool->current); + rmsg->lastchunk = chunk->next = newchunk; + chunk = newchunk; + } + + ptr = chunk->u.payload + chunk->size; + chunk->size += size8; + TRACE_RADMIN (("rmsg_alloc(%p, %u) = %p\n", rmsg, size, ptr)); +#if USE_VALGRIND + if (chunk == &rmsg->chunk) { + VALGRIND_MEMPOOL_CHANGE (rbuf->rbufpool, rmsg, rmsg, offsetof (struct nn_rmsg, chunk.u.payload) + chunk->size); + } else { + VALGRIND_MEMPOOL_CHANGE (rbuf->rbufpool, chunk, chunk, offsetof (struct nn_rmsg_chunk, u.payload) + chunk->size); + } +#endif + return ptr; +} + +/* RDATA --------------------------------------- */ + +struct nn_rdata *nn_rdata_new (struct nn_rmsg *rmsg, uint32_t start, uint32_t endp1, uint32_t submsg_offset, uint32_t payload_offset) +{ + struct nn_rdata *d; + if ((d = nn_rmsg_alloc (rmsg, sizeof (*d))) == NULL) + return NULL; + d->rmsg = rmsg; + d->nextfrag = NULL; + d->min = start; + d->maxp1 = endp1; + d->submsg_zoff = (uint16_t) NN_OFF_TO_ZOFF (submsg_offset); + d->payload_zoff = (uint16_t) NN_OFF_TO_ZOFF (payload_offset); +#ifndef NDEBUG + os_atomic_st32 (&d->refcount_bias_added, 0); +#endif + TRACE_RADMIN (("rdata_new(%p, bytes [%u,%u), submsg @ %u, payload @ %u) = %p\n", rmsg, start, endp1, NN_RDATA_SUBMSG_OFF (d), NN_RDATA_PAYLOAD_OFF (d), d)); + return d; +} + +static void nn_rdata_addbias (struct nn_rdata *rdata) +{ + TRACE_RADMIN (("rdata_addbias(%p)\n", rdata)); +#ifndef NDEBUG + ASSERT_RBUFPOOL_OWNER (rdata->rmsg->chunk.rbuf->rbufpool); + if (os_atomic_inc32_nv (&rdata->refcount_bias_added) != 1) + abort (); +#endif + nn_rmsg_addbias (rdata->rmsg); +} + +static void nn_rdata_rmbias_and_adjust (struct nn_rdata *rdata, int adjust) +{ + TRACE_RADMIN (("rdata_rmbias_and_adjust(%p, %d)\n", rdata, adjust)); +#ifndef NDEBUG + if (os_atomic_dec32_ov (&rdata->refcount_bias_added) != 1) + abort (); +#endif + nn_rmsg_rmbias_and_adjust (rdata->rmsg, adjust); +} + +static void nn_rdata_rmbias_anythread (struct nn_rdata *rdata) +{ + TRACE_RADMIN (("rdata_rmbias_anytrhead(%p, %d)\n", rdata)); +#ifndef NDEBUG + if (os_atomic_dec32_ov (&rdata->refcount_bias_added) != 1) + abort (); +#endif + nn_rmsg_rmbias_anythread (rdata->rmsg); +} + +static void nn_rdata_unref (struct nn_rdata *rdata) +{ + TRACE_RADMIN (("rdata_rdata_unref(%p)\n", rdata)); + nn_rmsg_unref (rdata->rmsg); +} + +/* DEFRAG -------------------------------------------------------------- + + Defragmentation happens separately from reordering, the reason + being that defragmentation really is best done only once, and + besides it simplifies reordering because it only ever has to deal + with whole messages. + + The defragmeter accepts both rdatas that are fragments of samples + and rdatas that are complete samples. The unfragmented ones are + returned immediately for further processing, in the format also + used for fragmented samples. Any rdata stored in the defrag index + as well as unfragmented ones returned immediately are accounted for + in rmsg::refcount. + + Defragmenting one sample is done using an interval tree where the + minima and maxima are given by byte indexes of the received + framgents. Consecutive frags get chained in one interval, to keep + the tree small even in the worst case. + + These intervals are represented using defrag_iv, and the fragment + chain for an interval is built using the nextfrag links in the + rdata. + + The defragmenter can defragment multiple samples in parallel (even + though a writer normally produces a single fragment chain only, + things may be different when packets get lost and/or + (transient-local) data is resent). + + Each sample is represented using an rsample. Each contains the + root of an interval tree of fragments with a cached pointer to the + last known interval (because we expect the data to arrive in-order + and like to avoid searching). The rsamples are stored in a tree + indexed on sequence number, which itself caches the last sample it + is currently defragmenting, again to avoid searching. + + The memory for an rsample is later re-used by the reordering + mechanism. Hence the union. For that use, see REORDER. + + Partial and complete overlap of fragments is acceptable, but may + result in a fragment chain containing fragments that do not add any + bytes of information. Those should be skipped by the deserializer. + If the sender decides to suddenly change the fragmentation for a + message, we happily keep processing them, even though there is no + good reason for the sender to do so and the likelihood of such + messy fragment chains increases significantly. + + Once done defragmenting, the tree consists of a root node only, + which points to a list of fragments, in-order (but for the caveat + above). + + Memory used for the storage of interval nodes while defragmenting + is afterward re-used for chaining samples. An unfragmented message + will have a new sample chain allocated for this purpose, a + fragmented message will have at least one interval allocated to it + and thus have sufficient space for the chain node. + + FIXME: These AVL trees are overkill. Either switch to parent-less + red-black trees (they have better performance anyway and only need + a single bit of state) or to splay trees (must have a parent + because they can degenerate to linear structures, unless the number + of intervals in the tree is limited, which probably is a good idea + anyway). */ + +struct nn_defrag_iv { + ut_avlNode_t avlnode; /* for nn_rsample.defrag::fragtree */ + uint32_t min, maxp1; + struct nn_rdata *first; + struct nn_rdata *last; +}; + +struct nn_rsample { + union { + struct nn_rsample_defrag { + ut_avlNode_t avlnode; /* for nn_defrag::sampletree */ + ut_avlTree_t fragtree; + struct nn_defrag_iv *lastfrag; + struct nn_rsample_info *sampleinfo; + seqno_t seq; + } defrag; + struct nn_rsample_reorder { + ut_avlNode_t avlnode; /* for nn_reorder::sampleivtree, if head of a chain */ + struct nn_rsample_chain sc; /* this interval's samples, covering ... */ + seqno_t min, maxp1; /* ... seq nos: [min,maxp1), but possibly with holes in it */ + uint32_t n_samples; /* so this is the actual length of the chain */ + } reorder; + } u; +}; + +struct nn_defrag { + ut_avlTree_t sampletree; + struct nn_rsample *max_sample; /* = max(sampletree) */ + uint32_t n_samples; + uint32_t max_samples; + enum nn_defrag_drop_mode drop_mode; +}; + +static int compare_uint32 (const void *va, const void *vb); +static int compare_seqno (const void *va, const void *vb); + +static const ut_avlTreedef_t defrag_sampletree_treedef = UT_AVL_TREEDEF_INITIALIZER (offsetof (struct nn_rsample, u.defrag.avlnode), offsetof (struct nn_rsample, u.defrag.seq), compare_seqno, 0); +static const ut_avlTreedef_t rsample_defrag_fragtree_treedef = UT_AVL_TREEDEF_INITIALIZER (offsetof (struct nn_defrag_iv, avlnode), offsetof (struct nn_defrag_iv, min), compare_uint32, 0); + +static int compare_uint32 (const void *va, const void *vb) +{ + uint32_t a = *((const uint32_t *) va); + uint32_t b = *((const uint32_t *) vb); + return (a == b) ? 0 : (a < b) ? -1 : 1; +} + +static int compare_seqno (const void *va, const void *vb) +{ + seqno_t a = *((const seqno_t *) va); + seqno_t b = *((const seqno_t *) vb); + return (a == b) ? 0 : (a < b) ? -1 : 1; +} + +struct nn_defrag *nn_defrag_new (enum nn_defrag_drop_mode drop_mode, uint32_t max_samples) +{ + struct nn_defrag *d; + assert (max_samples >= 1); + if ((d = os_malloc (sizeof (*d))) == NULL) + return NULL; + ut_avlInit (&defrag_sampletree_treedef, &d->sampletree); + d->drop_mode = drop_mode; + d->max_samples = max_samples; + d->n_samples = 0; + d->max_sample = NULL; + return d; +} + +void nn_fragchain_adjust_refcount (struct nn_rdata *frag, int adjust) +{ + struct nn_rdata *frag1; + TRACE_RADMIN (("fragchain_adjust_refcount(%p, %d)\n", frag, adjust)); + while (frag) + { + frag1 = frag->nextfrag; + nn_rdata_rmbias_and_adjust (frag, adjust); + frag = frag1; + } +} + +static void nn_fragchain_rmbias_anythread (struct nn_rdata *frag, UNUSED_ARG (int adjust)) +{ + struct nn_rdata *frag1; + TRACE_RADMIN (("fragchain_rmbias_anythread(%p)\n", frag)); + while (frag) + { + frag1 = frag->nextfrag; + nn_rdata_rmbias_anythread (frag); + frag = frag1; + } +} + +static void defrag_rsample_drop (struct nn_defrag *defrag, struct nn_rsample *rsample, void (*fragchain_free) (struct nn_rdata *frag, int adjust)) +{ + /* Can't reference rsample after the first fragchain_free, because + we don't know which rdata/rmsg provides the storage for the + rsample and therefore can't increment the reference count. + + So we need to walk the fragments while guaranteeing strict + "forward progress" in the memory accesses, which this particular + inorder treewalk does provide. */ + ut_avlIter_t iter; + struct nn_defrag_iv *iv; + TRACE_RADMIN ((" defrag_rsample_drop (%p, %p)\n", (void *) defrag, (void *) rsample)); + ut_avlDelete (&defrag_sampletree_treedef, &defrag->sampletree, rsample); + assert (defrag->n_samples > 0); + defrag->n_samples--; + for (iv = ut_avlIterFirst (&rsample_defrag_fragtree_treedef, &rsample->u.defrag.fragtree, &iter); iv; iv = ut_avlIterNext (&iter)) + fragchain_free (iv->first, 0); +} + +void nn_defrag_free (struct nn_defrag *defrag) +{ + struct nn_rsample *s; + s = ut_avlFindMin (&defrag_sampletree_treedef, &defrag->sampletree); + while (s) + { + TRACE_RADMIN (("defrag_free(%p, sample %p seq %lld)\n", defrag, s, s->u.defrag.seq)); + defrag_rsample_drop (defrag, s, nn_fragchain_rmbias_anythread); + s = ut_avlFindMin (&defrag_sampletree_treedef, &defrag->sampletree); + } + assert (defrag->n_samples == 0); + os_free (defrag); +} + +static int defrag_try_merge_with_succ (struct nn_rsample_defrag *sample, struct nn_defrag_iv *node) +{ + struct nn_defrag_iv *succ; + + TRACE_RADMIN ((" defrag_try_merge_with_succ(%p [%u..%u)):\n", + (void *) node, node->min, node->maxp1)); + if (node == sample->lastfrag) + { + /* there is no interval following node */ + TRACE_RADMIN ((" node is lastfrag\n")); + return 0; + } + + succ = ut_avlFindSucc (&rsample_defrag_fragtree_treedef, &sample->fragtree, node); + assert (succ != NULL); + TRACE_RADMIN ((" succ is %p [%u..%u)\n", (void *) succ, succ->min, succ->maxp1)); + if (succ->min > node->maxp1) + { + TRACE_RADMIN ((" gap between node and succ\n")); + return 0; + } + else + { + uint32_t succ_maxp1 = succ->maxp1; + + /* no longer a gap between node & succ => succ will be removed + from the interval tree and therefore node will become the + last interval if succ currently is */ + ut_avlDelete (&rsample_defrag_fragtree_treedef, &sample->fragtree, succ); + if (sample->lastfrag == succ) + { + TRACE_RADMIN ((" succ is lastfrag\n")); + sample->lastfrag = node; + } + + /* If succ's chain contains data beyond the frag we just + received, append it to node (but do note that this doesn't + guarantee that each fragment in the chain adds data!) and + throw succ away. + + Do the same if succ's frag chain is completely contained in + node, even though it wastes memory & cpu time (the latter, + eventually): because the rsample we use may be dependent on the + references to rmsgs of the rdata in succ, freeing it may cause + the rsample to be freed as well. */ + if (node->maxp1 < succ_maxp1) + TRACE_RADMIN ((" succ adds data to node\n")); + else + TRACE_RADMIN ((" succ is contained in node\n")); + + node->last->nextfrag = succ->first; + node->last = succ->last; + node->maxp1 = succ_maxp1; + + /* if the new fragment contains data beyond succ it may even + allow merging with succ-succ */ + return node->maxp1 > succ_maxp1; + } +} + +static void defrag_rsample_addiv (struct nn_rsample_defrag *sample, struct nn_rdata *rdata, ut_avlIPath_t *path) +{ + struct nn_defrag_iv *newiv; + if ((newiv = nn_rmsg_alloc (rdata->rmsg, sizeof (*newiv))) == NULL) + return; + rdata->nextfrag = NULL; + newiv->first = newiv->last = rdata; + newiv->min = rdata->min; + newiv->maxp1 = rdata->maxp1; + nn_rdata_addbias (rdata); + ut_avlInsertIPath (&rsample_defrag_fragtree_treedef, &sample->fragtree, newiv, path); + if (sample->lastfrag == NULL || rdata->min > sample->lastfrag->min) + sample->lastfrag = newiv; +} + +static void rsample_init_common (UNUSED_ARG (struct nn_rsample *rsample), UNUSED_ARG (struct nn_rdata *rdata), UNUSED_ARG (const struct nn_rsample_info *sampleinfo)) +{ +} + +static struct nn_rsample *defrag_rsample_new (struct nn_rdata *rdata, const struct nn_rsample_info *sampleinfo) +{ + struct nn_rsample *rsample; + struct nn_rsample_defrag *dfsample; + ut_avlIPath_t ivpath; + + if ((rsample = nn_rmsg_alloc (rdata->rmsg, sizeof (*rsample))) == NULL) + return NULL; + rsample_init_common (rsample, rdata, sampleinfo); + dfsample = &rsample->u.defrag; + dfsample->lastfrag = NULL; + dfsample->seq = sampleinfo->seq; + if ((dfsample->sampleinfo = nn_rmsg_alloc (rdata->rmsg, sizeof (*dfsample->sampleinfo))) == NULL) + return NULL; + *dfsample->sampleinfo = *sampleinfo; + + ut_avlInit (&rsample_defrag_fragtree_treedef, &dfsample->fragtree); + + /* add sentinel if rdata is not the first fragment of the message */ + if (rdata->min > 0) + { + struct nn_defrag_iv *sentinel; + if ((sentinel = nn_rmsg_alloc (rdata->rmsg, sizeof (*sentinel))) == NULL) + return NULL; + sentinel->first = sentinel->last = NULL; + sentinel->min = sentinel->maxp1 = 0; + ut_avlLookupIPath (&rsample_defrag_fragtree_treedef, &dfsample->fragtree, &sentinel->min, &ivpath); + ut_avlInsertIPath (&rsample_defrag_fragtree_treedef, &dfsample->fragtree, sentinel, &ivpath); + } + + /* add an interval for the first received fragment */ + ut_avlLookupIPath (&rsample_defrag_fragtree_treedef, &dfsample->fragtree, &rdata->min, &ivpath); + defrag_rsample_addiv (dfsample, rdata, &ivpath); + return rsample; +} + +static struct nn_rsample *reorder_rsample_new (struct nn_rdata *rdata, const struct nn_rsample_info *sampleinfo) +{ + /* Implements: + + defrag_rsample_new ; rsample_convert_defrag_to_reorder + + It is simple enough to warrant having an extra function. Note the + discrepancy between defrag_rsample_new which fully initializes + the rsample, including the AVL node headers, and this function, + which doesn't do so. */ + struct nn_rsample *rsample; + struct nn_rsample_reorder *s; + struct nn_rsample_chain_elem *sce; + + if ((rsample = nn_rmsg_alloc (rdata->rmsg, sizeof (*rsample))) == NULL) + return NULL; + rsample_init_common (rsample, rdata, sampleinfo); + + if ((sce = nn_rmsg_alloc (rdata->rmsg, sizeof (*sce))) == NULL) + return NULL; + sce->fragchain = rdata; + sce->next = NULL; + if ((sce->sampleinfo = nn_rmsg_alloc (rdata->rmsg, sizeof (*sce->sampleinfo))) == NULL) + return NULL; + *sce->sampleinfo = *sampleinfo; + rdata->nextfrag = NULL; + nn_rdata_addbias (rdata); + + s = &rsample->u.reorder; + s->min = sampleinfo->seq; + s->maxp1 = sampleinfo->seq + 1; + s->n_samples = 1; + s->sc.first = s->sc.last = sce; + return rsample; +} + +static int is_complete (const struct nn_rsample_defrag *sample) +{ + /* Returns: NULL if 'sample' is incomplete, else 'sample'. Complete: + one interval covering all bytes. One interval because of the + greedy coalescing in add_fragment(). There is at least one + interval if we get here. */ + const struct nn_defrag_iv *iv = ut_avlRoot (&rsample_defrag_fragtree_treedef, &sample->fragtree); + assert (iv != NULL); + if (iv->min == 0 && iv->maxp1 >= sample->sampleinfo->size) + { + /* Accept fragments containing data beyond the end of the sample, + only to filter them out (or not, as the case may be) at a later + stage. Dropping them before the defragmeter leaves us with + samples that will never be completed; dropping them in the + defragmenter would be feasible by discarding all fragments of + that sample collected so far. */ + assert (ut_avlIsSingleton (&sample->fragtree)); + return 1; + } + else + { + return 0; + } +} + +static void rsample_convert_defrag_to_reorder (struct nn_rsample *sample) +{ + /* Converts an rsample as stored in defrag to one as stored in a + reorder admin. Have to be careful with the ordering, or at least + somewhat, and the easy way out uses a few local variables -- any + self-respecting compiler will optimise them away, and any + self-respecting CPU would need to copy them via registers anyway + because it uses a load-store architecture. */ + struct nn_defrag_iv *iv = ut_avlRootNonEmpty (&rsample_defrag_fragtree_treedef, &sample->u.defrag.fragtree); + struct nn_rdata *fragchain = iv->first; + struct nn_rsample_info *sampleinfo = sample->u.defrag.sampleinfo; + struct nn_rsample_chain_elem *sce; + seqno_t seq = sample->u.defrag.seq; + + /* re-use memory fragment interval node for sample chain */ + sce = (struct nn_rsample_chain_elem *) ut_avlRootNonEmpty (&rsample_defrag_fragtree_treedef, &sample->u.defrag.fragtree); + sce->fragchain = fragchain; + sce->next = NULL; + sce->sampleinfo = sampleinfo; + + sample->u.reorder.sc.first = sample->u.reorder.sc.last = sce; + sample->u.reorder.min = seq; + sample->u.reorder.maxp1 = seq + 1; + sample->u.reorder.n_samples = 1; +} + +static struct nn_rsample *defrag_add_fragment (struct nn_rsample *sample, struct nn_rdata *rdata, const struct nn_rsample_info *sampleinfo) +{ + struct nn_rsample_defrag *dfsample = &sample->u.defrag; + struct nn_defrag_iv *predeq, *succ; + const uint32_t min = rdata->min; + const uint32_t maxp1 = rdata->maxp1; + + /* min, max are byte offsets; contents has max-min+1 bytes; it all + concerns the message pointer to by sample */ + assert (min < maxp1); + /* and it must concern this message */ + assert (dfsample->seq == sampleinfo->seq); + /* there must be a last fragment */ + assert (dfsample->lastfrag); + /* relatively expensive test: lastfrag, tree must be consistent */ + assert (dfsample->lastfrag == ut_avlFindMax (&rsample_defrag_fragtree_treedef, &dfsample->fragtree)); + + TRACE_RADMIN ((" lastfrag %p [%u..%u)\n", + (void *) dfsample->lastfrag, + dfsample->lastfrag->min, dfsample->lastfrag->maxp1)); + + /* Interval tree is sorted on min offset; each key is unique: + otherwise one would be wholly contained in another. */ + if (min >= dfsample->lastfrag->min) + { + /* Assumed normal case: fragment appends data */ + predeq = dfsample->lastfrag; + TRACE_RADMIN ((" fast path: predeq = lastfrag\n")); + } + else + { + /* Slow path: find preceding fragment by tree search */ + predeq = ut_avlLookupPredEq (&rsample_defrag_fragtree_treedef, &dfsample->fragtree, &min); + assert (predeq); + TRACE_RADMIN ((" slow path: predeq = lookup %u => %p [%u..%u)\n", + min, (void *) predeq, predeq->min, predeq->maxp1)); + } + + /* we have a sentinel interval of [0,0) until we receive a packet + that contains the first byte of the message, that is, there + should always be predeq */ + assert (predeq != NULL); + + if (predeq->maxp1 >= maxp1) + { + /* new is contained in predeq, discard new; rdata did not cause + completion of a sample */ + TRACE_RADMIN ((" new contained in predeq\n")); + return NULL; + } + else if (min <= predeq->maxp1) + { + /* new extends predeq, add it to the chain (necessarily at the + end); this may close the gap to the successor of predeq; predeq + need not have a fragment chain yet (it may be the sentinel) */ + TRACE_RADMIN ((" grow predeq with new\n")); + nn_rdata_addbias (rdata); + rdata->nextfrag = NULL; + if (predeq->first) + predeq->last->nextfrag = rdata; + else + { + /* 'Tis the sentinel => rewrite the sample info so we + eventually always use the sample info contributed by the + first fragment */ + predeq->first = rdata; + *dfsample->sampleinfo = *sampleinfo; + } + predeq->last = rdata; + predeq->maxp1 = maxp1; + /* it may now be possible to merge with the successor */ + while (defrag_try_merge_with_succ (dfsample, predeq)) + ; + return is_complete (dfsample) ? sample : NULL; + } + else if (predeq != dfsample->lastfrag && /* if predeq is last frag, there is no succ */ + (succ = ut_avlFindSucc (&rsample_defrag_fragtree_treedef, &dfsample->fragtree, predeq)) != NULL && + succ->min <= maxp1) + { + /* extends succ (at the low end; no guarantee each individual + fragment in the chain adds value); but doesn't overlap with + predeq so the tree structure doesn't change even though the key + does change */ + TRACE_RADMIN ((" extending succ %p [%u..%u) at head\n", + (void *) succ, succ->min, succ->maxp1)); + nn_rdata_addbias (rdata); + rdata->nextfrag = succ->first; + succ->first = rdata; + succ->min = min; + /* new one may cover all of succ & more, in which case we must + update the max of succ & see if we can merge it with + succ-succ */ + if (maxp1 > succ->maxp1) + { + TRACE_RADMIN ((" extending succ at end as well\n")); + succ->maxp1 = maxp1; + while (defrag_try_merge_with_succ (dfsample, succ)) + ; + } + assert (!is_complete (dfsample)); + return NULL; + } + else + { + /* doesn't extend either predeq at the end or succ at the head => + new interval; rdata did not cause completion of sample */ + ut_avlIPath_t path; + TRACE_RADMIN ((" new interval\n")); + if (ut_avlLookupIPath (&rsample_defrag_fragtree_treedef, &dfsample->fragtree, &min, &path)) + assert (0); + defrag_rsample_addiv (dfsample, rdata, &path); + return NULL; + } +} + +static int nn_rdata_is_fragment (const struct nn_rdata *rdata, const struct nn_rsample_info *sampleinfo) +{ + /* sanity check: min, maxp1 must be within bounds */ + assert (rdata->min <= rdata->maxp1); + assert (rdata->maxp1 <= sampleinfo->size); + return !(rdata->min == 0 && rdata->maxp1 == sampleinfo->size); +} + +static int defrag_limit_samples (struct nn_defrag *defrag, seqno_t seq, seqno_t *max_seq) +{ + struct nn_rsample *sample_to_drop = NULL; + if (defrag->n_samples < defrag->max_samples) + return 1; + /* max_samples >= 1 => some sample present => max_sample != NULL */ + assert (defrag->max_sample != NULL); + TRACE_RADMIN ((" max samples reached\n")); + switch (defrag->drop_mode) + { + case NN_DEFRAG_DROP_LATEST: + TRACE_RADMIN ((" drop mode = DROP_LATEST\n")); + if (seq > defrag->max_sample->u.defrag.seq) + { + TRACE_RADMIN ((" new sample is new latest => discarding it\n")); + return 0; + } + sample_to_drop = defrag->max_sample; + break; + case NN_DEFRAG_DROP_OLDEST: + TRACE_RADMIN ((" drop mode = DROP_OLDEST\n")); + sample_to_drop = ut_avlFindMin (&defrag_sampletree_treedef, &defrag->sampletree); + assert (sample_to_drop); + if (seq < sample_to_drop->u.defrag.seq) + { + TRACE_RADMIN ((" new sample is new oldest => discarding it\n")); + return 0; + } + break; + } + assert (sample_to_drop != NULL); + defrag_rsample_drop (defrag, sample_to_drop, nn_fragchain_adjust_refcount); + if (sample_to_drop == defrag->max_sample) + { + defrag->max_sample = ut_avlFindMax (&defrag_sampletree_treedef, &defrag->sampletree); + *max_seq = defrag->max_sample ? defrag->max_sample->u.defrag.seq : 0; + TRACE_RADMIN ((" updating max_sample: now %p %lld\n", + (void *) defrag->max_sample, + defrag->max_sample ? defrag->max_sample->u.defrag.seq : 0)); + } + return 1; +} + +struct nn_rsample *nn_defrag_rsample (struct nn_defrag *defrag, struct nn_rdata *rdata, const struct nn_rsample_info *sampleinfo) +{ + /* Takes an rdata, records it in defrag if needed and returns an + rdata chain representing a complete message ready for further + processing if 'rdata' is complete or caused a message to become + complete. + + On return 'rdata' is either: (a) stored in defrag and the rmsg + refcount is biased; (b) refcount is biased and sample returned + immediately because it wasn't actually a fragment; or (c) no + effect on refcount & and not stored because it did not add any + information. + + on entry: + + - rdata not refcounted, chaining fields need not be initialized. + + - sampleinfo fully initialised if first frag, else just seq, + fragsize and size; will be copied onto memory allocated from + the receive buffer + + return: all rdatas referenced in the chain returned by this + function have been accounted for in the refcount of their rmsgs + by adding BIAS to the refcount. */ + struct nn_rsample *sample, *result; + seqno_t max_seq; + ut_avlIPath_t path; + + assert (defrag->n_samples <= defrag->max_samples); + + /* not a fragment => always complete, so refcount rdata, turn into a + valid chain behind a valid msginfo and return it. */ + if (!nn_rdata_is_fragment (rdata, sampleinfo)) + return reorder_rsample_new (rdata, sampleinfo); + + /* max_seq is used for the fast path, and is 0 when there is no + last message in 'defrag'. max_seq and max_sample must be + consistent. Max_sample must be consistent with tree */ + assert (defrag->max_sample == ut_avlFindMax (&defrag_sampletree_treedef, &defrag->sampletree)); + max_seq = defrag->max_sample ? defrag->max_sample->u.defrag.seq : 0; + TRACE_RADMIN (("defrag_rsample(%p, %p [%u..%u) msg %p, %p seq %lld size %u) max_seq %p %lld:\n", + (void *) defrag, (void *) rdata, rdata->min, rdata->maxp1, rdata->rmsg, + (void *) sampleinfo, sampleinfo->seq, sampleinfo->size, + (void *) defrag->max_sample, max_seq)); + /* fast path: rdata is part of message with the highest sequence + number we're currently defragmenting, or is beyond that */ + if (sampleinfo->seq == max_seq) + { + TRACE_RADMIN ((" add fragment to max_sample\n")); + result = defrag_add_fragment (defrag->max_sample, rdata, sampleinfo); + } + else if (!defrag_limit_samples (defrag, sampleinfo->seq, &max_seq)) + { + TRACE_RADMIN ((" discarding sample\n")); + result = NULL; + } + else if (sampleinfo->seq > max_seq) + { + /* a node with a key greater than the maximum always is the right + child of the old maximum node */ + /* FIXME: MERGE THIS ONE WITH THE NEXT */ + TRACE_RADMIN ((" new max sample\n")); + ut_avlLookupIPath (&defrag_sampletree_treedef, &defrag->sampletree, &sampleinfo->seq, &path); + if ((sample = defrag_rsample_new (rdata, sampleinfo)) == NULL) + return NULL; + ut_avlInsertIPath (&defrag_sampletree_treedef, &defrag->sampletree, sample, &path); + defrag->max_sample = sample; + defrag->n_samples++; + result = NULL; + } + else if ((sample = ut_avlLookupIPath (&defrag_sampletree_treedef, &defrag->sampletree, &sampleinfo->seq, &path)) == NULL) + { + /* a new sequence number, but smaller than the maximum */ + TRACE_RADMIN ((" new sample less than max\n")); + assert (sampleinfo->seq < max_seq); + if ((sample = defrag_rsample_new (rdata, sampleinfo)) == NULL) + return NULL; + ut_avlInsertIPath (&defrag_sampletree_treedef, &defrag->sampletree, sample, &path); + defrag->n_samples++; + result = NULL; + } + else + { + /* adds (or, as the case may be, doesn't add) to a known message */ + TRACE_RADMIN ((" add fragment to %p\n", (void *) sample)); + result = defrag_add_fragment (sample, rdata, sampleinfo); + } + + if (result != NULL) + { + /* Once completed, remove from defrag sample tree and convert to + reorder format. If it is the sample with the maximum sequence in + the tree, an update of max_sample is required. */ + TRACE_RADMIN ((" complete\n")); + ut_avlDelete (&defrag_sampletree_treedef, &defrag->sampletree, result); + assert (defrag->n_samples > 0); + defrag->n_samples--; + if (result == defrag->max_sample) + { + defrag->max_sample = ut_avlFindMax (&defrag_sampletree_treedef, &defrag->sampletree); + TRACE_RADMIN ((" updating max_sample: now %p %lld\n", + (void *) defrag->max_sample, + defrag->max_sample ? defrag->max_sample->u.defrag.seq : 0)); + } + rsample_convert_defrag_to_reorder (result); + } + + assert (defrag->max_sample == ut_avlFindMax (&defrag_sampletree_treedef, &defrag->sampletree)); + return result; +} + +void nn_defrag_notegap (struct nn_defrag *defrag, seqno_t min, seqno_t maxp1) +{ + /* All sequence numbers in [min,maxp1) are unavailable so any + fragments in that range must be discarded. Used both for + Hearbeats (by setting min=1) and for Gaps. */ + struct nn_rsample *s = ut_avlLookupSuccEq (&defrag_sampletree_treedef, &defrag->sampletree, &min); + while (s && s->u.defrag.seq < maxp1) + { + struct nn_rsample *s1 = ut_avlFindSucc (&defrag_sampletree_treedef, &defrag->sampletree, s); + defrag_rsample_drop (defrag, s, nn_fragchain_adjust_refcount); + s = s1; + } + defrag->max_sample = ut_avlFindMax (&defrag_sampletree_treedef, &defrag->sampletree); +} + +int nn_defrag_nackmap (struct nn_defrag *defrag, seqno_t seq, uint32_t maxfragnum, struct nn_fragment_number_set *map, uint32_t maxsz) +{ + struct nn_rsample *s; + struct nn_defrag_iv *iv; + uint32_t i, fragsz, nfrags; + assert (maxsz <= 256); + s = ut_avlLookup (&defrag_sampletree_treedef, &defrag->sampletree, &seq); + if (s == NULL) + { + if (maxfragnum == UINT32_MAX) + { + /* If neither the caller nor the defragmenter knows anything about the sample, say so */ + return -1; + } + else + { + /* If caller says fragments [0..maxfragnum] should be there, but + we do not have a record of it, we can still generate a proper + nackmap */ + if (maxfragnum + 1 > maxsz) + map->numbits = maxsz; + else + map->numbits = maxfragnum + 1; + map->bitmap_base = 0; + nn_bitset_one (map->numbits, map->bits); + return (int) map->numbits; + } + } + + /* Limit maxfragnum to actual sample size, so that the caller can + get accurate info without knowing maxfragnum. MAXFRAGNUM is + 0-based, so at most nfrags-1. */ + fragsz = s->u.defrag.sampleinfo->fragsize; + nfrags = (s->u.defrag.sampleinfo->size + fragsz - 1) / fragsz; + if (maxfragnum >= nfrags) + maxfragnum = nfrags - 1; + + /* Determine bitmap start & size */ + { + /* We always have an interval starting at 0, which is empty if we + are missing the first fragment. */ + struct nn_defrag_iv *liv = s->u.defrag.lastfrag; + nn_fragment_number_t map_end; + iv = ut_avlFindMin (&rsample_defrag_fragtree_treedef, &s->u.defrag.fragtree); + assert (iv != NULL); + /* iv is first interval, iv->maxp1 is first byte beyond that => + divide by fragsz to get first missing fragment */ + map->bitmap_base = iv->maxp1 / fragsz; + /* if last interval ends before the last published fragment and it + isn't because the last fragment is shorter, bitmap runs to + maxfragnum; else it can end where the last interval starts, + i.e., (liv->min - 1) is the last byte missing of all that has + been published so far */ + if (liv->maxp1 < (maxfragnum + 1) * fragsz && liv->maxp1 < s->u.defrag.sampleinfo->size) + map_end = maxfragnum; + else if (liv->min > 0) + map_end = (liv->min - 1) / fragsz; + else + map_end = 0; + /* if all data is available, iv == liv and map_end < + map->bitmap_base, but there is nothing to request in that + case. */ + map->numbits = (map_end < map->bitmap_base) ? 0 : map_end - map->bitmap_base + 1; + iv = ut_avlFindSucc (&rsample_defrag_fragtree_treedef, &s->u.defrag.fragtree, iv); + } + + /* Clear bitmap, then set bits for gaps in available fragments */ + if (map->numbits > maxsz) + map->numbits = maxsz; + nn_bitset_zero (map->numbits, map->bits); + i = map->bitmap_base; + while (iv && i < map->bitmap_base + map->numbits) + { + /* iv->min is the next available byte, therefore the first + fragment we don't need to request a retransmission of */ + uint32_t bound = iv->min / fragsz; + if ((iv->min % fragsz) != 0) + { + /* this is actually disallowed by the spec ... it can only occur + when fragments are not always the same size for a single + sample; but if & when it happens, simply request a fragment + extra to cover everything up to iv->min. */ + ++bound; + } + for (; i < map->bitmap_base + map->numbits && i < bound; i++) + { + unsigned x = (unsigned) (i - map->bitmap_base); + nn_bitset_set (map->numbits, map->bits, x); + } + /* next sequence of fragments to request retranmsission of starts + at fragment containing maxp1 (because we don't have that byte + yet), and runs until the next interval begins */ + i = iv->maxp1 / fragsz; + iv = ut_avlFindSucc (&rsample_defrag_fragtree_treedef, &s->u.defrag.fragtree, iv); + } + /* and set bits for missing fragments beyond the highest interval */ + for (; i < map->bitmap_base + map->numbits; i++) + { + unsigned x = (unsigned) (i - map->bitmap_base); + nn_bitset_set (map->numbits, map->bits, x); + } + return (int) map->numbits; +} + +/* REORDER ------------------------------------------------------------- + + The reorder index tracks out-of-order messages as non-overlapping, + non-consecutive intervals of sequence numbers, with each interval + pointing to a chain of rsamples (rsample_chain{,_elem}). The + maximum number of samples stored by the radmin is max_samples + (setting it to 2**32-1 effectively makes it unlimited, by you're + then you're probably into TB territority as you need at least an + rmsg, rdata, sampleinfo, rsample, and a rsample_chain_elem, which + adds up to quite a few bytes). + + The policy is to prefer the lowest sequence numbers, as those need + to be delivered before the higher ones can be, and also because one + radmin tracks only a single sequence. Historical data uses a + per-reader radmin. + + Each reliable proxy writer has a reorder admin for reordering + messages, the "primary" reorder admin. For the primary one, it is + possible to store indexing data in memory originally allocated + memory for defragmenting, as the defragmenter is done with it and + this admin is the only one indexing the sample. + + Each out-of-sync proxy-writer--reader match also has an reorder + instance, a "secondary" reorder admin, but those can't re-use + memory like the proxy-writer's can, because there can be any number + of them. Before inserting in one of these, the sample must first + be replicated using reorder_rsample_dup(), which fortunately is an + extremely cheap operation. + + A sample either goes to the primary one (which may store it, reject + it, or return it and subsequent samples immediately) [CASE I], or + it goes to any number of secondary ones [CASE II]. + + The reorder_rsample function may require updates to the reference + counts of the rmsgs referenced by the rdatas in the sample it was + called with (and _only_ to those of that particular sample, as + others underwent all this processing before). The + "refcount_adjust" in/out parameter is updated to reflect the + required change. + + A complicating factor is that after storing a sample in a reorder + admin it potentially becomes part of a chain of samples, and may be + located anywhere within that chain. When that happens, the rsample + parameter provided to reorder_rsample becomes useless for adjusting + the reference counts as required. + + The initial reference count as it comes out of defragmentation is + always BIAS-per-rdata, which means all rmgs referenced by the + sample have refcount = BIAS if there is only ever a single sample + in each rmsg. (If multiple data submessages have been packed into + a single message, they'll all contribute to the refcount.) + + The reference count adjustment is incremented by reorder_rsample + whenever it stores or forwards the sample, and left unchanged when + it rejects it (old samples & duplicates). The initial reference + needs to be accounted for as well, and so: + + - In [CASE I]: accept (or forward): +1 for accepting it, -BIAS for + the initial reference, for a net change of 1-BIAS. Reject: 0 for + rejecting it, still -BIAS for the initial reference, for a net + change of -BIAS. + + - In [CASE 2], each reorder admin gets its own copy of the sample, + and therefore the sample that came out of defragmentation is + unchanged, and may thus be used, regardless of the adjustment + required. + + Accept by M out N: +M for accepting, 0 for the N-M rejects, -BIAS + for the initial reference. For a net change of M-BIAS. + + So in both cases, the adjustment needed is the number of reorder + admins that accepted it, less BIAS for the initial reference. We + can't use the original sample because of [CASE I], so we adjust + based on the fragment chain instead of the sample. Example code is + in the overview comment at the top of this file. */ + +struct nn_reorder { + ut_avlTree_t sampleivtree; + struct nn_rsample *max_sampleiv; /* = max(sampleivtree) */ + seqno_t next_seq; + enum nn_reorder_mode mode; + uint32_t max_samples; + uint32_t n_samples; +}; + +static const ut_avlTreedef_t reorder_sampleivtree_treedef = + UT_AVL_TREEDEF_INITIALIZER (offsetof (struct nn_rsample, u.reorder.avlnode), offsetof (struct nn_rsample, u.reorder.min), compare_seqno, 0); + +struct nn_reorder *nn_reorder_new (enum nn_reorder_mode mode, uint32_t max_samples) +{ + struct nn_reorder *r; + if ((r = os_malloc (sizeof (*r))) == NULL) + return NULL; + ut_avlInit (&reorder_sampleivtree_treedef, &r->sampleivtree); + r->max_sampleiv = NULL; + r->next_seq = 1; + r->mode = mode; + r->max_samples = max_samples; + r->n_samples = 0; + return r; +} + +void nn_fragchain_unref (struct nn_rdata *frag) +{ + struct nn_rdata *frag1; + while (frag) + { + frag1 = frag->nextfrag; + nn_rdata_unref (frag); + frag = frag1; + } +} + +void nn_reorder_free (struct nn_reorder *r) +{ + struct nn_rsample *iv; + struct nn_rsample_chain_elem *sce; + /* FXIME: instead of findmin/delete, a treewalk can be used. */ + iv = ut_avlFindMin (&reorder_sampleivtree_treedef, &r->sampleivtree); + while (iv) + { + ut_avlDelete (&reorder_sampleivtree_treedef, &r->sampleivtree, iv); + sce = iv->u.reorder.sc.first; + while (sce) + { + struct nn_rsample_chain_elem *sce1 = sce->next; + nn_fragchain_unref (sce->fragchain); + sce = sce1; + } + iv = ut_avlFindMin (&reorder_sampleivtree_treedef, &r->sampleivtree); + } + os_free (r); +} + +static void reorder_add_rsampleiv (struct nn_reorder *reorder, struct nn_rsample *rsample) +{ + ut_avlIPath_t path; + if (ut_avlLookupIPath (&reorder_sampleivtree_treedef, &reorder->sampleivtree, &rsample->u.reorder.min, &path) != NULL) + assert (0); + ut_avlInsertIPath (&reorder_sampleivtree_treedef, &reorder->sampleivtree, rsample, &path); +} + +#ifndef NDEBUG +static int rsample_is_singleton (const struct nn_rsample_reorder *s) +{ + assert (s->min < s->maxp1); + if (s->n_samples != 1) + return 0; + assert (s->min + 1 == s->maxp1); + assert (s->min + s->n_samples <= s->maxp1); + assert (s->sc.first != NULL); + assert (s->sc.first == s->sc.last); + assert (s->sc.first->next == NULL); + return 1; +} +#endif + +static void append_rsample_interval (struct nn_rsample *a, struct nn_rsample *b) +{ + a->u.reorder.sc.last->next = b->u.reorder.sc.first; + a->u.reorder.sc.last = b->u.reorder.sc.last; + a->u.reorder.maxp1 = b->u.reorder.maxp1; + a->u.reorder.n_samples += b->u.reorder.n_samples; +} + +static int reorder_try_append_and_discard (struct nn_reorder *reorder, struct nn_rsample *appendto, struct nn_rsample *todiscard) +{ + if (todiscard == NULL) + { + TRACE_RADMIN ((" try_append_and_discard: fail: todiscard = NULL\n")); + return 0; + } + else if (appendto->u.reorder.maxp1 < todiscard->u.reorder.min) + { + TRACE_RADMIN ((" try_append_and_discard: fail: appendto = [%lld,%lld) @ %p, " + "todiscard = [%lld,%lld) @ %p - gap\n", + appendto->u.reorder.min, appendto->u.reorder.maxp1, (void *) appendto, + todiscard->u.reorder.min, todiscard->u.reorder.maxp1, (void *) todiscard)); + return 0; + } + else + { + TRACE_RADMIN ((" try_append_and_discard: success: appendto = [%lld,%lld) @ %p, " + "todiscard = [%lld,%lld) @ %p\n", + appendto->u.reorder.min, appendto->u.reorder.maxp1, (void *) appendto, + todiscard->u.reorder.min, todiscard->u.reorder.maxp1, (void *) todiscard)); + assert (todiscard->u.reorder.min == appendto->u.reorder.maxp1); + ut_avlDelete (&reorder_sampleivtree_treedef, &reorder->sampleivtree, todiscard); + append_rsample_interval (appendto, todiscard); + TRACE_RADMIN ((" try_append_and_discard: max_sampleiv needs update? %s\n", + (todiscard == reorder->max_sampleiv) ? "yes" : "no")); + /* Inform caller whether reorder->max must be updated -- the + expected thing to do is to update it to appendto here, but that + fails if appendto isn't actually in the tree. And that happens + to be the fast path where the sample that comes in has the + sequence number we expected. */ + return todiscard == reorder->max_sampleiv; + } +} + +struct nn_rsample *nn_reorder_rsample_dup (struct nn_rmsg *rmsg, struct nn_rsample *rsampleiv) +{ + /* Duplicates the rsampleiv without updating any reference counts: + that is left to the caller, as they do not need to be updated if + the duplicate ultimately doesn't get used. + + The rmsg is the one to allocate from, and must be the one + currently being processed (one can only allocate memory from an + uncommitted rmsg) and must be referenced by an rdata in + rsampleiv. */ + struct nn_rsample *rsampleiv_new; + struct nn_rsample_chain_elem *sce; + assert (rsample_is_singleton (&rsampleiv->u.reorder)); +#ifndef NDEBUG + { + struct nn_rdata *d = rsampleiv->u.reorder.sc.first->fragchain; + while (d && d->rmsg != rmsg) + d = d->nextfrag; + assert (d != NULL); + } +#endif + if ((rsampleiv_new = nn_rmsg_alloc (rmsg, sizeof (*rsampleiv_new))) == NULL) + return NULL; + if ((sce = nn_rmsg_alloc (rmsg, sizeof (*sce))) == NULL) + return NULL; + sce->fragchain = rsampleiv->u.reorder.sc.first->fragchain; + sce->next = NULL; + sce->sampleinfo = rsampleiv->u.reorder.sc.first->sampleinfo; + *rsampleiv_new = *rsampleiv; + rsampleiv_new->u.reorder.sc.first = rsampleiv_new->u.reorder.sc.last = sce; + return rsampleiv_new; +} + +struct nn_rdata *nn_rsample_fragchain (struct nn_rsample *rsample) +{ + assert (rsample_is_singleton (&rsample->u.reorder)); + return rsample->u.reorder.sc.first->fragchain; +} + +static char reorder_mode_as_char (const struct nn_reorder *reorder) +{ + switch (reorder->mode) + { + case NN_REORDER_MODE_NORMAL: return 'R'; + case NN_REORDER_MODE_MONOTONICALLY_INCREASING: return 'U'; + case NN_REORDER_MODE_ALWAYS_DELIVER: return 'A'; + } + assert (0); + return '?'; +} + +static void delete_last_sample (struct nn_reorder *reorder) +{ + struct nn_rsample_reorder *last = &reorder->max_sampleiv->u.reorder; + struct nn_rdata *fragchain; + + /* This just removes it, it doesn't adjust the count. It is not + supposed to be called on an radmin with only one sample. */ + assert (reorder->n_samples > 0); + assert (reorder->max_sampleiv != NULL); + + if (last->sc.first == last->sc.last) + { + /* Last sample is in an interval of its own - delete it, and + recalc max_sampleiv. */ + TRACE_RADMIN ((" delete_last_sample: in singleton interval\n")); + fragchain = last->sc.first->fragchain; + ut_avlDelete (&reorder_sampleivtree_treedef, &reorder->sampleivtree, reorder->max_sampleiv); + reorder->max_sampleiv = ut_avlFindMax (&reorder_sampleivtree_treedef, &reorder->sampleivtree); + /* No harm done if it the sampleivtree is empty, except that we + chose not to allow it */ + assert (reorder->max_sampleiv != NULL); + } + else + { + /* Last sample is to be removed from the final interval. Which + requires scanning the sample chain because it is a + singly-linked list (so you might not want max_samples set very + large!). Can't be a singleton list, so might as well chop off + one evaluation of the loop condition. */ + struct nn_rsample_chain_elem *e, *pe; + TRACE_RADMIN ((" delete_last_sample: scanning last interval [%llu..%llu)\n", + last->min, last->maxp1)); + assert (last->n_samples >= 1); + assert (last->min + last->n_samples <= last->maxp1); + e = last->sc.first; + do { + pe = e; + e = e->next; + } while (e != last->sc.last); + fragchain = e->fragchain; + pe->next = NULL; + assert (pe->sampleinfo->seq + 1 < last->maxp1); + last->sc.last = pe; + last->maxp1--; + last->n_samples--; + } + + nn_fragchain_unref (fragchain); +} + +nn_reorder_result_t nn_reorder_rsample (struct nn_rsample_chain *sc, struct nn_reorder *reorder, struct nn_rsample *rsampleiv, int *refcount_adjust, int delivery_queue_full_p) +{ + /* Adds an rsample (represented as an interval) to the reorder admin + and returns the chain of consecutive samples ready for delivery + because of the insertion. Consequently, if it returns a sample + chain, the sample referenced by rsampleiv is the first in the + chain. + + refcount_adjust is incremented if the sample is not discarded. */ + struct nn_rsample_reorder *s = &rsampleiv->u.reorder; + + TRACE_RADMIN (("reorder_sample(%p %c, %lld @ %p) expecting %lld:\n", (void *) reorder, reorder_mode_as_char (reorder), rsampleiv->u.reorder.min, (void *) rsampleiv, reorder->next_seq)); + + /* Incoming rsample must be a singleton */ + assert (rsample_is_singleton (s)); + + /* Reorder must not contain samples with sequence numbers <= next + seq; max must be set iff the reorder is non-empty. */ +#ifndef NDEBUG + { + struct nn_rsample *min = ut_avlFindMin (&reorder_sampleivtree_treedef, &reorder->sampleivtree); + if (min) + TRACE_RADMIN ((" min = %lld @ %p\n", min->u.reorder.min, (void *) min)); + assert (min == NULL || reorder->next_seq < min->u.reorder.min); + assert ((reorder->max_sampleiv == NULL && min == NULL) || + (reorder->max_sampleiv != NULL && min != NULL)); + } +#endif + assert ((!!ut_avlIsEmpty (&reorder->sampleivtree)) == (reorder->max_sampleiv == NULL)); + assert (reorder->max_sampleiv == NULL || reorder->max_sampleiv == ut_avlFindMax (&reorder_sampleivtree_treedef, &reorder->sampleivtree)); + assert (reorder->n_samples <= reorder->max_samples); + if (reorder->max_sampleiv) + TRACE_RADMIN ((" max = [%lld,%lld) @ %p\n", reorder->max_sampleiv->u.reorder.min, reorder->max_sampleiv->u.reorder.maxp1, (void *) reorder->max_sampleiv)); + + if (s->min == reorder->next_seq || + (s->min > reorder->next_seq && reorder->mode == NN_REORDER_MODE_MONOTONICALLY_INCREASING) || + reorder->mode == NN_REORDER_MODE_ALWAYS_DELIVER) + { + /* Can deliver at least one sample, but that appends samples to + the delivery queue. If delivery_queue_full_p is set, the delivery + queue has hit its maximum length, so appending to it isn't such + a great idea. Therefore, we simply reject the sample. (We + have to, we can't have a deliverable sample in the reorder + admin, or things go wrong very quickly.) */ + if (delivery_queue_full_p) + { + TRACE_RADMIN ((" discarding deliverable sample: delivery queue is full\n")); + return NN_REORDER_REJECT; + } + + /* 's' is next sample to be delivered; maybe we can append the + first interval in the tree to it. We can avoid all processing + if the index is empty, which is the normal case. Unreliable + out-of-order either ends up here or in discard.) */ + if (reorder->max_sampleiv != NULL) + { + struct nn_rsample *min = ut_avlFindMin (&reorder_sampleivtree_treedef, &reorder->sampleivtree); + TRACE_RADMIN ((" try append_and_discard\n")); + if (reorder_try_append_and_discard (reorder, rsampleiv, min)) + reorder->max_sampleiv = NULL; + } + reorder->next_seq = s->maxp1; + *sc = rsampleiv->u.reorder.sc; + (*refcount_adjust)++; + TRACE_RADMIN ((" return [%lld,%lld)\n", s->min, s->maxp1)); + + /* Adjust reorder->n_samples, new sample is not counted yet */ + assert (s->maxp1 - s->min >= 1); + assert (s->maxp1 - s->min <= (int) INT32_MAX); + assert (s->min + s->n_samples <= s->maxp1); + assert (reorder->n_samples >= s->n_samples - 1); + reorder->n_samples -= s->n_samples - 1; + return (nn_reorder_result_t) s->n_samples; + } + else if (s->min < reorder->next_seq) + { + /* we've moved beyond this one: discard it; no need to adjust + n_samples */ + TRACE_RADMIN ((" discard: too old\n")); + return NN_REORDER_TOO_OLD; /* don't want refcount increment */ + } + else if (ut_avlIsEmpty (&reorder->sampleivtree)) + { + /* else, if nothing's stored simply add this one, max_samples = 0 + is technically allowed, and potentially useful, so check for + it */ + assert (reorder->n_samples == 0); + TRACE_RADMIN ((" adding to empty store\n")); + if (reorder->max_samples == 0) + { + TRACE_RADMIN ((" NOT - max_samples hit\n")); + return NN_REORDER_REJECT; + } + else + { + reorder_add_rsampleiv (reorder, rsampleiv); + reorder->max_sampleiv = rsampleiv; + reorder->n_samples++; + } + } + else if (s->min == reorder->max_sampleiv->u.reorder.maxp1) + { + /* note: sampleivtree not empty <=> max_sampleiv is set (compilers + and static analyzers may warn) */ + if (delivery_queue_full_p) + { + /* growing last inteval will not be accepted when this flag is set */ + TRACE_RADMIN ((" discarding sample: only accepting delayed samples due to backlog in delivery queue\n")); + return NN_REORDER_REJECT; + } + + /* grow the last interval, if we're still accepting samples */ + TRACE_RADMIN ((" growing last interval\n")); + if (reorder->n_samples < reorder->max_samples) + { + append_rsample_interval (reorder->max_sampleiv, rsampleiv); + reorder->n_samples++; + } + else + { + TRACE_RADMIN ((" discarding sample: max_samples reached and sample at end\n")); + return NN_REORDER_REJECT; + } + } + else if (s->min > reorder->max_sampleiv->u.reorder.maxp1) + { + if (delivery_queue_full_p) + { + /* new interval at the end will not be accepted when this flag is set */ + TRACE_RADMIN ((" discarding sample: only accepting delayed samples due to backlog in delivery queue\n")); + return NN_REORDER_REJECT; + } + if (reorder->n_samples < reorder->max_samples) + { + TRACE_RADMIN ((" new interval at end\n")); + reorder_add_rsampleiv (reorder, rsampleiv); + reorder->max_sampleiv = rsampleiv; + reorder->n_samples++; + } + else + { + TRACE_RADMIN ((" discarding sample: max_samples reached and sample at end\n")); + return NN_REORDER_REJECT; + } + } + else + { + /* lookup interval predeq=[m,n) s.t. m <= s->min and + immsucc=[m',n') s.t. m' = s->maxp1: + + - if m <= s->min < n we discard it (duplicate) + - if n=s->min we can append s to predeq + - if immsucc exists we can prepend s to immsucc + - and possibly join predeq, s, and immsucc */ + struct nn_rsample *predeq, *immsucc; + TRACE_RADMIN ((" hard case ...\n")); + + if (config.late_ack_mode && delivery_queue_full_p) + { + TRACE_RADMIN ((" discarding sample: delivery queue full\n")); + return NN_REORDER_REJECT; + } + + predeq = ut_avlLookupPredEq (&reorder_sampleivtree_treedef, &reorder->sampleivtree, &s->min); + if (predeq) + TRACE_RADMIN ((" predeq = [%lld,%lld) @ %p\n", + predeq->u.reorder.min, predeq->u.reorder.maxp1, (void *) predeq)); + else + TRACE_RADMIN ((" predeq = null\n")); + if (predeq && s->min >= predeq->u.reorder.min && s->min < predeq->u.reorder.maxp1) + { + /* contained in predeq */ + TRACE_RADMIN ((" discard: contained in predeq\n")); + return NN_REORDER_REJECT; + } + + immsucc = ut_avlLookup (&reorder_sampleivtree_treedef, &reorder->sampleivtree, &s->maxp1); + if (immsucc) + TRACE_RADMIN ((" immsucc = [%lld,%lld) @ %p\n", + immsucc->u.reorder.min, immsucc->u.reorder.maxp1, (void *) immsucc)); + else + TRACE_RADMIN ((" immsucc = null\n")); + if (predeq && s->min == predeq->u.reorder.maxp1) + { + /* grow predeq at end, and maybe append immsucc as well */ + TRACE_RADMIN ((" growing predeq at end ...\n")); + append_rsample_interval (predeq, rsampleiv); + if (reorder_try_append_and_discard (reorder, predeq, immsucc)) + reorder->max_sampleiv = predeq; + } + else if (immsucc) + { + /* no predecessor, grow immsucc at head, which _does_ alter the + key of the node in the tree, but _doesn't_ change the tree's + structure. */ + TRACE_RADMIN ((" growing immsucc at head\n")); + s->sc.last->next = immsucc->u.reorder.sc.first; + immsucc->u.reorder.sc.first = s->sc.first; + immsucc->u.reorder.min = s->min; + immsucc->u.reorder.n_samples += s->n_samples; + + /* delete_last_sample may eventually decide to delete the last + sample contained in immsucc without checking whether immsucc + were allocated dependent on that sample. That in turn would + cause sampleivtree to point to freed memory (either freed as + in free(), or freed as in available for reuse, and hence the + result may be a silent corruption of the interval tree). + + We do know that rsampleiv will remain live, that it is not + dependent on the last sample (because we're growing immsucc + at the head), and that we don't otherwise need it anymore. + Therefore, we can swap rsampleiv in for immsucc and avoid the + case above. */ + rsampleiv->u.reorder = immsucc->u.reorder; + ut_avlSwapNode (&reorder_sampleivtree_treedef, &reorder->sampleivtree, immsucc, rsampleiv); + if (immsucc == reorder->max_sampleiv) + reorder->max_sampleiv = rsampleiv; + } + else + { + /* neither extends predeq nor immsucc */ + TRACE_RADMIN ((" new interval\n")); + reorder_add_rsampleiv (reorder, rsampleiv); + } + + /* do not let radmin grow beyond max_samples; now that we've + inserted it (and possibly have grown the radmin beyond its max + size), we no longer risk deleting the interval that the new + sample belongs to when deleting the last sample. */ + if (reorder->n_samples < reorder->max_samples) + reorder->n_samples++; + else + { + delete_last_sample (reorder); + } + } + + (*refcount_adjust)++; + return NN_REORDER_ACCEPT; +} + +static struct nn_rsample *coalesce_intervals_touching_range (struct nn_reorder *reorder, seqno_t min, seqno_t maxp1, int *valuable) +{ + struct nn_rsample *s, *t; + *valuable = 0; + /* Find first (lowest m) interval [m,n) s.t. n >= min && m <= maxp1 */ + s = ut_avlLookupPredEq (&reorder_sampleivtree_treedef, &reorder->sampleivtree, &min); + if (s && s->u.reorder.maxp1 >= min) + { + /* m <= min && n >= min (note: pred of s [m',n') necessarily has n' < m) */ +#ifndef NDEBUG + struct nn_rsample *q = ut_avlFindPred (&reorder_sampleivtree_treedef, &reorder->sampleivtree, s); + assert (q == NULL || q->u.reorder.maxp1 < min); +#endif + } + else + { + /* No good, but the first (if s = NULL) or the next one (if s != + NULL) may still have m <= maxp1 (m > min is implied now). If + not, no such interval. */ + s = ut_avlFindSucc (&reorder_sampleivtree_treedef, &reorder->sampleivtree, s); + if (!(s && s->u.reorder.min <= maxp1)) + return NULL; + } + /* Append successors [m',n') s.t. m' <= maxp1 to s */ + assert (s->u.reorder.min + s->u.reorder.n_samples <= s->u.reorder.maxp1); + while ((t = ut_avlFindSucc (&reorder_sampleivtree_treedef, &reorder->sampleivtree, s)) != NULL && t->u.reorder.min <= maxp1) + { + ut_avlDelete (&reorder_sampleivtree_treedef, &reorder->sampleivtree, t); + assert (t->u.reorder.min + t->u.reorder.n_samples <= t->u.reorder.maxp1); + append_rsample_interval (s, t); + *valuable = 1; + } + /* If needed, grow range to [min,maxp1) */ + if (min < s->u.reorder.min) + { + *valuable = 1; + s->u.reorder.min = min; + } + if (maxp1 > s->u.reorder.maxp1) + { + *valuable = 1; + s->u.reorder.maxp1 = maxp1; + } + return s; +} + +struct nn_rdata *nn_rdata_newgap (struct nn_rmsg *rmsg) +{ + struct nn_rdata *d; + if ((d = nn_rdata_new (rmsg, 0, 0, 0, 0)) == NULL) + return NULL; + nn_rdata_addbias (d); + return d; +} + +static int reorder_insert_gap (struct nn_reorder *reorder, struct nn_rdata *rdata, seqno_t min, seqno_t maxp1) +{ + struct nn_rsample_chain_elem *sce; + struct nn_rsample *s; + ut_avlIPath_t path; + if (ut_avlLookupIPath (&reorder_sampleivtree_treedef, &reorder->sampleivtree, &min, &path) != NULL) + assert (0); + if ((sce = nn_rmsg_alloc (rdata->rmsg, sizeof (*sce))) == NULL) + return 0; + sce->fragchain = rdata; + sce->next = NULL; + sce->sampleinfo = NULL; + if ((s = nn_rmsg_alloc (rdata->rmsg, sizeof (*s))) == NULL) + return 0; + s->u.reorder.sc.first = s->u.reorder.sc.last = sce; + s->u.reorder.min = min; + s->u.reorder.maxp1 = maxp1; + s->u.reorder.n_samples = 1; + ut_avlInsertIPath (&reorder_sampleivtree_treedef, &reorder->sampleivtree, s, &path); + return 1; +} + +nn_reorder_result_t nn_reorder_gap (struct nn_rsample_chain *sc, struct nn_reorder *reorder, struct nn_rdata *rdata, seqno_t min, seqno_t maxp1, int *refcount_adjust) +{ + /* All sequence numbers in [min,maxp1) are unavailable so any + fragments in that range must be discarded. Used both for + Hearbeats (by setting min=1) and for Gaps. + + Case I: maxp1 <= next_seq. No effect whatsoever. + + Otherwise: + + Case II: min <= next_seq. All samples we have with sequence + numbers less than maxp1 plus those following it consecutively + are returned, and next_seq is updated to max(maxp1, highest + returned sequence number+1) + + Else: + + Case III: Causes coalescing of intervals overlapping with + [min,maxp1) or consecutive to it, possibly extending + intervals to min on the lower bound or maxp1 on the upper + one, or if there are no such intervals, the creation of a + [min,maxp1) interval without any samples. + + NOTE: must not store anything (i.e. modify rdata, + refcount_adjust) if gap causes data to be delivered: altnerative + path for out-of-order delivery if all readers of a reliable + proxy-writer are unrelibale depends on it. */ + struct nn_rsample *coalesced; + int valuable; + + TRACE_RADMIN (("reorder_gap(%p %c, [%lld,%lld) data %p) expecting %lld:\n", + (void *) reorder, reorder_mode_as_char (reorder), + min, maxp1, (void *) rdata, reorder->next_seq)); + + if (maxp1 <= reorder->next_seq) + { + TRACE_RADMIN ((" too old\n")); + return NN_REORDER_TOO_OLD; + } + if (reorder->mode != NN_REORDER_MODE_NORMAL) + { + TRACE_RADMIN ((" special mode => don't care\n")); + return NN_REORDER_REJECT; + } + + /* Coalesce all intervals [m,n) with n >= min or m <= maxp1 */ + if ((coalesced = coalesce_intervals_touching_range (reorder, min, maxp1, &valuable)) == NULL) + { + nn_reorder_result_t res; + TRACE_RADMIN ((" coalesced = null\n")); + if (min <= reorder->next_seq) + { + TRACE_RADMIN ((" next expected: %lld\n", maxp1)); + reorder->next_seq = maxp1; + res = NN_REORDER_ACCEPT; + } + else if (reorder->n_samples == reorder->max_samples && + (reorder->max_sampleiv == NULL || min > reorder->max_sampleiv->u.reorder.maxp1)) + { + /* n_samples = max_samples => (max_sampleiv = NULL <=> max_samples = 0) */ + TRACE_RADMIN ((" discarding gap: max_samples reached and gap at end\n")); + res = NN_REORDER_REJECT; + } + else if (!reorder_insert_gap (reorder, rdata, min, maxp1)) + { + TRACE_RADMIN ((" store gap failed: no memory\n")); + res = NN_REORDER_REJECT; + } + else + { + TRACE_RADMIN ((" storing gap\n")); + res = NN_REORDER_ACCEPT; + /* do not let radmin grow beyond max_samples; there is a small + possibility that we insert it & delete it immediately + afterward. */ + if (reorder->n_samples < reorder->max_samples) + reorder->n_samples++; + else + delete_last_sample (reorder); + (*refcount_adjust)++; + } + reorder->max_sampleiv = ut_avlFindMax (&reorder_sampleivtree_treedef, &reorder->sampleivtree); + return res; + } + else if (coalesced->u.reorder.min <= reorder->next_seq) + { + TRACE_RADMIN ((" coalesced = [%lld,%lld) @ %p containing %d samples\n", + coalesced->u.reorder.min, coalesced->u.reorder.maxp1, + (void *) coalesced, coalesced->u.reorder.n_samples)); + ut_avlDelete (&reorder_sampleivtree_treedef, &reorder->sampleivtree, coalesced); + if (coalesced->u.reorder.min <= reorder->next_seq) + assert (min <= reorder->next_seq); + reorder->next_seq = coalesced->u.reorder.maxp1; + reorder->max_sampleiv = ut_avlFindMax (&reorder_sampleivtree_treedef, &reorder->sampleivtree); + TRACE_RADMIN ((" next expected: %lld\n", reorder->next_seq)); + *sc = coalesced->u.reorder.sc; + + /* Adjust n_samples */ + assert (coalesced->u.reorder.min + coalesced->u.reorder.n_samples <= coalesced->u.reorder.maxp1); + assert (reorder->n_samples >= coalesced->u.reorder.n_samples); + reorder->n_samples -= coalesced->u.reorder.n_samples; + return (nn_reorder_result_t) coalesced->u.reorder.n_samples; + } + else + { + TRACE_RADMIN ((" coalesced = [%lld,%lld) @ %p - that is all\n", + coalesced->u.reorder.min, coalesced->u.reorder.maxp1, (void *) coalesced)); + reorder->max_sampleiv = ut_avlFindMax (&reorder_sampleivtree_treedef, &reorder->sampleivtree); + return valuable ? NN_REORDER_ACCEPT : NN_REORDER_REJECT; + } +} + +int nn_reorder_wantsample (struct nn_reorder *reorder, seqno_t seq) +{ + struct nn_rsample *s; + if (seq < reorder->next_seq) + /* trivially not interesting */ + return 0; + /* Find interval that contains seq, if we know seq. We are + interested if seq is outside this interval (if any). */ + s = ut_avlLookupPredEq (&reorder_sampleivtree_treedef, &reorder->sampleivtree, &seq); + return (s == NULL || s->u.reorder.maxp1 <= seq); +} + +unsigned nn_reorder_nackmap (struct nn_reorder *reorder, seqno_t base, seqno_t maxseq, struct nn_sequence_number_set *map, uint32_t maxsz, int notail) +{ + struct nn_rsample *iv; + seqno_t i; + + /* reorder->next_seq-1 is the last one we delivered, so the last one + we ack; maxseq is the latest sample we know exists. Valid bitmap + lengths are 1 .. 256, so maxsz must be within that range, except + that we allow length-0 bitmaps here as well. Map->numbits is + bounded by max(based on sequence numbers, maxsz). */ + assert (maxsz <= 256); + /* not much point in requesting more data than we're willing to store + (it would be ok if we knew we'd be able to keep up) */ + if (maxsz > reorder->max_samples) + maxsz = reorder->max_samples; +#if 0 + /* this is what it used to be, where the reorder buffer is with + delivery */ + base = reorder->next_seq; +#else + if (base > reorder->next_seq) + { + NN_ERROR ("nn_reorder_nackmap: incorrect base sequence number supplied (%"PRId64" > %"PRId64")\n", base, reorder->next_seq); + base = reorder->next_seq; + } +#endif + if (maxseq + 1 < base) + { + NN_ERROR ("nn_reorder_nackmap: incorrect max sequence number supplied (maxseq %"PRId64" base %"PRId64")\n", maxseq, base); + maxseq = base - 1; + } + + map->bitmap_base = toSN (base); + if (maxseq + 1 - base > maxsz) + map->numbits = maxsz; + else + map->numbits = (uint32_t) (maxseq + 1 - base); + nn_bitset_zero (map->numbits, map->bits); + + if ((iv = ut_avlFindMin (&reorder_sampleivtree_treedef, &reorder->sampleivtree)) != NULL) + assert (iv->u.reorder.min > base); + i = base; + while (iv && i < base + map->numbits) + { + for (; i < base + map->numbits && i < iv->u.reorder.min; i++) + { + unsigned x = (unsigned) (i - base); + nn_bitset_set (map->numbits, map->bits, x); + } + i = iv->u.reorder.maxp1; + iv = ut_avlFindSucc (&reorder_sampleivtree_treedef, &reorder->sampleivtree, iv); + } + if (notail && i < base + map->numbits) + map->numbits = (unsigned) (i - base); + else + { + for (; i < base + map->numbits; i++) + { + unsigned x = (unsigned) (i - base); + nn_bitset_set (map->numbits, map->bits, x); + } + } + return map->numbits; +} + +seqno_t nn_reorder_next_seq (const struct nn_reorder *reorder) +{ + return reorder->next_seq; +} + +/* DQUEUE -------------------------------------------------------------- */ + +struct nn_dqueue { + os_mutex lock; + os_cond cond; + nn_dqueue_handler_t handler; + void *handler_arg; + + struct nn_rsample_chain sc; + + struct thread_state1 *ts; + char *name; + uint32_t max_samples; + os_atomic_uint32_t nof_samples; +}; + +enum dqueue_elem_kind { + DQEK_DATA, + DQEK_GAP, + DQEK_BUBBLE +}; + +enum nn_dqueue_bubble_kind { + NN_DQBK_STOP, /* _not_ os_malloc()ed! */ + NN_DQBK_CALLBACK, + NN_DQBK_RDGUID +}; + +struct nn_dqueue_bubble { + /* sample_chain_elem must be first: and is used to link it into the + queue, with the sampleinfo pointing to itself, but mangled */ + struct nn_rsample_chain_elem sce; + + enum nn_dqueue_bubble_kind kind; + union { + /* stop */ + struct { + nn_dqueue_callback_t cb; + void *arg; + } cb; + struct { + nn_guid_t rdguid; + uint32_t count; + } rdguid; + } u; +}; + +static enum dqueue_elem_kind dqueue_elem_kind (const struct nn_rsample_chain_elem *e) +{ + if (e->sampleinfo == NULL) + return DQEK_GAP; + else if ((char *) e->sampleinfo != (char *) e) + return DQEK_DATA; + else + return DQEK_BUBBLE; +} + +static uint32_t dqueue_thread (struct nn_dqueue *q) +{ + struct thread_state1 *self = lookup_thread_state (); + nn_mtime_t next_thread_cputime = { 0 }; + int keepgoing = 1; + nn_guid_t rdguid, *prdguid = NULL; + uint32_t rdguid_count = 0; + + os_mutexLock (&q->lock); + while (keepgoing) + { + struct nn_rsample_chain sc; + + LOG_THREAD_CPUTIME (next_thread_cputime); + + if (q->sc.first == NULL) + os_condWait (&q->cond, &q->lock); + sc = q->sc; + q->sc.first = q->sc.last = NULL; + os_mutexUnlock (&q->lock); + + while (sc.first) + { + struct nn_rsample_chain_elem *e = sc.first; + int ret; + sc.first = e->next; + if (os_atomic_dec32_ov (&q->nof_samples) == 1) { + os_condBroadcast (&q->cond); + } + thread_state_awake (self); + switch (dqueue_elem_kind (e)) + { + case DQEK_DATA: + ret = q->handler (e->sampleinfo, e->fragchain, prdguid, q->handler_arg); + (void) ret; /* eliminate set-but-not-used in NDEBUG case */ + assert (ret == 0); /* so every handler will return 0 */ + /* FALLS THROUGH */ + case DQEK_GAP: + nn_fragchain_unref (e->fragchain); + if (rdguid_count > 0) + { + if (--rdguid_count == 0) + prdguid = NULL; + } + break; + + case DQEK_BUBBLE: + { + struct nn_dqueue_bubble *b = (struct nn_dqueue_bubble *) e->sampleinfo; + if (b->kind == NN_DQBK_STOP) + { + /* Stuff enqueued behind the bubble will still be + processed, we do want to drain the queue. Nothing + may be queued anymore once we queue the stop bubble, + so q->sc.first should be empty. If it isn't + ... dqueue_free fail an assertion. STOP bubble + doesn't get malloced, and hence not freed. */ + keepgoing = 0; + } + else + { + switch (b->kind) + { + case NN_DQBK_STOP: + abort (); + case NN_DQBK_CALLBACK: + b->u.cb.cb (b->u.cb.arg); + break; + case NN_DQBK_RDGUID: + rdguid = b->u.rdguid.rdguid; + rdguid_count = b->u.rdguid.count; + prdguid = &rdguid; + break; + } + os_free (b); + } + break; + } + } + thread_state_asleep (self); + } + + os_mutexLock (&q->lock); + } + os_mutexUnlock (&q->lock); + return 0; +} + +struct nn_dqueue *nn_dqueue_new (const char *name, uint32_t max_samples, nn_dqueue_handler_t handler, void *arg) +{ + struct nn_dqueue *q; + char *thrname; + + if ((q = os_malloc (sizeof (*q))) == NULL) + goto fail_q; + if ((q->name = os_strdup (name)) == NULL) + goto fail_name; + q->max_samples = max_samples; + os_atomic_st32 (&q->nof_samples, 0); + q->handler = handler; + q->handler_arg = arg; + q->sc.first = q->sc.last = NULL; + + os_mutexInit (&q->lock); + os_condInit (&q->cond, &q->lock); + + if ((thrname = os_malloc (3 + strlen (name) + 1)) == NULL) + goto fail_thrname; + sprintf (thrname, "dq.%s", name); + if ((q->ts = create_thread (thrname, (uint32_t (*) (void *)) dqueue_thread, q)) == NULL) + goto fail_thread; + os_free (thrname); + return q; + + fail_thread: + os_free (thrname); + fail_thrname: + os_condDestroy (&q->cond); + os_mutexDestroy (&q->lock); + os_free (q->name); + fail_name: + os_free (q); + fail_q: + return NULL; +} + +static int nn_dqueue_enqueue_locked (struct nn_dqueue *q, struct nn_rsample_chain *sc) +{ + int must_signal; + if (q->sc.first == NULL) + { + must_signal = 1; + q->sc = *sc; + } + else + { + must_signal = 0; + q->sc.last->next = sc->first; + q->sc.last = sc->last; + } + return must_signal; +} + +void nn_dqueue_enqueue (struct nn_dqueue *q, struct nn_rsample_chain *sc, nn_reorder_result_t rres) +{ + assert (rres > 0); + assert (sc->first); + assert (sc->last->next == NULL); + os_mutexLock (&q->lock); + os_atomic_add32 (&q->nof_samples, (uint32_t) rres); + if (nn_dqueue_enqueue_locked (q, sc)) + os_condBroadcast (&q->cond); + os_mutexUnlock (&q->lock); +} + +static int nn_dqueue_enqueue_bubble_locked (struct nn_dqueue *q, struct nn_dqueue_bubble *b) +{ + struct nn_rsample_chain sc; + b->sce.next = NULL; + b->sce.fragchain = NULL; + b->sce.sampleinfo = (struct nn_rsample_info *) b; + sc.first = sc.last = &b->sce; + return nn_dqueue_enqueue_locked (q, &sc); +} + +static void nn_dqueue_enqueue_bubble (struct nn_dqueue *q, struct nn_dqueue_bubble *b) +{ + os_mutexLock (&q->lock); + os_atomic_inc32 (&q->nof_samples); + if (nn_dqueue_enqueue_bubble_locked (q, b)) + os_condBroadcast (&q->cond); + os_mutexUnlock (&q->lock); +} + +void nn_dqueue_enqueue_callback (struct nn_dqueue *q, nn_dqueue_callback_t cb, void *arg) +{ + struct nn_dqueue_bubble *b; + b = os_malloc (sizeof (*b)); + b->kind = NN_DQBK_CALLBACK; + b->u.cb.cb = cb; + b->u.cb.arg = arg; + nn_dqueue_enqueue_bubble (q, b); +} + +void nn_dqueue_enqueue1 (struct nn_dqueue *q, const nn_guid_t *rdguid, struct nn_rsample_chain *sc, nn_reorder_result_t rres) +{ + struct nn_dqueue_bubble *b; + + b = os_malloc (sizeof (*b)); + b->kind = NN_DQBK_RDGUID; + b->u.rdguid.rdguid = *rdguid; + b->u.rdguid.count = (uint32_t) rres; + + assert (rres > 0); + assert (rdguid != NULL); + assert (sc->first); + assert (sc->last->next == NULL); + os_mutexLock (&q->lock); + os_atomic_add32 (&q->nof_samples, 1 + (uint32_t) rres); + if (nn_dqueue_enqueue_bubble_locked (q, b)) + os_condBroadcast (&q->cond); + nn_dqueue_enqueue_locked (q, sc); + os_mutexUnlock (&q->lock); +} + +int nn_dqueue_is_full (struct nn_dqueue *q) +{ + /* Reading nof_samples exactly once. It IS a 32-bit int, so at + worst we get an old value. That mean: we think it is full when + it is not, in which case we discard the sample and rely on a + retransmit; or we think it is not full when it is. But if we + don't mind the occasional extra sample in the queue (we don't), + and survive the occasional decision to not queue when it + could've been queued (we do), it should be ok. */ + const uint32_t count = os_atomic_ld32 (&q->nof_samples); + return (count >= q->max_samples); +} + +void nn_dqueue_wait_until_empty_if_full (struct nn_dqueue *q) +{ + const uint32_t count = os_atomic_ld32 (&q->nof_samples); + if (count >= q->max_samples) + { + os_mutexLock (&q->lock); + while (os_atomic_ld32 (&q->nof_samples) > 0) + os_condWait (&q->cond, &q->lock); + os_mutexUnlock (&q->lock); + } +} + +void nn_dqueue_free (struct nn_dqueue *q) +{ + /* There must not be any thread enqueueing things anymore at this + point. The stop bubble is special in that it does _not_ get + malloced or freed, but instead lives on the stack for a little + while. It would be a shame to fail in free() due to a lack of + heap space, would it not? */ + struct nn_dqueue_bubble b; + b.kind = NN_DQBK_STOP; + nn_dqueue_enqueue_bubble (q, &b); + + join_thread (q->ts); + assert (q->sc.first == NULL); + os_condDestroy (&q->cond); + os_mutexDestroy (&q->lock); + os_free (q->name); + os_free (q); +} diff --git a/src/core/ddsi/src/q_receive.c b/src/core/ddsi/src/q_receive.c new file mode 100644 index 0000000..a42d1dd --- /dev/null +++ b/src/core/ddsi/src/q_receive.c @@ -0,0 +1,3309 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include + +#include "os/os.h" + + +#include "ddsi/q_md5.h" +#include "util/ut_avl.h" +#include "q__osplser.h" +#include "ddsi/q_protocol.h" +#include "ddsi/q_rtps.h" +#include "ddsi/q_misc.h" +#include "ddsi/q_config.h" +#include "ddsi/q_log.h" +#include "ddsi/q_plist.h" +#include "ddsi/q_unused.h" +#include "ddsi/q_bswap.h" +#include "ddsi/q_lat_estim.h" +#include "ddsi/q_bitset.h" +#include "ddsi/q_xevent.h" +#include "ddsi/q_align.h" +#include "ddsi/q_addrset.h" +#include "ddsi/q_ddsi_discovery.h" +#include "ddsi/q_radmin.h" +#include "ddsi/q_error.h" +#include "ddsi/q_thread.h" +#include "ddsi/q_ephash.h" +#include "ddsi/q_lease.h" +#include "ddsi/q_gc.h" +#include "ddsi/q_whc.h" +#include "ddsi/q_entity.h" +#include "ddsi/q_xmsg.h" +#include "ddsi/q_receive.h" +#include "ddsi/q_transmit.h" +#include "ddsi/q_globals.h" +#include "ddsi/q_static_assert.h" + +#include "ddsi/sysdeps.h" + +/* +Notes: + +- for now, the safer option is usually chosen: hold a lock even if it + isn't strictly necessary in the particular configuration we have + (such as one receive thread vs. multiple receive threads) + +- nn_dqueue_enqueue may be called with pwr->e.lock held + +- deliver_user_data_synchronously may be called with pwr->e.lock held, + which is needed if IN-ORDER synchronous delivery is desired when + there are also multiple receive threads + +- deliver_user_data gets passed in whether pwr->e.lock is held on entry + +*/ + +static void deliver_user_data_synchronously (struct nn_rsample_chain *sc); + +static void maybe_set_reader_in_sync (struct proxy_writer *pwr, struct pwr_rd_match *wn, seqno_t last_deliv_seq) +{ + switch (wn->in_sync) + { + case PRMSS_SYNC: + assert(0); + break; + case PRMSS_TLCATCHUP: + if (last_deliv_seq >= wn->u.not_in_sync.end_of_tl_seq) + { + wn->in_sync = PRMSS_SYNC; + pwr->n_readers_out_of_sync--; + } + break; + case PRMSS_OUT_OF_SYNC: + if (nn_reorder_next_seq (wn->u.not_in_sync.reorder) - 1 >= wn->u.not_in_sync.end_of_out_of_sync_seq) + { + TRACE ((" msr_in_sync(%x:%x:%x:%x out-of-sync to tlcatchup)", PGUID (wn->rd_guid))); + wn->in_sync = PRMSS_TLCATCHUP; + maybe_set_reader_in_sync (pwr, wn, last_deliv_seq); + } + break; + } +} + +static int valid_sequence_number_set (const nn_sequence_number_set_t *snset) +{ + if (fromSN (snset->bitmap_base) <= 0) + return 0; + if (snset->numbits <= 0 || snset->numbits > 256) + return 0; + return 1; +} + +static int valid_fragment_number_set (const nn_fragment_number_set_t *fnset) +{ + if (fnset->bitmap_base <= 0) + return 0; + if (fnset->numbits <= 0 || fnset->numbits > 256) + return 0; + return 1; +} + +static int valid_AckNack (AckNack_t *msg, size_t size, int byteswap) +{ + nn_count_t *count; /* this should've preceded the bitmap */ + if (size < ACKNACK_SIZE (0)) + /* note: sizeof(*msg) is not sufficient verification, but it does + suffice for verifying all fixed header fields exist */ + return 0; + if (byteswap) + { + bswap_sequence_number_set_hdr (&msg->readerSNState); + /* bits[], count deferred until validation of fixed part */ + } + msg->readerId = nn_ntoh_entityid (msg->readerId); + msg->writerId = nn_ntoh_entityid (msg->writerId); + /* Validation following 8.3.7.1.3 + 8.3.5.5 */ + if (!valid_sequence_number_set (&msg->readerSNState)) + { + if (NN_STRICT_P) + return 0; + else + { + /* RTI generates AckNacks with bitmapBase = 0 and numBits = 0 + (and possibly others that I don't know about) - their + Wireshark RTPS dissector says that such a message has a + length-0 bitmap, which is to expected given the way the + length is computed from numbits */ + if (fromSN (msg->readerSNState.bitmap_base) == 0 && + msg->readerSNState.numbits == 0) + ; /* accept this one known case */ + else if (msg->readerSNState.numbits == 0) + ; /* maybe RTI, definitely Twinoaks */ + else + return 0; + } + } + /* Given the number of bits, we can compute the size of the AckNack + submessage, and verify that the submessage is large enough */ + if (size < ACKNACK_SIZE (msg->readerSNState.numbits)) + return 0; + count = (nn_count_t *) ((char *) &msg->readerSNState + NN_SEQUENCE_NUMBER_SET_SIZE (msg->readerSNState.numbits)); + if (byteswap) + { + bswap_sequence_number_set_bitmap (&msg->readerSNState); + *count = bswap4 (*count); + } + return 1; +} + +static int valid_Gap (Gap_t *msg, size_t size, int byteswap) +{ + if (size < GAP_SIZE (0)) + return 0; + if (byteswap) + { + bswapSN (&msg->gapStart); + bswap_sequence_number_set_hdr (&msg->gapList); + } + msg->readerId = nn_ntoh_entityid (msg->readerId); + msg->writerId = nn_ntoh_entityid (msg->writerId); + if (fromSN (msg->gapStart) <= 0) + return 0; + if (!valid_sequence_number_set (&msg->gapList)) + { + if (NN_STRICT_P || msg->gapList.numbits != 0) + return 0; + } + /* One would expect gapStart < gapList.base, but it is not required by + the spec for the GAP to valid. */ + if (size < GAP_SIZE (msg->gapList.numbits)) + return 0; + if (byteswap) + bswap_sequence_number_set_bitmap (&msg->gapList); + return 1; +} + +static int valid_InfoDST (InfoDST_t *msg, size_t size, UNUSED_ARG (int byteswap)) +{ + if (size < sizeof (*msg)) + return 0; + return 1; +} + +static int valid_InfoSRC (InfoSRC_t *msg, size_t size, UNUSED_ARG (int byteswap)) +{ + if (size < sizeof (*msg)) + return 0; + return 1; +} + +static int valid_InfoTS (InfoTS_t *msg, size_t size, int byteswap) +{ + assert (sizeof (SubmessageHeader_t) <= size); + if (msg->smhdr.flags & INFOTS_INVALIDATE_FLAG) + return 1; + else if (size < sizeof (InfoTS_t)) + return 0; + else + { + if (byteswap) + { + msg->time.seconds = bswap4 (msg->time.seconds); + msg->time.fraction = bswap4u (msg->time.fraction); + } + return valid_ddsi_timestamp (msg->time); + } +} + +static int valid_PT_InfoContainer (PT_InfoContainer_t *msg, size_t size, int byteswap) +{ + if (size < sizeof (PT_InfoContainer_t)) + return 0; +#if 0 + if (msg->smhdr.flags) + return 0; +#endif + if (byteswap) + msg->id = bswap4u (msg->id); + return 1; +} + +static int valid_Heartbeat (Heartbeat_t *msg, size_t size, int byteswap) +{ + if (size < sizeof (*msg)) + return 0; + if (byteswap) + { + bswapSN (&msg->firstSN); + bswapSN (&msg->lastSN); + msg->count = bswap4 (msg->count); + } + msg->readerId = nn_ntoh_entityid (msg->readerId); + msg->writerId = nn_ntoh_entityid (msg->writerId); + /* Validation following 8.3.7.5.3 */ + if (fromSN (msg->firstSN) <= 0 || + /* fromSN (msg->lastSN) <= 0 || -- implicit in last < first */ + fromSN (msg->lastSN) < fromSN (msg->firstSN)) + { + if (NN_STRICT_P) + return 0; + else + { + /* Note that we don't actually know the set of all possible + malformed messages that we have to process, so we stick to + the ones we've seen */ + if (fromSN (msg->firstSN) == fromSN (msg->lastSN) + 1) + ; /* ok */ + else + return 0; + } + } + return 1; +} + +static int valid_HeartbeatFrag (HeartbeatFrag_t *msg, size_t size, int byteswap) +{ + if (size < sizeof (*msg)) + return 0; + if (byteswap) + { + bswapSN (&msg->writerSN); + msg->lastFragmentNum = bswap4u (msg->lastFragmentNum); + msg->count = bswap4 (msg->count); + } + msg->readerId = nn_ntoh_entityid (msg->readerId); + msg->writerId = nn_ntoh_entityid (msg->writerId); + if (fromSN (msg->writerSN) <= 0 || msg->lastFragmentNum == 0) + return 0; + return 1; +} + +static int valid_NackFrag (NackFrag_t *msg, size_t size, int byteswap) +{ + nn_count_t *count; /* this should've preceded the bitmap */ + if (size < NACKFRAG_SIZE (0)) + /* note: sizeof(*msg) is not sufficient verification, but it does + suffice for verifying all fixed header fields exist */ + return 0; + if (byteswap) + { + bswapSN (&msg->writerSN); + bswap_fragment_number_set_hdr (&msg->fragmentNumberState); + /* bits[], count deferred until validation of fixed part */ + } + msg->readerId = nn_ntoh_entityid (msg->readerId); + msg->writerId = nn_ntoh_entityid (msg->writerId); + /* Validation following 8.3.7.1.3 + 8.3.5.5 */ + if (!valid_fragment_number_set (&msg->fragmentNumberState)) + return 0; + /* Given the number of bits, we can compute the size of the Nackfrag + submessage, and verify that the submessage is large enough */ + if (size < NACKFRAG_SIZE (msg->fragmentNumberState.numbits)) + return 0; + count = (nn_count_t *) ((char *) &msg->fragmentNumberState + + NN_FRAGMENT_NUMBER_SET_SIZE (msg->fragmentNumberState.numbits)); + if (byteswap) + { + bswap_fragment_number_set_bitmap (&msg->fragmentNumberState); + *count = bswap4 (*count); + } + return 1; +} + +static void set_sampleinfo_proxy_writer (struct nn_rsample_info *sampleinfo, nn_guid_t * pwr_guid) +{ + struct proxy_writer * pwr = ephash_lookup_proxy_writer_guid (pwr_guid); + sampleinfo->pwr = pwr; + if (pwr) + { + sampleinfo->pwr_info.guid = pwr->e.guid; + sampleinfo->pwr_info.ownership_strength = pwr->c.xqos->ownership_strength.value; + sampleinfo->pwr_info.auto_dispose = pwr->c.xqos->writer_data_lifecycle.autodispose_unregistered_instances; + sampleinfo->pwr_info.iid = pwr->e.iid; + } +} + +static int valid_Data (const struct receiver_state *rst, struct nn_rmsg *rmsg, Data_t *msg, size_t size, int byteswap, struct nn_rsample_info *sampleinfo, unsigned char **payloadp) +{ + /* on success: sampleinfo->{seq,rst,statusinfo,pt_wr_info_zoff,bswap,complex_qos} all set */ + nn_guid_t pwr_guid; + unsigned char *ptr; + + if (size < sizeof (*msg)) + return 0; /* too small even for fixed fields */ + /* D=1 && K=1 is invalid in this version of the protocol */ + if ((msg->x.smhdr.flags & (DATA_FLAG_DATAFLAG | DATA_FLAG_KEYFLAG)) == + (DATA_FLAG_DATAFLAG | DATA_FLAG_KEYFLAG)) + return 0; + if (byteswap) + { + msg->x.extraFlags = bswap2u (msg->x.extraFlags); + msg->x.octetsToInlineQos = bswap2u (msg->x.octetsToInlineQos); + bswapSN (&msg->x.writerSN); + } + msg->x.readerId = nn_ntoh_entityid (msg->x.readerId); + msg->x.writerId = nn_ntoh_entityid (msg->x.writerId); + pwr_guid.prefix = rst->src_guid_prefix; + pwr_guid.entityid = msg->x.writerId; + + sampleinfo->rst = (struct receiver_state *) rst; /* drop const */ + set_sampleinfo_proxy_writer (sampleinfo, &pwr_guid); + sampleinfo->seq = fromSN (msg->x.writerSN); + sampleinfo->fragsize = 0; /* for unfragmented data, fragsize = 0 works swell */ + + if (sampleinfo->seq <= 0 && sampleinfo->seq != NN_SEQUENCE_NUMBER_UNKNOWN) + return 0; + + if ((msg->x.smhdr.flags & (DATA_FLAG_INLINE_QOS | DATA_FLAG_DATAFLAG | DATA_FLAG_KEYFLAG)) == 0) + { + /* no QoS, no payload, so octetsToInlineQos will never be used + though one would expect octetsToInlineQos and size to be in + agreement or octetsToInlineQos to be 0 or so */ + *payloadp = NULL; + sampleinfo->size = 0; /* size is full payload size, no payload & unfragmented => size = 0 */ + sampleinfo->statusinfo = 0; + sampleinfo->pt_wr_info_zoff = NN_OFF_TO_ZOFF (0); + sampleinfo->complex_qos = 0; + return 1; + } + + /* QoS and/or payload, so octetsToInlineQos must be within the + msg; since the serialized data and serialized parameter lists + have a 4 byte header, that one, too must fit */ + if (offsetof (Data_DataFrag_common_t, octetsToInlineQos) + sizeof (msg->x.octetsToInlineQos) + msg->x.octetsToInlineQos + 4 > size) + return 0; + + ptr = (unsigned char *) msg + offsetof (Data_DataFrag_common_t, octetsToInlineQos) + sizeof (msg->x.octetsToInlineQos) + msg->x.octetsToInlineQos; + if (msg->x.smhdr.flags & DATA_FLAG_INLINE_QOS) + { + nn_plist_src_t src; + src.protocol_version = rst->protocol_version; + src.vendorid = rst->vendor; + src.encoding = (msg->x.smhdr.flags & SMFLAG_ENDIANNESS) ? PL_CDR_LE : PL_CDR_BE; + src.buf = ptr; + src.bufsz = (unsigned) ((unsigned char *) msg + size - src.buf); /* end of message, that's all we know */ + /* just a quick scan, gathering only what we _really_ need */ + if ((ptr = nn_plist_quickscan (sampleinfo, rmsg, &src)) == NULL) + return 0; + } + else + { + sampleinfo->statusinfo = 0; + sampleinfo->pt_wr_info_zoff = NN_OFF_TO_ZOFF (0); + sampleinfo->complex_qos = 0; + } + + if (!(msg->x.smhdr.flags & (DATA_FLAG_DATAFLAG | DATA_FLAG_KEYFLAG))) + { + /*TRACE (("no payload\n"));*/ + *payloadp = NULL; + sampleinfo->size = 0; + } + else if ((size_t) ((char *) ptr + 4 - (char *) msg) > size) + { + /* no space for the header */ + return 0; + } + else + { + struct CDRHeader *hdr; + sampleinfo->size = (uint32_t) ((char *) msg + size - (char *) ptr); + *payloadp = ptr; + hdr = (struct CDRHeader *) ptr; + switch (hdr->identifier) + { + case CDR_BE: + case PL_CDR_BE: + { + sampleinfo->bswap = PLATFORM_IS_LITTLE_ENDIAN ? 1 : 0; + break; + } + case CDR_LE: + case PL_CDR_LE: + { + sampleinfo->bswap = PLATFORM_IS_LITTLE_ENDIAN ? 0 : 1; + break; + } + default: + return 0; + } + } + return 1; +} + +static int valid_DataFrag (const struct receiver_state *rst, struct nn_rmsg *rmsg, DataFrag_t *msg, size_t size, int byteswap, struct nn_rsample_info *sampleinfo, unsigned char **payloadp) +{ + /* on success: sampleinfo->{rst,statusinfo,pt_wr_info_zoff,bswap,complex_qos} all set */ + const int interpret_smhdr_flags_asif_data = config.buggy_datafrag_flags_mode; + uint32_t payloadsz; + nn_guid_t pwr_guid; + unsigned char *ptr; + + if (size < sizeof (*msg)) + return 0; /* too small even for fixed fields */ + + if (interpret_smhdr_flags_asif_data) + { + /* D=1 && K=1 is invalid in this version of the protocol */ + if ((msg->x.smhdr.flags & (DATA_FLAG_DATAFLAG | DATA_FLAG_KEYFLAG)) == + (DATA_FLAG_DATAFLAG | DATA_FLAG_KEYFLAG)) + return 0; + } + + if (byteswap) + { + msg->x.extraFlags = bswap2u (msg->x.extraFlags); + msg->x.octetsToInlineQos = bswap2u (msg->x.octetsToInlineQos); + bswapSN (&msg->x.writerSN); + msg->fragmentStartingNum = bswap4u (msg->fragmentStartingNum); + msg->fragmentsInSubmessage = bswap2u (msg->fragmentsInSubmessage); + msg->fragmentSize = bswap2u (msg->fragmentSize); + msg->sampleSize = bswap4u (msg->sampleSize); + } + msg->x.readerId = nn_ntoh_entityid (msg->x.readerId); + msg->x.writerId = nn_ntoh_entityid (msg->x.writerId); + pwr_guid.prefix = rst->src_guid_prefix; + pwr_guid.entityid = msg->x.writerId; + + if (NN_STRICT_P && msg->fragmentSize <= 1024 && msg->fragmentSize < config.fragment_size) + { + /* Spec says fragments must > 1kB; not allowing 1024 bytes is IMHO + totally ridiculous; and I really don't care how small the + fragments anyway. And we're certainly not going too fail the + message if it is as least as large as the configured fragment + size. */ + return 0; + } + if (msg->fragmentSize == 0 || msg->fragmentStartingNum == 0 || msg->fragmentsInSubmessage == 0) + return 0; + if (NN_STRICT_P && msg->fragmentSize >= msg->sampleSize) + /* may not fragment if not needed -- but I don't care */ + return 0; + if ((msg->fragmentStartingNum + msg->fragmentsInSubmessage - 2) * msg->fragmentSize >= msg->sampleSize) + /* starting offset of last fragment must be within sample, note: + fragment numbers are 1-based */ + return 0; + + sampleinfo->rst = (struct receiver_state *) rst; /* drop const */ + set_sampleinfo_proxy_writer (sampleinfo, &pwr_guid); + sampleinfo->seq = fromSN (msg->x.writerSN); + sampleinfo->fragsize = msg->fragmentSize; + sampleinfo->size = msg->sampleSize; + + if (sampleinfo->seq <= 0 && sampleinfo->seq != NN_SEQUENCE_NUMBER_UNKNOWN) + return 0; + + if (interpret_smhdr_flags_asif_data) + { + if ((msg->x.smhdr.flags & (DATA_FLAG_DATAFLAG | DATA_FLAG_KEYFLAG)) == 0) + /* may not fragment if not needed => surely _some_ payload must be present! */ + return 0; + } + + /* QoS and/or payload, so octetsToInlineQos must be within the msg; + since the serialized data and serialized parameter lists have a 4 + byte header, that one, too must fit */ + if (offsetof (Data_DataFrag_common_t, octetsToInlineQos) + sizeof (msg->x.octetsToInlineQos) + msg->x.octetsToInlineQos + 4 > size) + return 0; + + /* Quick check inline QoS if present, collecting a little bit of + information on it. The only way to find the payload offset if + inline QoSs are present. */ + ptr = (unsigned char *) msg + offsetof (Data_DataFrag_common_t, octetsToInlineQos) + sizeof (msg->x.octetsToInlineQos) + msg->x.octetsToInlineQos; + if (msg->x.smhdr.flags & DATAFRAG_FLAG_INLINE_QOS) + { + nn_plist_src_t src; + src.protocol_version = rst->protocol_version; + src.vendorid = rst->vendor; + src.encoding = (msg->x.smhdr.flags & SMFLAG_ENDIANNESS) ? PL_CDR_LE : PL_CDR_BE; + src.buf = ptr; + src.bufsz = (unsigned) ((unsigned char *) msg + size - src.buf); /* end of message, that's all we know */ + /* just a quick scan, gathering only what we _really_ need */ + if ((ptr = nn_plist_quickscan (sampleinfo, rmsg, &src)) == NULL) + return 0; + } + else + { + sampleinfo->statusinfo = 0; + sampleinfo->pt_wr_info_zoff = NN_OFF_TO_ZOFF (0); + sampleinfo->complex_qos = 0; + } + + *payloadp = ptr; + payloadsz = (uint32_t) ((char *) msg + size - (char *) ptr); + if ((uint32_t) msg->fragmentsInSubmessage * msg->fragmentSize <= payloadsz) + ; /* all spec'd fragments fit in payload */ + else if ((uint32_t) (msg->fragmentsInSubmessage - 1) * msg->fragmentSize >= payloadsz) + return 0; /* I can live with a short final fragment, but _only_ the final one */ + else if ((uint32_t) (msg->fragmentStartingNum - 1) * msg->fragmentSize + payloadsz >= msg->sampleSize) + ; /* final fragment is long enough to cover rest of sample */ + else + return 0; + if (msg->fragmentStartingNum == 1) + { + struct CDRHeader *hdr = (struct CDRHeader *) ptr; + if ((size_t) ((char *) ptr + 4 - (char *) msg) > size) + { + /* no space for the header -- technically, allowing small + fragments would also mean allowing a partial header, but I + prefer this */ + return 0; + } + switch (hdr->identifier) + { + case CDR_BE: + case PL_CDR_BE: + { + sampleinfo->bswap = PLATFORM_IS_LITTLE_ENDIAN ? 1 : 0; + break; + } + case CDR_LE: + case PL_CDR_LE: + { + sampleinfo->bswap = PLATFORM_IS_LITTLE_ENDIAN ? 0 : 1; + break; + } + default: + return 0; + } + } + return 1; +} + +static int add_Gap (struct nn_xmsg *msg, struct writer *wr, struct proxy_reader *prd, seqno_t start, seqno_t base, unsigned numbits, const unsigned *bits) +{ + struct nn_xmsg_marker sm_marker; + Gap_t *gap; + ASSERT_MUTEX_HELD (wr->e.lock); + assert (numbits > 0); + gap = nn_xmsg_append (msg, &sm_marker, GAP_SIZE (numbits)); + nn_xmsg_submsg_init (msg, sm_marker, SMID_GAP); + gap->readerId = nn_hton_entityid (prd->e.guid.entityid); + gap->writerId = nn_hton_entityid (wr->e.guid.entityid); + gap->gapStart = toSN (start); + gap->gapList.bitmap_base = toSN (base); + gap->gapList.numbits = numbits; + memcpy (gap->gapList.bits, bits, NN_SEQUENCE_NUMBER_SET_BITS_SIZE (numbits)); + nn_xmsg_submsg_setnext (msg, sm_marker); + return 0; +} + +static void force_heartbeat_to_peer (struct writer *wr, struct proxy_reader *prd, int hbansreq) +{ + struct nn_xmsg *m; + + ASSERT_MUTEX_HELD (&wr->e.lock); + assert (wr->reliable); + + m = nn_xmsg_new (gv.xmsgpool, &wr->e.guid.prefix, 0, NN_XMSG_KIND_CONTROL); + if (nn_xmsg_setdstPRD (m, prd) < 0) + { + /* If we don't have an address, give up immediately */ + nn_xmsg_free (m); + return; + } + + if (whc_empty (wr->whc) && !config.respond_to_rti_init_zero_ack_with_invalid_heartbeat) + { + /* If WHC is empty, we send a Gap combined with a Heartbeat. The + Gap reuses the latest sequence number (or consumes a new one if + the writer hasn't sent anything yet), therefore for the reader + it is as-if a Data submessage had once been sent with that + sequence number and it now receives an unsollicited response to + a NACK ... */ + unsigned bits = 0; + seqno_t seq; + if (wr->seq > 0) + seq = wr->seq; + else + { + /* never sent anything, pretend we did */ + seq = wr->seq = 1; + UPDATE_SEQ_XMIT_LOCKED(wr, 1); + } + add_Gap (m, wr, prd, seq, seq+1, 1, &bits); + add_Heartbeat (m, wr, hbansreq, prd->e.guid.entityid, 1); + TRACE (("force_heartbeat_to_peer: %x:%x:%x:%x -> %x:%x:%x:%x - whc empty, queueing gap #%"PRId64" + heartbeat for transmit\n", + PGUID (wr->e.guid), PGUID (prd->e.guid), seq)); + } + else + { + /* Send a Heartbeat just to this peer */ + add_Heartbeat (m, wr, hbansreq, prd->e.guid.entityid, 0); + TRACE (("force_heartbeat_to_peer: %x:%x:%x:%x -> %x:%x:%x:%x - queue for transmit\n", + PGUID (wr->e.guid), PGUID (prd->e.guid))); + } + qxev_msg (wr->evq, m); +} + +static seqno_t grow_gap_to_next_seq (const struct writer *wr, seqno_t seq) +{ + seqno_t next_seq = whc_next_seq (wr->whc, seq - 1); + seqno_t seq_xmit = READ_SEQ_XMIT(wr); + if (next_seq == MAX_SEQ_NUMBER) /* no next sample */ + return seq_xmit + 1; + else if (next_seq > seq_xmit) /* next is beyond last actually transmitted */ + return seq_xmit; + else /* next one is already visible in the outside world */ + return next_seq; +} + +static int acknack_is_nack (const AckNack_t *msg) +{ + unsigned x = 0, mask; + int i; + if (msg->readerSNState.numbits == 0) + /* Disallowed by the spec, but RTI appears to require them (and so + even we generate them) */ + return 0; + for (i = 0; i < (int) NN_SEQUENCE_NUMBER_SET_BITS_SIZE (msg->readerSNState.numbits) / 4 - 1; i++) + x |= msg->readerSNState.bits[i]; + if ((msg->readerSNState.numbits % 32) == 0) + mask = ~0u; + else + mask = ~(~0u >> (msg->readerSNState.numbits % 32)); + x |= msg->readerSNState.bits[i] & mask; + return x != 0; +} + +static int accept_ack_or_hb_w_timeout (nn_count_t new_count, nn_count_t *exp_count, nn_etime_t tnow, nn_etime_t *t_last_accepted, int force_accept) +{ + /* AckNacks and Heartbeats with a sequence number (called "count" + for some reason) equal to or less than the highest one received + so far must be dropped. However, we provide an override + (force_accept) for pre-emptive acks and we accept ones regardless + of the sequence number after a few seconds. + + This allows continuing after an asymmetrical disconnection if the + re-connecting side jumps back in its sequence numbering. DDSI2.1 + 8.4.15.7 says: "New HEARTBEATS should have Counts greater than + all older HEARTBEATs. Then, received HEARTBEATs with Counts not + greater than any previously received can be ignored." But it + isn't clear whether that is about connections or entities, and + besides there is an issue with the wrap around after 2**31-1. + + This combined procedure should give the best of all worlds, and + is not more expensive in the common case. */ + const int64_t timeout = 2 * T_SECOND; + + if (new_count < *exp_count && tnow.v - t_last_accepted->v < timeout && !force_accept) + return 0; + + *exp_count = new_count + 1; + *t_last_accepted = tnow; + return 1; +} + +static int handle_AckNack (struct receiver_state *rst, nn_etime_t tnow, const AckNack_t *msg, nn_ddsi_time_t timestamp) +{ + struct proxy_reader *prd; + struct wr_prd_match *rn; + struct writer *wr; + nn_guid_t src, dst; + seqno_t seqbase; + seqno_t seq_xmit; + nn_count_t *countp; + seqno_t gapstart = -1, gapend = -1; + unsigned gapnumbits = 0; + unsigned gapbits[256 / 32]; + int accelerate_rexmit = 0; + int is_pure_ack; + int is_pure_nonhist_ack; + int is_preemptive_ack; + int enqueued; + unsigned numbits; + uint32_t msgs_sent, msgs_lost; + seqno_t max_seq_in_reply; + struct whc_node *deferred_free_list = NULL; + unsigned i; + int hb_sent_in_response = 0; + memset (gapbits, 0, sizeof (gapbits)); + countp = (nn_count_t *) ((char *) msg + offsetof (AckNack_t, readerSNState) + + NN_SEQUENCE_NUMBER_SET_SIZE (msg->readerSNState.numbits)); + src.prefix = rst->src_guid_prefix; + src.entityid = msg->readerId; + dst.prefix = rst->dst_guid_prefix; + dst.entityid = msg->writerId; + TRACE (("ACKNACK(%s#%d:%"PRId64"/%u:", msg->smhdr.flags & ACKNACK_FLAG_FINAL ? "F" : "", + *countp, fromSN (msg->readerSNState.bitmap_base), msg->readerSNState.numbits)); + for (i = 0; i < msg->readerSNState.numbits; i++) + TRACE (("%c", nn_bitset_isset (msg->readerSNState.numbits, msg->readerSNState.bits, i) ? '1' : '0')); + seqbase = fromSN (msg->readerSNState.bitmap_base); + + if (!rst->forme) + { + TRACE ((" %x:%x:%x:%x -> %x:%x:%x:%x not-for-me)", PGUID (src), PGUID (dst))); + return 1; + } + + if ((wr = ephash_lookup_writer_guid (&dst)) == NULL) + { + TRACE ((" %x:%x:%x:%x -> %x:%x:%x:%x?)", PGUID (src), PGUID (dst))); + return 1; + } + /* Always look up the proxy reader -- even though we don't need for + the normal pure ack steady state. If (a big "if"!) this shows up + as a significant portion of the time, we can always rewrite it to + only retrieve it when needed. */ + if ((prd = ephash_lookup_proxy_reader_guid (&src)) == NULL) + { + TRACE ((" %x:%x:%x:%x? -> %x:%x:%x:%x)", PGUID (src), PGUID (dst))); + return 1; + } + + /* liveliness is still only implemented partially (with all set to AUTOMATIC, BY_PARTICIPANT, &c.), so we simply renew the proxy participant's lease. */ + if (prd->assert_pp_lease) + lease_renew (os_atomic_ldvoidp (&prd->c.proxypp->lease), tnow); + + if (!wr->reliable) /* note: reliability can't be changed */ + { + TRACE ((" %x:%x:%x:%x -> %x:%x:%x:%x not a reliable writer!)", PGUID (src), PGUID (dst))); + return 1; + } + + os_mutexLock (&wr->e.lock); + if ((rn = ut_avlLookup (&wr_readers_treedef, &wr->readers, &src)) == NULL) + { + TRACE ((" %x:%x:%x:%x -> %x:%x:%x:%x not a connection)", PGUID (src), PGUID (dst))); + goto out; + } + + /* is_pure_nonhist ack differs from is_pure_ack in that it doesn't + get set when only historical data is being acked, which is + relevant to setting "has_replied_to_hb" and "assumed_in_sync". */ + is_pure_ack = !acknack_is_nack (msg); + is_pure_nonhist_ack = is_pure_ack && seqbase - 1 >= rn->seq; + is_preemptive_ack = seqbase <= 1 && is_pure_ack; + + wr->num_acks_received++; + if (!is_pure_ack) + { + wr->num_nacks_received++; + rn->rexmit_requests++; + } + + if (!accept_ack_or_hb_w_timeout (*countp, &rn->next_acknack, tnow, &rn->t_acknack_accepted, is_preemptive_ack)) + { + TRACE ((" [%x:%x:%x:%x -> %x:%x:%x:%x])", PGUID (src), PGUID (dst))); + goto out; + } + TRACE ((" %x:%x:%x:%x -> %x:%x:%x:%x", PGUID (src), PGUID (dst))); + + /* Update latency estimates if we have a timestamp -- won't actually + work so well if the timestamp can be a left over from some other + submessage -- but then, it is no more than a quick hack at the + moment. */ + if (config.meas_hb_to_ack_latency && valid_ddsi_timestamp (timestamp)) + { + nn_wctime_t tstamp_now = now (); + nn_wctime_t tstamp_msg = nn_wctime_from_ddsi_time (timestamp); + nn_lat_estim_update (&rn->hb_to_ack_latency, tstamp_now.v - tstamp_msg.v); + if ((config.enabled_logcats & (LC_TRACE | LC_INFO)) && + tstamp_now.v > rn->hb_to_ack_latency_tlastlog.v + 10 * T_SECOND) + { + if (config.enabled_logcats & LC_TRACE) + nn_lat_estim_log (LC_TRACE, NULL, &rn->hb_to_ack_latency); + else if (config.enabled_logcats & LC_INFO) + { + char tagbuf[2*(4*8+3) + 4 + 1]; + (void) snprintf (tagbuf, sizeof (tagbuf), "%x:%x:%x:%x -> %x:%x:%x:%x", PGUID (src), PGUID (dst)); + if (nn_lat_estim_log (LC_INFO, tagbuf, &rn->hb_to_ack_latency)) + nn_log (LC_INFO, "\n"); + } + rn->hb_to_ack_latency_tlastlog = tstamp_now; + } + } + + /* First, the ACK part: if the AckNack advances the highest sequence + number ack'd by the remote reader, update state & try dropping + some messages */ + if (seqbase - 1 > rn->seq) + { + int64_t n_ack = (seqbase - 1) - rn->seq; + unsigned n; + rn->seq = seqbase - 1; + if (rn->seq > wr->seq) { + /* Prevent a reader from ACKing future samples (is only malicious because we require + that rn->seq <= wr->seq) */ + rn->seq = wr->seq; + } + ut_avlAugmentUpdate (&wr_readers_treedef, rn); + n = remove_acked_messages (wr, &deferred_free_list); + TRACE ((" ACK%"PRId64" RM%u", n_ack, n)); + } + + /* If this reader was marked as "non-responsive" in the past, it's now responding again, + so update its status */ + if (rn->seq == MAX_SEQ_NUMBER && prd->c.xqos->reliability.kind == NN_RELIABLE_RELIABILITY_QOS) + { + seqno_t oldest_seq; + if (whc_empty (wr->whc)) + oldest_seq = wr->seq; + else + oldest_seq = whc_max_seq (wr->whc); + rn->has_replied_to_hb = 1; /* was temporarily cleared to ensure heartbeats went out */ + rn->seq = seqbase - 1; + if (oldest_seq > rn->seq) { + /* Prevent a malicious reader from lowering the min. sequence number retained in the WHC. */ + rn->seq = oldest_seq; + } + if (rn->seq > wr->seq) { + /* Prevent a reader from ACKing future samples (is only malicious because we require + that rn->seq <= wr->seq) */ + rn->seq = wr->seq; + } + ut_avlAugmentUpdate (&wr_readers_treedef, rn); + NN_WARNING ("writer %x:%x:%x:%x considering reader %x:%x:%x:%x responsive again\n", PGUID (wr->e.guid), PGUID (rn->prd_guid)); + } + + /* Second, the NACK bits (literally, that is). To do so, attempt to + classify the AckNack for reverse-engineered compatibility with + RTI's invalid acks and sometimes slightly odd behaviour. */ + numbits = msg->readerSNState.numbits; + msgs_sent = 0; + msgs_lost = 0; + max_seq_in_reply = 0; + if (!rn->has_replied_to_hb && seqbase > 1 && is_pure_nonhist_ack) + { + TRACE ((" setting-has-replied-to-hb")); + rn->has_replied_to_hb = 1; + /* walk the whole tree to ensure all proxy readers for this writer + have their unack'ed info updated */ + ut_avlAugmentUpdate (&wr_readers_treedef, rn); + } + if (is_preemptive_ack) + { + /* Pre-emptive nack: RTI uses (seqbase = 0, numbits = 0), we use + (seqbase = 1, numbits = 1, bits = {0}). Seqbase <= 1 and not a + NACK covers both and is otherwise a useless message. Sent on + reader start-up and we respond with a heartbeat and, if we have + data in our WHC, we start sending it regardless of whether the + remote reader asked for it */ + TRACE ((" preemptive-nack")); + if (whc_empty (wr->whc)) + { + TRACE ((" whc-empty ")); + force_heartbeat_to_peer (wr, prd, 0); + hb_sent_in_response = 1; + } + else + { + TRACE ((" rebase ")); + force_heartbeat_to_peer (wr, prd, 0); + hb_sent_in_response = 1; + numbits = config.accelerate_rexmit_block_size; + seqbase = whc_min_seq (wr->whc); + } + } + else if (!rn->assumed_in_sync) + { + /* We assume a remote reader that hasn't ever sent a pure Ack -- + an AckNack that doesn't NACK a thing -- is still trying to + catch up, so we try to accelerate its attempts at catching up + by a configurable amount. FIXME: what about a pulling reader? + that doesn't play too nicely with this. */ + if (is_pure_nonhist_ack) + { + TRACE ((" happy-now")); + rn->assumed_in_sync = 1; + } + else if (msg->readerSNState.numbits < config.accelerate_rexmit_block_size) + { + TRACE ((" accelerating")); + accelerate_rexmit = 1; + if (accelerate_rexmit && numbits < config.accelerate_rexmit_block_size) + numbits = config.accelerate_rexmit_block_size; + } + else + { + TRACE ((" complying")); + } + } + /* Retransmit requested messages, including whatever we decided to + retransmit that the remote reader didn't ask for. While doing so, + note any gaps in the sequence: if there are some, we transmit a + Gap message as well. + + Note: ignoring retransmit requests for samples beyond the one we + last transmitted, even though we may have more available. If it + hasn't been transmitted ever, the initial transmit should solve + that issue; if it has, then the timing is terribly unlucky, but + a future request'll fix it. */ + enqueued = 1; + seq_xmit = READ_SEQ_XMIT(wr); + for (i = 0; i < numbits && seqbase + i <= seq_xmit && enqueued; i++) + { + /* Accelerated schedule may run ahead of sequence number set + contained in the acknack, and assumes all messages beyond the + set are NACK'd -- don't feel like tracking where exactly we + left off ... */ + if (i >= msg->readerSNState.numbits || nn_bitset_isset (numbits, msg->readerSNState.bits, i)) + { + seqno_t seq = seqbase + i; + struct whc_node *whcn; + if ((whcn = whc_findseq (wr->whc, seq)) != NULL) + { + if (!wr->retransmitting && whcn->unacked) + writer_set_retransmitting (wr); + + if (config.retransmit_merging != REXMIT_MERGE_NEVER && rn->assumed_in_sync) + { + /* send retransmit to all receivers, but skip if recently done */ + nn_mtime_t tstamp = now_mt (); + if (tstamp.v > whcn->last_rexmit_ts.v + config.retransmit_merging_period) + { + TRACE ((" RX%"PRId64, seqbase + i)); + enqueued = (enqueue_sample_wrlock_held (wr, seq, whcn->plist, whcn->serdata, NULL, 0) >= 0); + if (enqueued) + { + max_seq_in_reply = seqbase + i; + msgs_sent++; + whcn->last_rexmit_ts = tstamp; + } + } + else + { + TRACE ((" RX%"PRId64" (merged)", seqbase + i)); + } + } + else + { + /* no merging, send directed retransmit */ + TRACE ((" RX%"PRId64"", seqbase + i)); + enqueued = (enqueue_sample_wrlock_held (wr, seq, whcn->plist, whcn->serdata, prd, 0) >= 0); + if (enqueued) + { + max_seq_in_reply = seqbase + i; + msgs_sent++; + whcn->rexmit_count++; + } + } + } + else if (gapstart == -1) + { + TRACE ((" M%"PRId64, seqbase + i)); + gapstart = seqbase + i; + gapend = gapstart + 1; + msgs_lost++; + } + else if (seqbase + i == gapend) + { + TRACE ((" M%"PRId64, seqbase + i)); + gapend = seqbase + i + 1; + msgs_lost++; + } + else if (seqbase + i - gapend < 256) + { + unsigned idx = (unsigned) (seqbase + i - gapend); + TRACE ((" M%"PRId64, seqbase + i)); + gapnumbits = idx + 1; + nn_bitset_set (gapnumbits, gapbits, idx); + msgs_lost++; + } + } + } + if (!enqueued) + TRACE ((" rexmit-limit-hit")); + /* Generate a Gap message if some of the sequence is missing */ + if (gapstart > 0) + { + struct nn_xmsg *m; + if (gapend == seqbase + msg->readerSNState.numbits) + { + /* We automatically grow a gap as far as we can -- can't + retransmit those messages anyway, so no need for round-trip + to the remote reader. */ + gapend = grow_gap_to_next_seq (wr, gapend); + } + if (gapnumbits == 0) + { + /* Avoid sending an invalid bitset */ + gapnumbits = 1; + nn_bitset_set (gapnumbits, gapbits, 0); + gapend--; + } + /* The non-bitmap part of a gap message says everything <= + gapend-1 is no more (so the maximum sequence number it informs + the peer of is gapend-1); each bit adds one sequence number to + that. */ + if (gapend-1 + gapnumbits > max_seq_in_reply) + max_seq_in_reply = gapend-1 + gapnumbits; + TRACE ((" XGAP%"PRId64"..%"PRId64"/%u:", gapstart, gapend, gapnumbits)); + for (i = 0; i < gapnumbits; i++) + TRACE (("%c", nn_bitset_isset (gapnumbits, gapbits, i) ? '1' : '0')); + m = nn_xmsg_new (gv.xmsgpool, &wr->e.guid.prefix, 0, NN_XMSG_KIND_CONTROL); +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + nn_xmsg_setencoderid (m, wr->partition_id); +#endif + if (nn_xmsg_setdstPRD (m, prd) < 0) + nn_xmsg_free (m); + else + { + add_Gap (m, wr, prd, gapstart, gapend, gapnumbits, gapbits); + qxev_msg (wr->evq, m); + } + msgs_sent++; + } + wr->rexmit_count += msgs_sent; + wr->rexmit_lost_count += msgs_lost; + /* If rexmits and/or a gap message were sent, and if the last + sequence number that we're informing the NACK'ing peer about is + less than the last sequence number transmitted by the writer, + tell the peer to acknowledge quickly. Not sure if that helps, but + it might ... [NB writer->seq is the last msg sent so far] */ + if (msgs_sent && max_seq_in_reply < seq_xmit) + { + TRACE ((" rexmit#%"PRIu32" maxseq:%"PRId64"<%"PRId64"<=%"PRId64"", msgs_sent, max_seq_in_reply, seq_xmit, wr->seq)); + force_heartbeat_to_peer (wr, prd, 1); + hb_sent_in_response = 1; + + /* The primary purpose of hbcontrol_note_asyncwrite is to ensure + heartbeats will go out at the "normal" rate again, instead of a + gradually lowering rate. If we just got a request for a + retransmit, and there is more to be retransmitted, surely the + rate should be kept up for now */ + writer_hbcontrol_note_asyncwrite (wr, now_mt ()); + } + /* If "final" flag not set, we must respond with a heartbeat. Do it + now if we haven't done so already */ + if (!(msg->smhdr.flags & ACKNACK_FLAG_FINAL) && !hb_sent_in_response) + force_heartbeat_to_peer (wr, prd, 0); + TRACE ((")")); + out: + os_mutexUnlock (&wr->e.lock); + whc_free_deferred_free_list (wr->whc, deferred_free_list); + return 1; +} + +static void handle_forall_destinations (const nn_guid_t *dst, struct proxy_writer *pwr, ut_avlWalk_t fun, void *arg) +{ + /* prefix: id: to: + 0 0 all matched readers + 0 !=0 all matched readers with entityid id + !=0 0 to all matched readers in addressed participant + !=0 !=0 to the one addressed reader + */ + const int haveprefix = + !(dst->prefix.u[0] == 0 && dst->prefix.u[1] == 0 && dst->prefix.u[2] == 0); + const int haveid = !(dst->entityid.u == NN_ENTITYID_UNKNOWN); + + /* must have pwr->e.lock held for safely iterating over readers */ + ASSERT_MUTEX_HELD (&pwr->e.lock); + + switch ((haveprefix << 1) | haveid) + { + case (0 << 1) | 0: /* all: full treewalk */ + ut_avlWalk (&pwr_readers_treedef, &pwr->readers, fun, arg); + break; + case (0 << 1) | 1: /* all with correct entityid: special filtering treewalk */ + { + struct pwr_rd_match *wn; + for (wn = ut_avlFindMin (&pwr_readers_treedef, &pwr->readers); wn; wn = ut_avlFindSucc (&pwr_readers_treedef, &pwr->readers, wn)) + { + if (wn->rd_guid.entityid.u == dst->entityid.u) + fun (wn, arg); + } + } + break; + case (1 << 1) | 0: /* all within one participant: walk a range of keyvalues */ + { + nn_guid_t a, b; + a = *dst; a.entityid.u = 0; + b = *dst; b.entityid.u = ~0u; + ut_avlWalkRange (&pwr_readers_treedef, &pwr->readers, &a, &b, fun, arg); + } + break; + case (1 << 1) | 1: /* fully addressed: dst should exist (but for removal) */ + { + struct pwr_rd_match *wn; + if ((wn = ut_avlLookup (&pwr_readers_treedef, &pwr->readers, dst)) != NULL) + fun (wn, arg); + } + break; + } +} + +struct handle_Heartbeat_helper_arg { + struct receiver_state *rst; + const Heartbeat_t *msg; + struct proxy_writer *pwr; + nn_ddsi_time_t timestamp; + nn_etime_t tnow; + nn_mtime_t tnow_mt; +}; + +static void handle_Heartbeat_helper (struct pwr_rd_match * const wn, struct handle_Heartbeat_helper_arg * const arg) +{ + Heartbeat_t const * const msg = arg->msg; + struct proxy_writer * const pwr = arg->pwr; + seqno_t refseq; + + ASSERT_MUTEX_HELD (&pwr->e.lock); + + /* Not supposed to respond to repeats and old heartbeats. */ + if (!accept_ack_or_hb_w_timeout (msg->count, &wn->next_heartbeat, arg->tnow, &wn->t_heartbeat_accepted, 0)) + { + TRACE ((" (%x:%x:%x:%x)", PGUID (wn->rd_guid))); + return; + } + + /* Reference sequence number for determining whether or not to + Ack/Nack unfortunately depends on whether the reader is in + sync. */ + if (wn->in_sync != PRMSS_OUT_OF_SYNC) + refseq = nn_reorder_next_seq (pwr->reorder) - 1; + else + refseq = nn_reorder_next_seq (wn->u.not_in_sync.reorder) - 1; + TRACE ((" %x:%x:%x:%x@%"PRId64"%s", PGUID (wn->rd_guid), refseq, (wn->in_sync == PRMSS_SYNC) ? "(sync)" : (wn->in_sync == PRMSS_TLCATCHUP) ? "(tlcatchup)" : "")); + + /* Reschedule AckNack transmit if deemed appropriate; unreliable + readers have acknack_xevent == NULL and can't do this. + + There is no real need to send a nack from each reader that is in + sync -- indeed, we could simply ignore the destination address in + the messages we receive and only ever nack each sequence number + once, regardless of which readers care about it. */ + if (wn->acknack_xevent) + { + nn_mtime_t tsched; + tsched.v = T_NEVER; + if (pwr->last_seq > refseq) + { + TRACE (("/NAK")); + if (arg->tnow_mt.v >= wn->t_last_nack.v + config.nack_delay || refseq >= wn->seq_last_nack) + tsched = arg->tnow_mt; + else + { + tsched.v = arg->tnow_mt.v + config.nack_delay; + TRACE (("d")); + } + } + else if (!(msg->smhdr.flags & HEARTBEAT_FLAG_FINAL)) + { + tsched = arg->tnow_mt; + } + if (resched_xevent_if_earlier (wn->acknack_xevent, tsched)) + { + if (config.meas_hb_to_ack_latency && valid_ddsi_timestamp (arg->timestamp)) + wn->hb_timestamp = nn_wctime_from_ddsi_time (arg->timestamp); + } + } +} + +static int handle_Heartbeat (struct receiver_state *rst, nn_etime_t tnow, struct nn_rmsg *rmsg, const Heartbeat_t *msg, nn_ddsi_time_t timestamp) +{ + /* We now cheat: and process the heartbeat for _all_ readers, + always, regardless of the destination address in the Heartbeat + sub-message. This is to take care of the samples with sequence + numbers that become deliverable because of the heartbeat. + + We do play by the book with respect to generating AckNacks in + response -- done by handle_Heartbeat_helper. + + A heartbeat that states [a,b] is the smallest interval in which + the range of available sequence numbers is is interpreted here as + a gap [1,a). See also handle_Gap. */ + const seqno_t firstseq = fromSN (msg->firstSN); + struct handle_Heartbeat_helper_arg arg; + struct proxy_writer *pwr; + nn_guid_t src, dst; + + src.prefix = rst->src_guid_prefix; + src.entityid = msg->writerId; + dst.prefix = rst->dst_guid_prefix; + dst.entityid = msg->readerId; + + TRACE (("HEARTBEAT(%s#%d:%"PRId64"..%"PRId64" ", msg->smhdr.flags & HEARTBEAT_FLAG_FINAL ? "F" : "", + msg->count, firstseq, fromSN (msg->lastSN))); + + if (!rst->forme) + { + TRACE (("%x:%x:%x:%x -> %x:%x:%x:%x not-for-me)", PGUID (src), PGUID (dst))); + return 1; + } + + if ((pwr = ephash_lookup_proxy_writer_guid (&src)) == NULL) + { + TRACE (("%x:%x:%x:%x? -> %x:%x:%x:%x)", PGUID (src), PGUID (dst))); + return 1; + } + + /* liveliness is still only implemented partially (with all set to AUTOMATIC, BY_PARTICIPANT, &c.), so we simply renew the proxy participant's lease. */ + if (pwr->assert_pp_lease) + lease_renew (os_atomic_ldvoidp (&pwr->c.proxypp->lease), tnow); + + TRACE (("%x:%x:%x:%x -> %x:%x:%x:%x:", PGUID (src), PGUID (dst))); + + os_mutexLock (&pwr->e.lock); + + pwr->have_seen_heartbeat = 1; + if (fromSN (msg->lastSN) > pwr->last_seq) + { + pwr->last_seq = fromSN (msg->lastSN); + pwr->last_fragnum = ~0u; + pwr->last_fragnum_reset = 0; + } + else if (pwr->last_fragnum != ~0u && fromSN (msg->lastSN) == pwr->last_seq) + { + if (!pwr->last_fragnum_reset) + pwr->last_fragnum_reset = 1; + else + { + pwr->last_fragnum = ~0u; + pwr->last_fragnum_reset = 0; + } + } + + nn_defrag_notegap (pwr->defrag, 1, firstseq); + + { + struct nn_rdata *gap; + struct pwr_rd_match *wn; + struct nn_rsample_chain sc; + int refc_adjust = 0; + nn_reorder_result_t res; + gap = nn_rdata_newgap (rmsg); + if ((res = nn_reorder_gap (&sc, pwr->reorder, gap, 1, firstseq, &refc_adjust)) > 0) + { + if (pwr->deliver_synchronously) + deliver_user_data_synchronously (&sc); + else + nn_dqueue_enqueue (pwr->dqueue, &sc, res); + } + for (wn = ut_avlFindMin (&pwr_readers_treedef, &pwr->readers); wn; wn = ut_avlFindSucc (&pwr_readers_treedef, &pwr->readers, wn)) + if (wn->in_sync != PRMSS_SYNC) + { + seqno_t last_deliv_seq = 0; + switch (wn->in_sync) + { + case PRMSS_SYNC: + assert(0); + break; + case PRMSS_TLCATCHUP: + last_deliv_seq = nn_reorder_next_seq (pwr->reorder) - 1; + break; + case PRMSS_OUT_OF_SYNC: { + struct nn_reorder *ro = wn->u.not_in_sync.reorder; + if ((res = nn_reorder_gap (&sc, ro, gap, 1, firstseq, &refc_adjust)) > 0) + nn_dqueue_enqueue1 (pwr->dqueue, &wn->rd_guid, &sc, res); + last_deliv_seq = nn_reorder_next_seq (wn->u.not_in_sync.reorder) - 1; + } + } + if (wn->u.not_in_sync.end_of_tl_seq == MAX_SEQ_NUMBER) + { + wn->u.not_in_sync.end_of_tl_seq = fromSN (msg->lastSN); + TRACE ((" end-of-tl-seq(rd %x:%x:%x:%x #%"PRId64")", PGUID(wn->rd_guid), wn->u.not_in_sync.end_of_tl_seq)); + } + maybe_set_reader_in_sync (pwr, wn, last_deliv_seq); + } + nn_fragchain_adjust_refcount (gap, refc_adjust); + } + + arg.rst = rst; + arg.msg = msg; + arg.pwr = pwr; + arg.timestamp = timestamp; + arg.tnow = tnow; + arg.tnow_mt = now_mt (); + handle_forall_destinations (&dst, pwr, (ut_avlWalk_t) handle_Heartbeat_helper, &arg); + TRACE ((")")); + + os_mutexUnlock (&pwr->e.lock); + return 1; +} + +static int handle_HeartbeatFrag (struct receiver_state *rst, UNUSED_ARG(nn_etime_t tnow), const HeartbeatFrag_t *msg) +{ + const seqno_t seq = fromSN (msg->writerSN); + const nn_fragment_number_t fragnum = msg->lastFragmentNum - 1; /* we do 0-based */ + nn_guid_t src, dst; + struct proxy_writer *pwr; + + src.prefix = rst->src_guid_prefix; + src.entityid = msg->writerId; + dst.prefix = rst->dst_guid_prefix; + dst.entityid = msg->readerId; + + TRACE (("HEARTBEATFRAG(#%d:%"PRId64"/[1,%u]", msg->count, seq, fragnum+1)); + if (!rst->forme) + { + TRACE ((" %x:%x:%x:%x -> %x:%x:%x:%x not-for-me)", PGUID (src), PGUID (dst))); + return 1; + } + + if ((pwr = ephash_lookup_proxy_writer_guid (&src)) == NULL) + { + TRACE ((" %x:%x:%x:%x? -> %x:%x:%x:%x)", PGUID (src), PGUID (dst))); + return 1; + } + + /* liveliness is still only implemented partially (with all set to AUTOMATIC, BY_PARTICIPANT, &c.), so we simply renew the proxy participant's lease. */ + if (pwr->assert_pp_lease) + lease_renew (os_atomic_ldvoidp (&pwr->c.proxypp->lease), tnow); + + TRACE ((" %x:%x:%x:%x -> %x:%x:%x:%x", PGUID (src), PGUID (dst))); + os_mutexLock (&pwr->e.lock); + + if (seq > pwr->last_seq) + { + pwr->last_seq = seq; + pwr->last_fragnum = fragnum; + pwr->last_fragnum_reset = 0; + } + else if (seq == pwr->last_seq && fragnum > pwr->last_fragnum) + { + pwr->last_fragnum = fragnum; + pwr->last_fragnum_reset = 0; + } + + /* Defragmenting happens at the proxy writer, readers have nothing + to do with it. Here we immediately respond with a NackFrag if we + discover a missing fragment, which differs significantly from + handle_Heartbeat's scheduling of an AckNack event when it must + respond. Why? Just because. */ + if (ut_avlIsEmpty (&pwr->readers) || pwr->local_matching_inprogress) + TRACE ((" no readers")); + else + { + struct pwr_rd_match *m = NULL; + + if (nn_reorder_wantsample (pwr->reorder, seq)) + { + /* Pick an arbitrary reliable reader's guid for the response -- + assuming a reliable writer -> unreliable reader is rare, and + so scanning the readers is acceptable if the first guess + fails */ + m = ut_avlRootNonEmpty (&pwr_readers_treedef, &pwr->readers); + if (m->acknack_xevent == NULL) + { + m = ut_avlFindMin (&pwr_readers_treedef, &pwr->readers); + while (m && m->acknack_xevent == NULL) + m = ut_avlFindSucc (&pwr_readers_treedef, &pwr->readers, m); + } + } + else if (seq < nn_reorder_next_seq (pwr->reorder)) + { + /* Check out-of-sync readers -- should add a bit to cheaply test + whether there are any (usually there aren't) */ + m = ut_avlFindMin (&pwr_readers_treedef, &pwr->readers); + while (m) + { + if ((m->in_sync == PRMSS_OUT_OF_SYNC) && m->acknack_xevent != NULL && nn_reorder_wantsample (m->u.not_in_sync.reorder, seq)) + { + /* If reader is out-of-sync, and reader is realiable, and + reader still wants this particular sample, then use this + reader to decide which fragments to nack */ + break; + } + m = ut_avlFindSucc (&pwr_readers_treedef, &pwr->readers, m); + } + } + + if (m == NULL) + TRACE ((" no interested reliable readers")); + else + { + /* Check if we are missing something */ + union { + struct nn_fragment_number_set set; + char pad[NN_FRAGMENT_NUMBER_SET_SIZE (256)]; + } nackfrag; + if (nn_defrag_nackmap (pwr->defrag, seq, fragnum, &nackfrag.set, 256) > 0) + { + /* Yes we are (note that this potentially also happens for + samples we no longer care about) */ + int64_t delay = config.nack_delay; + TRACE (("/nackfrag")); + resched_xevent_if_earlier (m->acknack_xevent, add_duration_to_mtime (now_mt(), delay)); + } + } + } + TRACE ((")")); + os_mutexUnlock (&pwr->e.lock); + return 1; +} + +static int handle_NackFrag (struct receiver_state *rst, nn_etime_t tnow, const NackFrag_t *msg) +{ + struct proxy_reader *prd; + struct wr_prd_match *rn; + struct writer *wr; + struct whc_node *whcn; + nn_guid_t src, dst; + nn_count_t *countp; + seqno_t seq = fromSN (msg->writerSN); + unsigned i; + + countp = (nn_count_t *) ((char *) msg + offsetof (NackFrag_t, fragmentNumberState) + NN_FRAGMENT_NUMBER_SET_SIZE (msg->fragmentNumberState.numbits)); + src.prefix = rst->src_guid_prefix; + src.entityid = msg->readerId; + dst.prefix = rst->dst_guid_prefix; + dst.entityid = msg->writerId; + + TRACE (("NACKFRAG(#%d:%"PRId64"/%u/%u:", *countp, seq, msg->fragmentNumberState.bitmap_base, msg->fragmentNumberState.numbits)); + for (i = 0; i < msg->fragmentNumberState.numbits; i++) + TRACE (("%c", nn_bitset_isset (msg->fragmentNumberState.numbits, msg->fragmentNumberState.bits, i) ? '1' : '0')); + + if (!rst->forme) + { + TRACE ((" %x:%x:%x:%x -> %x:%x:%x:%x not-for-me)", PGUID (src), PGUID (dst))); + return 1; + } + + if ((wr = ephash_lookup_writer_guid (&dst)) == NULL) + { + TRACE ((" %x:%x:%x:%x -> %x:%x:%x:%x?)", PGUID (src), PGUID (dst))); + return 1; + } + /* Always look up the proxy reader -- even though we don't need for + the normal pure ack steady state. If (a big "if"!) this shows up + as a significant portion of the time, we can always rewrite it to + only retrieve it when needed. */ + if ((prd = ephash_lookup_proxy_reader_guid (&src)) == NULL) + { + TRACE ((" %x:%x:%x:%x? -> %x:%x:%x:%x)", PGUID (src), PGUID (dst))); + return 1; + } + + /* liveliness is still only implemented partially (with all set to AUTOMATIC, BY_PARTICIPANT, &c.), so we simply renew the proxy participant's lease. */ + if (prd->assert_pp_lease) + lease_renew (os_atomic_ldvoidp (&prd->c.proxypp->lease), tnow); + + if (!wr->reliable) /* note: reliability can't be changed */ + { + TRACE ((" %x:%x:%x:%x -> %x:%x:%x:%x not a reliable writer)", PGUID (src), PGUID (dst))); + return 1; + } + + os_mutexLock (&wr->e.lock); + if ((rn = ut_avlLookup (&wr_readers_treedef, &wr->readers, &src)) == NULL) + { + TRACE ((" %x:%x:%x:%x -> %x:%x:%x:%x not a connection", PGUID (src), PGUID (dst))); + goto out; + } + + /* Ignore old NackFrags (see also handle_AckNack) */ + if (*countp < rn->next_nackfrag) + { + TRACE ((" [%x:%x:%x:%x -> %x:%x:%x:%x]", PGUID (src), PGUID (dst))); + goto out; + } + rn->next_nackfrag = *countp + 1; + TRACE ((" %x:%x:%x:%x -> %x:%x:%x:%x", PGUID (src), PGUID (dst))); + + /* Resend the requested fragments if we still have the sample, send + a Gap if we don't have them anymore. */ + if ((whcn = whc_findseq (wr->whc, seq)) == NULL) + { + static unsigned zero = 0; + struct nn_xmsg *m; + TRACE ((" msg not available: scheduling Gap\n")); + m = nn_xmsg_new (gv.xmsgpool, &wr->e.guid.prefix, 0, NN_XMSG_KIND_CONTROL); +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + nn_xmsg_setencoderid (m, wr->partition_id); +#endif + if (nn_xmsg_setdstPRD (m, prd) < 0) + nn_xmsg_free (m); + else + { + /* length-1 bitmap with the bit clear avoids the illegal case of a + length-0 bitmap */ + add_Gap (m, wr, prd, seq, seq+1, 1, &zero); + qxev_msg (wr->evq, m); + } + } + else + { + const unsigned base = msg->fragmentNumberState.bitmap_base - 1; + int enqueued = 1; + TRACE ((" scheduling requested frags ...\n")); + for (i = 0; i < msg->fragmentNumberState.numbits && enqueued; i++) + { + if (nn_bitset_isset (msg->fragmentNumberState.numbits, msg->fragmentNumberState.bits, i)) + { + struct nn_xmsg *reply; + if (create_fragment_message (wr, seq, whcn->plist, whcn->serdata, base + i, prd, &reply, 0) < 0) + enqueued = 0; + else + enqueued = qxev_msg_rexmit_wrlock_held (wr->evq, reply, 0); + } + } + } + if (seq < READ_SEQ_XMIT(wr)) + { + /* Not everything was retransmitted yet, so force a heartbeat out + to give the reader a chance to nack the rest and make sure + hearbeats will go out at a reasonably high rate for a while */ + force_heartbeat_to_peer (wr, prd, 1); + writer_hbcontrol_note_asyncwrite (wr, now_mt ()); + } + + out: + os_mutexUnlock (&wr->e.lock); + TRACE ((")")); + return 1; +} + +static int handle_InfoDST (struct receiver_state *rst, const InfoDST_t *msg, const nn_guid_prefix_t *dst_prefix) +{ + rst->dst_guid_prefix = nn_ntoh_guid_prefix (msg->guid_prefix); + TRACE (("INFODST(%x:%x:%x)", PGUIDPREFIX (rst->dst_guid_prefix))); + if (rst->dst_guid_prefix.u[0] == 0 && rst->dst_guid_prefix.u[1] == 0 && rst->dst_guid_prefix.u[2] == 0) + { + if (dst_prefix) + rst->dst_guid_prefix = *dst_prefix; + rst->forme = 1; + } + else + { + nn_guid_t dst; + dst.prefix = rst->dst_guid_prefix; + dst.entityid = to_entityid(NN_ENTITYID_PARTICIPANT); + rst->forme = (ephash_lookup_participant_guid (&dst) != NULL) || is_deleted_participant_guid (&dst, DPG_LOCAL); + } + return 1; +} + +static int handle_InfoSRC (struct receiver_state *rst, const InfoSRC_t *msg) +{ + rst->src_guid_prefix = nn_ntoh_guid_prefix (msg->guid_prefix); + rst->protocol_version = msg->version; + rst->vendor = msg->vendorid; + TRACE (("INFOSRC(%x:%x:%x vendor %u.%u)", + PGUIDPREFIX (rst->src_guid_prefix), rst->vendor.id[0], rst->vendor.id[1])); + return 1; +} + +static int handle_InfoTS (const InfoTS_t *msg, nn_ddsi_time_t *timestamp) +{ + TRACE (("INFOTS(")); + if (msg->smhdr.flags & INFOTS_INVALIDATE_FLAG) + { + *timestamp = invalid_ddsi_timestamp; + TRACE (("invalidate")); + } + else + { + *timestamp = msg->time; + if (config.enabled_logcats & LC_TRACE) + { + nn_wctime_t t = nn_wctime_from_ddsi_time (* timestamp); + TRACE (("%d.%09d", (int) (t.v / 1000000000), (int) (t.v % 1000000000))); + } + } + TRACE ((")")); + return 1; +} + +static int handle_one_gap (struct proxy_writer *pwr, struct pwr_rd_match *wn, seqno_t a, seqno_t b, struct nn_rdata *gap, int *refc_adjust) +{ + struct nn_rsample_chain sc; + nn_reorder_result_t res; + int gap_was_valuable = 0; + ASSERT_MUTEX_HELD (&pwr->e.lock); + + /* Clean up the defrag admin: no fragments of a missing sample will + be arriving in the future */ + nn_defrag_notegap (pwr->defrag, a, b); + + /* Primary reorder: the gap message may cause some samples to become + deliverable. */ + if ((res = nn_reorder_gap (&sc, pwr->reorder, gap, a, b, refc_adjust)) > 0) + { + if (pwr->deliver_synchronously) + deliver_user_data_synchronously (&sc); + else + nn_dqueue_enqueue (pwr->dqueue, &sc, res); + } + + /* If the result was REJECT or TOO_OLD, then this gap didn't add + anything useful, or there was insufficient memory to store it. + When the result is either ACCEPT or a sample chain, it clearly + meant something. */ + Q_STATIC_ASSERT_CODE (NN_REORDER_ACCEPT == 0); + if (res >= 0) + gap_was_valuable = 1; + + /* Out-of-sync readers never deal with samples with a sequence + number beyond end_of_tl_seq -- and so it needn't be bothered + with gaps that start beyond that number */ + if (wn != NULL && wn->in_sync != PRMSS_SYNC) + { + switch (wn->in_sync) + { + case PRMSS_SYNC: + assert(0); + break; + case PRMSS_TLCATCHUP: + break; + case PRMSS_OUT_OF_SYNC: + if (a <= wn->u.not_in_sync.end_of_out_of_sync_seq) + { + if ((res = nn_reorder_gap (&sc, wn->u.not_in_sync.reorder, gap, a, b, refc_adjust)) > 0) + nn_dqueue_enqueue1 (pwr->dqueue, &wn->rd_guid, &sc, res); + if (res >= 0) + gap_was_valuable = 1; + } + break; + } + + /* Upon receipt of data a reader can only become in-sync if there + is something to deliver; for missing data, you just don't know. + The return value of reorder_gap _is_ sufficiently precise, but + why not simply check? It isn't a very expensive test. */ + maybe_set_reader_in_sync (pwr, wn, b-1); + } + + return gap_was_valuable; +} + +static int handle_Gap (struct receiver_state *rst, nn_etime_t tnow, struct nn_rmsg *rmsg, const Gap_t *msg) +{ + /* Option 1: Process the Gap for the proxy writer and all + out-of-sync readers: what do I care which reader is being + addressed? Either the sample can still be reproduced by the + writer, or it can't be anymore. + + Option 2: Process the Gap for the proxy writer and for the + addressed reader if it happens to be out-of-sync. + + Obviously, both options differ from the specification, but we + don't have much choice: there is no way of addressing just a + single in-sync reader, and if that's impossible than we might as + well ignore the destination completely. + + Option 1 can be fairly expensive if there are many readers, so we + do option 2. */ + + struct proxy_writer *pwr; + struct pwr_rd_match *wn; + nn_guid_t src, dst; + seqno_t gapstart, listbase; + int64_t last_included_rel; + unsigned listidx; + + src.prefix = rst->src_guid_prefix; + src.entityid = msg->writerId; + dst.prefix = rst->dst_guid_prefix; + dst.entityid = msg->readerId; + gapstart = fromSN (msg->gapStart); + listbase = fromSN (msg->gapList.bitmap_base); + TRACE (("GAP(%"PRId64"..%"PRId64"/%u ", gapstart, listbase, msg->gapList.numbits)); + + /* There is no _good_ reason for a writer to start the bitmap with a + 1 bit, but check for it just in case, to reduce the number of + sequence number gaps to be processed. */ + for (listidx = 0; listidx < msg->gapList.numbits; listidx++) + if (!nn_bitset_isset (msg->gapList.numbits, msg->gapList.bits, listidx)) + break; + last_included_rel = (int)listidx - 1; + + if (!rst->forme) + { + TRACE (("%x:%x:%x:%x -> %x:%x:%x:%x not-for-me)", PGUID (src), PGUID (dst))); + return 1; + } + + if ((pwr = ephash_lookup_proxy_writer_guid (&src)) == NULL) + { + TRACE (("%x:%x:%x:%x? -> %x:%x:%x:%x)", PGUID (src), PGUID (dst))); + return 1; + } + + /* liveliness is still only implemented partially (with all set to AUTOMATIC, BY_PARTICIPANT, &c.), so we simply renew the proxy participant's lease. */ + if (pwr->assert_pp_lease) + lease_renew (os_atomic_ldvoidp (&pwr->c.proxypp->lease), tnow); + + os_mutexLock (&pwr->e.lock); + if ((wn = ut_avlLookup (&pwr_readers_treedef, &pwr->readers, &dst)) == NULL) + { + TRACE (("%x:%x:%x:%x -> %x:%x:%x:%x not a connection)", PGUID (src), PGUID (dst))); + os_mutexUnlock (&pwr->e.lock); + return 1; + } + TRACE (("%x:%x:%x:%x -> %x:%x:%x:%x", PGUID (src), PGUID (dst))); + + /* Notify reordering in proxy writer & and the addressed reader (if + it is out-of-sync, &c.), while delivering samples that become + available because preceding ones are now known to be missing. */ + { + int refc_adjust = 0; + struct nn_rdata *gap; + gap = nn_rdata_newgap (rmsg); + if (gapstart < listbase + listidx) + { + /* sanity check on sequence numbers because a GAP message is not invalid even + if start >= listbase (DDSI 2.1 sect 8.3.7.4.3), but only handle non-empty + intervals */ + (void) handle_one_gap (pwr, wn, gapstart, listbase + listidx, gap, &refc_adjust); + } + while (listidx < msg->gapList.numbits) + { + if (!nn_bitset_isset (msg->gapList.numbits, msg->gapList.bits, listidx)) + listidx++; + else + { + unsigned j; + for (j = listidx+1; j < msg->gapList.numbits; j++) + if (!nn_bitset_isset (msg->gapList.numbits, msg->gapList.bits, j)) + break; + /* spec says gapList (2) identifies an additional list of sequence numbers that + are invalid (8.3.7.4.2), so by that rule an insane start would simply mean the + initial interval is to be ignored and the bitmap to be applied */ + (void) handle_one_gap (pwr, wn, listbase + listidx, listbase + j, gap, &refc_adjust); + assert(j >= 1); + last_included_rel = j - 1; + listidx = j; + } + } + nn_fragchain_adjust_refcount (gap, refc_adjust); + } + + /* If the last sequence number explicitly included in the set is + beyond the last sequence number we know exists, update the + latter. Note that a sequence number _not_ included in the set + doesn't tell us anything (which is something that RTI apparently + got wrong in its interpetation of pure acks that do include a + bitmap). */ + if (listbase + last_included_rel > pwr->last_seq) + { + pwr->last_seq = listbase + last_included_rel; + pwr->last_fragnum = ~0u; + pwr->last_fragnum_reset = 0; + } + TRACE ((")")); + os_mutexUnlock (&pwr->e.lock); + return 1; +} + +static serstate_t make_raw_serstate +( + struct sertopic const * const topic, + const struct nn_rdata *fragchain, uint32_t sz, int justkey, + unsigned statusinfo, nn_wctime_t tstamp +) +{ + serstate_t st = ddsi_serstate_new (gv.serpool, topic); + ddsi_serstate_set_msginfo (st, statusinfo, tstamp, NULL); + st->kind = justkey ? STK_KEY : STK_DATA; + /* the CDR header is always fully contained in the first fragment + (see valid_DataFrag), so extracting it is easy */ + assert (fragchain->min == 0); + + /* alignment at head-of-stream is guaranteed, requesting 1 byte + alignment is therefore fine for pasting together fragments of + data */ + { + uint32_t off = 4; /* must skip the CDR header */ + while (fragchain) + { + assert (fragchain->min <= off); + assert (fragchain->maxp1 <= sz); + if (fragchain->maxp1 > off) + { + /* only copy if this fragment adds data */ + const unsigned char *payload = NN_RMSG_PAYLOADOFF (fragchain->rmsg, NN_RDATA_PAYLOAD_OFF (fragchain)); + ddsi_serstate_append_blob (st, 1, fragchain->maxp1 - off, payload + off - fragchain->min); + off = fragchain->maxp1; + } + fragchain = fragchain->nextfrag; + } + } + return st; +} + +static serdata_t extract_sample_from_data +( + const struct nn_rsample_info *sampleinfo, unsigned char data_smhdr_flags, + const nn_plist_t *qos, const struct nn_rdata *fragchain, unsigned statusinfo, + nn_wctime_t tstamp, struct sertopic const * const topic +) +{ + static const nn_guid_t null_guid = {{{0,0,0,0,0,0,0,0,0,0,0,0}},{0}}; + const char *failmsg = NULL; + serdata_t sample = NULL; + + if (statusinfo == 0) + { + /* normal write */ + serstate_t st; + if (!(data_smhdr_flags & DATA_FLAG_DATAFLAG) || sampleinfo->size == 0) + { + const struct proxy_writer *pwr = sampleinfo->pwr; + nn_guid_t guid = pwr ? pwr->e.guid : null_guid; /* can't be null _yet_, but that might change some day */ + TRACE (("data(application, vendor %u.%u): %x:%x:%x:%x #%"PRId64 + ": write without proper payload (data_smhdr_flags 0x%x size %u)\n", + sampleinfo->rst->vendor.id[0], sampleinfo->rst->vendor.id[1], + PGUID (guid), sampleinfo->seq, + data_smhdr_flags, sampleinfo->size)); + return NULL; + } + st = make_raw_serstate (topic, fragchain, sampleinfo->size, 0, statusinfo, tstamp); + sample = ddsi_serstate_fix (st); + } + else if (sampleinfo->size) + { + /* dispose or unregister with included serialized key or data + (data is a PrismTech extension) -- i.e., dispose or unregister + as one would expect to receive */ + serstate_t st; + if (data_smhdr_flags & DATA_FLAG_KEYFLAG) + { + st = make_raw_serstate (topic, fragchain, sampleinfo->size, 1, statusinfo, tstamp); + sample = ddsi_serstate_fix (st); + } + else + { + assert (data_smhdr_flags & DATA_FLAG_DATAFLAG); + st = make_raw_serstate (topic, fragchain, sampleinfo->size, 0, statusinfo, tstamp); + sample = ddsi_serstate_fix (st); + } + } + else if (data_smhdr_flags & DATA_FLAG_INLINE_QOS) + { + /* RTI always tries to make us survive on the keyhash. RTI must + mend its ways. */ + if (NN_STRICT_P) + failmsg = "no content"; + else if (!(qos->present & PP_KEYHASH)) + failmsg = "qos present but without keyhash"; + else + { + serstate_t st; + st = ddsi_serstate_new (gv.serpool, topic); + ddsi_serstate_set_msginfo (st, statusinfo, tstamp, NULL); + st->kind = STK_KEY; + ddsi_serstate_append_blob (st, 1, sizeof (qos->keyhash), qos->keyhash.value); + sample = ddsi_serstate_fix (st); + } + } + else + { + failmsg = "no content whatsoever"; + } + if (sample == NULL) + { + /* No message => error out */ + const struct proxy_writer *pwr = sampleinfo->pwr; + nn_guid_t guid = pwr ? pwr->e.guid : null_guid; /* can't be null _yet_, but that might change some day */ + NN_WARNING + ( + "data(application, vendor %u.%u): %x:%x:%x:%x #%"PRId64": deserialization %s/%s failed (%s)\n", + sampleinfo->rst->vendor.id[0], sampleinfo->rst->vendor.id[1], + PGUID (guid), sampleinfo->seq, + topic->name, topic->typename, + failmsg ? failmsg : "for reasons unknown" + ); + } + else + { + sample->v.bswap = sampleinfo->bswap; + } + return sample; +} + +unsigned char normalize_data_datafrag_flags (const SubmessageHeader_t *smhdr, int datafrag_as_data) +{ + switch ((SubmessageKind_t) smhdr->submessageId) + { + case SMID_DATA: + return smhdr->flags; + case SMID_DATA_FRAG: + if (datafrag_as_data) + return smhdr->flags; + else + { + unsigned char common = smhdr->flags & DATA_FLAG_INLINE_QOS; + Q_STATIC_ASSERT_CODE (DATA_FLAG_INLINE_QOS == DATAFRAG_FLAG_INLINE_QOS); + if (smhdr->flags & DATAFRAG_FLAG_KEYFLAG) + return common | DATA_FLAG_KEYFLAG; + else + return common | DATA_FLAG_DATAFLAG; + } + default: + assert (0); + return 0; + } +} + + + + +static int deliver_user_data (const struct nn_rsample_info *sampleinfo, const struct nn_rdata *fragchain, const nn_guid_t *rdguid, int pwr_locked) +{ + struct receiver_state const * const rst = sampleinfo->rst; + struct proxy_writer * const pwr = sampleinfo->pwr; + struct sertopic const * const topic = pwr->c.topic; + unsigned statusinfo; + Data_DataFrag_common_t *msg; + unsigned char data_smhdr_flags; + nn_plist_t qos; + int need_keyhash; + serdata_t payload; + + if (pwr->ddsi2direct_cb) + { + pwr->ddsi2direct_cb (sampleinfo, fragchain, pwr->ddsi2direct_cbarg); + return 0; + } + + /* NOTE: pwr->e.lock need not be held for correct processing (though + it may be useful to hold it for maintaining order all the way to + v_groupWrite): guid is constant, set_vmsg_header() explains about + the qos issue (and will have to deal with that); and + pwr->groupset takes care of itself. FIXME: groupset may be + taking care of itself, but it is currently doing so in an + annoyingly simplistic manner ... */ + + /* FIXME: fragments are now handled by copying the message to + freshly malloced memory (see defragment()) ... that'll have to + change eventually */ + assert (fragchain->min == 0); + assert (!is_builtin_entityid (pwr->e.guid.entityid, pwr->c.vendor)); + /* Can only get here if at some point readers existed => topic can't + still be NULL, even if there are no readers at the moment */ + assert (topic != NULL); + + /* Luckily, the Data header (up to inline QoS) is a prefix of the + DataFrag header, so for the fixed-position things that we're + interested in here, both can be treated as Data submessages. */ + msg = (Data_DataFrag_common_t *) NN_RMSG_PAYLOADOFF (fragchain->rmsg, NN_RDATA_SUBMSG_OFF (fragchain)); + data_smhdr_flags = normalize_data_datafrag_flags (&msg->smhdr, config.buggy_datafrag_flags_mode); + + /* Extract QoS's to the extent necessary. The expected case has all + we need predecoded into a few bits in the sample info. + + If there is no payload, it is either a completely invalid message + or a dispose/unregister in RTI style. We assume the latter, + consequently expect to need the keyhash. Then, if sampleinfo + says it is a complex qos, or the keyhash is required, extract all + we need from the inline qos. + + Complex qos bit also gets set when statusinfo bits other than + dispose/unregister are set. They are not currently defined, but + this may save us if they do get defined one day. */ + need_keyhash = (sampleinfo->size == 0 || (data_smhdr_flags & (DATA_FLAG_KEYFLAG | DATA_FLAG_DATAFLAG)) == 0); + if (!(sampleinfo->complex_qos || need_keyhash) || !(data_smhdr_flags & DATA_FLAG_INLINE_QOS)) + { + nn_plist_init_empty (&qos); + statusinfo = sampleinfo->statusinfo; + } + else + { + nn_plist_src_t src; + size_t qos_offset = NN_RDATA_SUBMSG_OFF (fragchain) + offsetof (Data_DataFrag_common_t, octetsToInlineQos) + sizeof (msg->octetsToInlineQos) + msg->octetsToInlineQos; + src.protocol_version = rst->protocol_version; + src.vendorid = rst->vendor; + src.encoding = (msg->smhdr.flags & SMFLAG_ENDIANNESS) ? PL_CDR_LE : PL_CDR_BE; + src.buf = NN_RMSG_PAYLOADOFF (fragchain->rmsg, qos_offset); + src.bufsz = NN_RDATA_PAYLOAD_OFF (fragchain) - qos_offset; + if (nn_plist_init_frommsg (&qos, NULL, PP_STATUSINFO | PP_KEYHASH | PP_COHERENT_SET | PP_PRISMTECH_EOTINFO, 0, &src) < 0) + { + NN_WARNING ("data(application, vendor %u.%u): %x:%x:%x:%x #%"PRId64": invalid inline qos\n", + src.vendorid.id[0], src.vendorid.id[1], PGUID (pwr->e.guid), sampleinfo->seq); + return 0; + } + statusinfo = (qos.present & PP_STATUSINFO) ? qos.statusinfo : 0; + } + + /* Note: deserializing done potentially many times for a historical + data sample (once per reader that cares about that data). For + now, this is accepted as sufficiently abnormal behaviour to not + worry about it. */ + { + nn_wctime_t tstamp; + if (valid_ddsi_timestamp (sampleinfo->timestamp)) + tstamp = nn_wctime_from_ddsi_time (sampleinfo->timestamp); + else + tstamp.v = 0; + payload = extract_sample_from_data (sampleinfo, data_smhdr_flags, &qos, fragchain, statusinfo, tstamp, topic); + } + if (payload == NULL) + { + goto no_payload; + } + + + /* Generate the DDS_SampleInfo (which is faked to some extent + because we don't actually have a data reader); also note that + the PRISMTECH_WRITER_INFO thing is completely meaningless to + us */ + { + struct tkmap_instance * tk; + + if (sampleinfo->hashash) + { + payload->v.keyhash.m_flags = DDS_KEY_HASH_SET; + memcpy (&payload->v.keyhash.m_hash, &sampleinfo->keyhash, sizeof (payload->v.keyhash.m_hash)); + } + tk = (ddsi_plugin.rhc_lookup_fn) (payload); + + if (tk) + { + if (rdguid == NULL) + { + TRACE ((" %"PRId64"=>EVERYONE\n", sampleinfo->seq)); + + /* FIXME: pwr->rdary is an array of pointers to attached + readers. There's only one thread delivering data for the + proxy writer (as long as there is only one receive thread), + so could get away with not locking at all, and doing safe + updates + GC of rdary instead. */ + + /* Retry loop, for re-delivery of rejected reliable samples. Is a + temporary hack till throttling back of writer is implemented + (with late acknowledgement of sample and nack). */ +retry: + + os_mutexLock (&pwr->rdary.rdary_lock); + if (pwr->rdary.fastpath_ok) + { + struct reader ** const rdary = pwr->rdary.rdary; + unsigned i; + for (i = 0; rdary[i]; i++) + { + TRACE (("reader %x:%x:%x:%x\n", PGUID (rdary[i]->e.guid))); + if (! (ddsi_plugin.rhc_store_fn) (rdary[i]->rhc, sampleinfo, payload, tk)) + { + if (pwr_locked) os_mutexUnlock (&pwr->e.lock); + os_mutexUnlock (&pwr->rdary.rdary_lock); + dds_sleepfor (DDS_MSECS (10)); + if (pwr_locked) os_mutexLock (&pwr->e.lock); + goto retry; + } + } + os_mutexUnlock (&pwr->rdary.rdary_lock); + } + else + { + /* When deleting, pwr is no longer accessible via the hash + tables, and consequently, a reader may be deleted without + it being possible to remove it from rdary. The primary + reason rdary exists is to avoid locking the proxy writer + but this is less of an issue when we are deleting it, so + we fall back to using the GUIDs so that we can deliver all + samples we received from it. As writer being deleted any + reliable samples that are rejected are simply discarded. */ + ut_avlIter_t it; + struct pwr_rd_match *m; + os_mutexUnlock (&pwr->rdary.rdary_lock); + if (!pwr_locked) os_mutexLock (&pwr->e.lock); + for (m = ut_avlIterFirst (&pwr_readers_treedef, &pwr->readers, &it); m != NULL; m = ut_avlIterNext (&it)) + { + struct reader *rd; + if ((rd = ephash_lookup_reader_guid (&m->rd_guid)) != NULL) + { + TRACE (("reader-via-guid %x:%x:%x:%x\n", PGUID (rd->e.guid))); + (void) (ddsi_plugin.rhc_store_fn) (rd->rhc, sampleinfo, payload, tk); + } + } + if (!pwr_locked) os_mutexUnlock (&pwr->e.lock); + } + + os_atomic_st32 (&pwr->next_deliv_seq_lowword, (uint32_t) (sampleinfo->seq + 1)); + } + else + { + struct reader *rd = ephash_lookup_reader_guid (rdguid);; + TRACE ((" %"PRId64"=>%x:%x:%x:%x%s\n", sampleinfo->seq, PGUID (*rdguid), rd ? "" : "?")); + while (rd && ! (ddsi_plugin.rhc_store_fn) (rd->rhc, sampleinfo, payload, tk) && ephash_lookup_proxy_writer_guid (&pwr->e.guid)) + { + if (pwr_locked) os_mutexUnlock (&pwr->e.lock); + dds_sleepfor (DDS_MSECS (1)); + if (pwr_locked) os_mutexLock (&pwr->e.lock); + } + } + (ddsi_plugin.rhc_unref_fn) (tk); + } + } + ddsi_serdata_unref (payload); + no_payload: + nn_plist_fini (&qos); + return 0; +} + +int user_dqueue_handler (const struct nn_rsample_info *sampleinfo, const struct nn_rdata *fragchain, const nn_guid_t *rdguid, UNUSED_ARG (void *qarg)) +{ + int res; + res = deliver_user_data (sampleinfo, fragchain, rdguid, 0); + return res; +} + +static void deliver_user_data_synchronously (struct nn_rsample_chain *sc) +{ + while (sc->first) + { + struct nn_rsample_chain_elem *e = sc->first; + sc->first = e->next; + if (e->sampleinfo != NULL) + { + /* Must not try to deliver a gap -- possibly a FIXME for + sample_lost events. Also note that the synchronous path is + _never_ used for historical data, and therefore never has the + GUID of a reader to deliver to */ + deliver_user_data (e->sampleinfo, e->fragchain, NULL, 1); + } + nn_fragchain_unref (e->fragchain); + } +} + +static void clean_defrag (struct proxy_writer *pwr) +{ + seqno_t seq = nn_reorder_next_seq (pwr->reorder); + struct pwr_rd_match *wn; + for (wn = ut_avlFindMin (&pwr_readers_treedef, &pwr->readers); wn != NULL; wn = ut_avlFindSucc (&pwr_readers_treedef, &pwr->readers, wn)) + { + if (wn->in_sync == PRMSS_OUT_OF_SYNC) + { + seqno_t seq1 = nn_reorder_next_seq (wn->u.not_in_sync.reorder); + if (seq1 < seq) + seq = seq1; + } + } + nn_defrag_notegap (pwr->defrag, 1, seq); +} + +static void handle_regular (struct receiver_state *rst, nn_etime_t tnow, struct nn_rmsg *rmsg, const Data_DataFrag_common_t *msg, const struct nn_rsample_info *sampleinfo, uint32_t fragnum, struct nn_rdata *rdata) +{ + struct proxy_writer *pwr; + struct nn_rsample *rsample; + nn_guid_t dst; + + dst.prefix = rst->dst_guid_prefix; + dst.entityid = msg->readerId; + + pwr = sampleinfo->pwr; + if (pwr == NULL) + { + nn_guid_t src; + src.prefix = rst->src_guid_prefix; + src.entityid = msg->writerId; + TRACE ((" %x:%x:%x:%x? -> %x:%x:%x:%x", PGUID (src), PGUID (dst))); + return; + } + + /* liveliness is still only implemented partially (with all set to + AUTOMATIC, BY_PARTICIPANT, &c.), so we simply renew the proxy + participant's lease. */ + if (pwr->assert_pp_lease) + { + lease_renew (os_atomic_ldvoidp (&pwr->c.proxypp->lease), tnow); + } + + /* Shouldn't lock the full writer, but will do so for now */ + os_mutexLock (&pwr->e.lock); + if (ut_avlIsEmpty (&pwr->readers) || pwr->local_matching_inprogress) + { + os_mutexUnlock (&pwr->e.lock); + TRACE ((" %x:%x:%x:%x -> %x:%x:%x:%x: no readers", PGUID (pwr->e.guid), PGUID (dst))); + return; + } + + /* Track highest sequence number we know of -- we track both + sequence number & fragment number so that the NACK generation can + do the Right Thing. */ + if (sampleinfo->seq > pwr->last_seq) + { + pwr->last_seq = sampleinfo->seq; + pwr->last_fragnum = fragnum; + pwr->last_fragnum_reset = 0; + } + else if (sampleinfo->seq == pwr->last_seq && fragnum > pwr->last_fragnum) + { + pwr->last_fragnum = fragnum; + pwr->last_fragnum_reset = 0; + } + + clean_defrag (pwr); + + if ((rsample = nn_defrag_rsample (pwr->defrag, rdata, sampleinfo)) != NULL) + { + int refc_adjust = 0; + struct nn_rsample_chain sc; + struct nn_rdata *fragchain = nn_rsample_fragchain (rsample); + nn_reorder_result_t rres; + + rres = nn_reorder_rsample (&sc, pwr->reorder, rsample, &refc_adjust, 0); // nn_dqueue_is_full (pwr->dqueue)); + + if (rres == NN_REORDER_ACCEPT && pwr->n_reliable_readers == 0) + { + /* If no reliable readers but the reorder buffer accepted the + sample, it must be a reliable proxy writer with only + unreliable readers. "Inserting" a Gap [1, sampleinfo->seq) + will force delivery of this sample, and not cause the gap to + be added to the reorder admin. */ + int gap_refc_adjust = 0; + rres = nn_reorder_gap (&sc, pwr->reorder, rdata, 1, sampleinfo->seq, &gap_refc_adjust); + assert (rres > 0); + assert (gap_refc_adjust == 0); + } + + if (rres > 0) + { + /* Enqueue or deliver with pwr->e.lock held: to ensure no other + receive thread's data gets interleaved -- arguably delivery + needn't be exactly in-order, which would allow us to do this + without pwr->e.lock held. */ + if (!pwr->deliver_synchronously) + nn_dqueue_enqueue (pwr->dqueue, &sc, rres); + else + deliver_user_data_synchronously (&sc); + if (pwr->n_readers_out_of_sync > 0) + { + /* Those readers catching up with TL but in sync with the proxy + writer may have become in sync with the proxy writer and the + writer; those catching up with TL all by themselves go through + the "TOO_OLD" path below. */ + ut_avlIter_t it; + struct pwr_rd_match *wn; + for (wn = ut_avlIterFirst (&pwr_readers_treedef, &pwr->readers, &it); wn != NULL; wn = ut_avlIterNext (&it)) + if (wn->in_sync == PRMSS_TLCATCHUP) + maybe_set_reader_in_sync (pwr, wn, sampleinfo->seq); + } + } + else if (rres == NN_REORDER_TOO_OLD) + { + struct pwr_rd_match *wn; + struct nn_rsample *rsample_dup = NULL; + int reuse_rsample_dup = 0; + for (wn = ut_avlFindMin (&pwr_readers_treedef, &pwr->readers); wn != NULL; wn = ut_avlFindSucc (&pwr_readers_treedef, &pwr->readers, wn)) + { + nn_reorder_result_t rres2; + if (wn->in_sync != PRMSS_OUT_OF_SYNC || sampleinfo->seq > wn->u.not_in_sync.end_of_out_of_sync_seq) + continue; + if (!reuse_rsample_dup) + rsample_dup = nn_reorder_rsample_dup (rmsg, rsample); + rres2 = nn_reorder_rsample (&sc, wn->u.not_in_sync.reorder, rsample_dup, &refc_adjust, nn_dqueue_is_full (pwr->dqueue)); + switch (rres2) + { + case NN_REORDER_TOO_OLD: + case NN_REORDER_REJECT: + reuse_rsample_dup = 1; + break; + case NN_REORDER_ACCEPT: + reuse_rsample_dup = 0; + break; + default: + assert (rres2 > 0); + /* note: can't deliver to a reader, only to a group */ + maybe_set_reader_in_sync (pwr, wn, sampleinfo->seq); + reuse_rsample_dup = 0; + /* No need to deliver old data to out-of-sync readers + synchronously -- ordering guarantees don't change + as fresh data will be delivered anyway and hence + the old data will never be guaranteed to arrive + in-order, and those few microseconds can't hurt in + catching up on transient-local data. See also + NN_REORDER_DELIVER case in outer switch. */ + nn_dqueue_enqueue1 (pwr->dqueue, &wn->rd_guid, &sc, rres2); + break; + } + } + } +#ifndef NDEBUG + else + { + assert (rres == NN_REORDER_ACCEPT || rres == NN_REORDER_REJECT); + } +#endif + nn_fragchain_adjust_refcount (fragchain, refc_adjust); + } + os_mutexUnlock (&pwr->e.lock); + nn_dqueue_wait_until_empty_if_full (pwr->dqueue); +} + +static int handle_SPDP (const struct nn_rsample_info *sampleinfo, struct nn_rdata *rdata) +{ + struct nn_rsample *rsample; + struct nn_rsample_chain sc; + struct nn_rdata *fragchain; + nn_reorder_result_t rres; + int refc_adjust = 0; + os_mutexLock (&gv.spdp_lock); + rsample = nn_defrag_rsample (gv.spdp_defrag, rdata, sampleinfo); + fragchain = nn_rsample_fragchain (rsample); + if ((rres = nn_reorder_rsample (&sc, gv.spdp_reorder, rsample, &refc_adjust, nn_dqueue_is_full (gv.builtins_dqueue))) > 0) + nn_dqueue_enqueue (gv.builtins_dqueue, &sc, rres); + os_mutexUnlock (&gv.spdp_lock); + nn_fragchain_adjust_refcount (fragchain, refc_adjust); + return 0; +} + +static void drop_oversize (struct receiver_state *rst, struct nn_rmsg *rmsg, const Data_DataFrag_common_t *msg, struct nn_rsample_info *sampleinfo) +{ + struct proxy_writer *pwr = sampleinfo->pwr; + if (pwr == NULL) + { + /* No proxy writer means nothing really gets done with, unless it + is SPDP. SPDP is periodic, so oversize discovery packets would + cause periodic warnings. */ + if (msg->writerId.u == NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER) + { + NN_WARNING ("dropping oversize (%u > %u) SPDP sample %"PRId64" from remote writer %x:%x:%x:%x\n", + sampleinfo->size, config.max_sample_size, sampleinfo->seq, + PGUIDPREFIX (rst->src_guid_prefix), msg->writerId.u); + } + } + else + { + /* Normal case: we actually do know the writer. Dropping it is as + easy as pushing a gap through the pipe, but trying to log the + event only once is trickier. Checking whether the gap had some + effect seems a reasonable approach. */ + int refc_adjust = 0; + struct nn_rdata *gap = nn_rdata_newgap (rmsg); + nn_guid_t dst; + struct pwr_rd_match *wn; + int gap_was_valuable; + + dst.prefix = rst->dst_guid_prefix; + dst.entityid = msg->readerId; + + os_mutexLock (&pwr->e.lock); + wn = ut_avlLookup (&pwr_readers_treedef, &pwr->readers, &dst); + gap_was_valuable = handle_one_gap (pwr, wn, sampleinfo->seq, sampleinfo->seq+1, gap, &refc_adjust); + os_mutexUnlock (&pwr->e.lock); + nn_fragchain_adjust_refcount (gap, refc_adjust); + + if (gap_was_valuable) + { + const char *tname = pwr->c.topic ? pwr->c.topic->name : "(null)"; + const char *ttname = pwr->c.topic ? pwr->c.topic->typename : "(null)"; + NN_WARNING ("dropping oversize (%u > %u) sample %"PRId64" from remote writer %x:%x:%x:%x %s/%s\n", + sampleinfo->size, config.max_sample_size, sampleinfo->seq, + PGUIDPREFIX (rst->src_guid_prefix), msg->writerId.u, + tname, ttname); + } + } +} + +static int handle_Data (struct receiver_state *rst, nn_etime_t tnow, struct nn_rmsg *rmsg, const Data_t *msg, size_t size, struct nn_rsample_info *sampleinfo, unsigned char *datap) +{ + TRACE (("DATA(%x:%x:%x:%x -> %x:%x:%x:%x #%"PRId64"", + PGUIDPREFIX (rst->src_guid_prefix), msg->x.writerId.u, + PGUIDPREFIX (rst->dst_guid_prefix), msg->x.readerId.u, + fromSN (msg->x.writerSN))); + if (!rst->forme) + { + TRACE ((" not-for-me)")); + return 1; + } + + if (sampleinfo->size > config.max_sample_size) + drop_oversize (rst, rmsg, &msg->x, sampleinfo); + else + { + struct nn_rdata *rdata; + unsigned submsg_offset, payload_offset; + submsg_offset = (unsigned) ((unsigned char *) msg - NN_RMSG_PAYLOAD (rmsg)); + if (datap) + { + payload_offset = (unsigned) ((unsigned char *) datap - NN_RMSG_PAYLOAD (rmsg)); + } + else + { + payload_offset = submsg_offset + (unsigned) size; + } + rdata = nn_rdata_new (rmsg, 0, sampleinfo->size, submsg_offset, payload_offset); + + if (msg->x.writerId.u == NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER) + /* SPDP needs special treatment: there are no proxy writers for it + and we accept data from unknown sources */ + { + handle_SPDP (sampleinfo, rdata); + } + else + { + handle_regular (rst, tnow, rmsg, &msg->x, sampleinfo, ~0u, rdata); + } + } + TRACE ((")")); + return 1; +} + +static int handle_DataFrag (struct receiver_state *rst, nn_etime_t tnow, struct nn_rmsg *rmsg, const DataFrag_t *msg, size_t size, struct nn_rsample_info *sampleinfo, unsigned char *datap) +{ + TRACE (("DATAFRAG(%x:%x:%x:%x -> %x:%x:%x:%x #%"PRId64"/[%u..%u]", + PGUIDPREFIX (rst->src_guid_prefix), msg->x.writerId.u, + PGUIDPREFIX (rst->dst_guid_prefix), msg->x.readerId.u, + fromSN (msg->x.writerSN), + msg->fragmentStartingNum, msg->fragmentStartingNum + msg->fragmentsInSubmessage - 1)); + if (!rst->forme) + { + TRACE ((" not-for-me)")); + return 1; + } + + if (sampleinfo->size > config.max_sample_size) + drop_oversize (rst, rmsg, &msg->x, sampleinfo); + else + { + struct nn_rdata *rdata; + unsigned submsg_offset, payload_offset; + uint32_t begin, endp1; + if (msg->x.writerId.u == NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER) + { + NN_WARNING ("DATAFRAG(%x:%x:%x:%x #%"PRId64" -> %x:%x:%x:%x) - fragmented builtin data not yet supported\n", + PGUIDPREFIX (rst->src_guid_prefix), msg->x.writerId.u, fromSN (msg->x.writerSN), + PGUIDPREFIX (rst->dst_guid_prefix), msg->x.readerId.u); + return 1; + } + + submsg_offset = (unsigned) ((unsigned char *) msg - NN_RMSG_PAYLOAD (rmsg)); + if (datap) + payload_offset = (unsigned) ((unsigned char *) datap - NN_RMSG_PAYLOAD (rmsg)); + else + payload_offset = submsg_offset + (unsigned) size; + + begin = (msg->fragmentStartingNum - 1) * msg->fragmentSize; + if (msg->fragmentSize * msg->fragmentsInSubmessage > ((unsigned char *) msg + size - datap)) { + /* this happens for the last fragment (which usually is short) -- + and is included here merely as a sanity check, because that + would mean the computed endp1'd be larger than the sample + size */ + endp1 = begin + (uint32_t) ((unsigned char *) msg + size - datap); + } else { + /* most of the time we get here, but this differs from the + preceding only when the fragment size is not a multiple of 4 + whereas all the length of CDR data always is (and even then, + you'd be fine as the defragmenter can deal with partially + overlapping fragments ...) */ + endp1 = begin + (uint32_t) msg->fragmentSize * msg->fragmentsInSubmessage; + } + if (endp1 > msg->sampleSize) + { + /* the sample size need not be a multiple of 4 so we can still get + here */ + endp1 = msg->sampleSize; + } + TRACE (("/[%u..%u) of %u", begin, endp1, msg->sampleSize)); + + rdata = nn_rdata_new (rmsg, begin, endp1, submsg_offset, payload_offset); + + /* Fragment numbers in DDSI2 internal representation are 0-based, + whereas in DDSI they are 1-based. The highest fragment number in + the sample in internal representation is therefore START+CNT-2, + rather than the expect START+CNT-1. Nothing will go terribly + wrong, it'll simply generate a request for retransmitting a + non-existent fragment. The other side SHOULD be capable of + dealing with that. */ + handle_regular (rst, tnow, rmsg, &msg->x, sampleinfo, msg->fragmentStartingNum + msg->fragmentsInSubmessage - 2, rdata); + } + TRACE ((")")); + return 1; +} + +#ifdef DDSI_INCLUDE_ENCRYPTION +static size_t decode_container (unsigned char *submsg, size_t len) +{ + size_t result = len; + if (gv.recvSecurityCodec && len > 0) + { + if (! (q_security_plugin.decode) + (gv.recvSecurityCodec, submsg, len, &result /* in/out, decrements the length*/)) + { + result = 0; + } + } + return result; +} +#endif /* DDSI_INCLUDE_ENCRYPTION */ + +static void malformed_packet_received_nosubmsg +( + const unsigned char * msg, + ssize_t len, + const char *state, + nn_vendorid_t vendorid +) +{ + char tmp[1024]; + ssize_t i; + size_t pos; + + /* Show beginning of message (as hex dumps) */ + pos = (size_t) snprintf (tmp, sizeof (tmp), "malformed packet received from vendor %u.%u state %s <", vendorid.id[0], vendorid.id[1], state); + for (i = 0; i < 32 && i < len && pos < sizeof (tmp); i++) + pos += (size_t) snprintf (tmp + pos, sizeof (tmp) - pos, "%s%02x", (i > 0 && (i%4) == 0) ? " " : "", msg[i]); + if (pos < sizeof (tmp)) + pos += (size_t) snprintf (tmp + pos, sizeof (tmp) - pos, "> (note: maybe partially bswap'd)"); + assert (pos < sizeof (tmp)); + NN_WARNING ("%s\n", tmp); +} + +static void malformed_packet_received +( + const unsigned char *msg, + const unsigned char *submsg, + size_t len, + const char *state, + SubmessageKind_t smkind, + nn_vendorid_t vendorid +) +{ + char tmp[1024]; + size_t i, pos, smsize; + assert (submsg >= msg && submsg < msg + len); + + /* Show beginning of message and of submessage (as hex dumps) */ + pos = (size_t) snprintf (tmp, sizeof (tmp), "malformed packet received from vendor %u.%u state %s <", vendorid.id[0], vendorid.id[1], state); + for (i = 0; i < 32 && i < len && msg + i < submsg && pos < sizeof (tmp); i++) + pos += (size_t) snprintf (tmp + pos, sizeof (tmp) - pos, "%s%02x", (i > 0 && (i%4) == 0) ? " " : "", msg[i]); + if (pos < sizeof (tmp)) + pos += (size_t) snprintf (tmp + pos, sizeof (tmp) - pos, " @0x%x ", (int) (submsg - msg)); + for (i = 0; i < 32 && i < len - (size_t) (submsg - msg) && pos < sizeof (tmp); i++) + pos += (size_t) snprintf (tmp + pos, sizeof (tmp) - pos, "%s%02x", (i > 0 && (i%4) == 0) ? " " : "", submsg[i]); + if (pos < sizeof (tmp)) + pos += (size_t) snprintf (tmp + pos, sizeof (tmp) - pos, "> (note: maybe partially bswap'd)"); + assert (pos < (int) sizeof (tmp)); + + /* Partially decode header if we have enough bytes available */ + smsize = len - (size_t) (submsg - msg); + switch (smkind) + { + case SMID_ACKNACK: + if (smsize >= sizeof (AckNack_t)) + { + const AckNack_t *x = (const AckNack_t *) submsg; + (void) snprintf (tmp + pos, sizeof (tmp) - pos, " {{%x,%x,%u},%x,%x,%"PRId64",%u}", + x->smhdr.submessageId, x->smhdr.flags, x->smhdr.octetsToNextHeader, + x->readerId.u, x->writerId.u, fromSN (x->readerSNState.bitmap_base), + x->readerSNState.numbits); + } + break; + case SMID_HEARTBEAT: + if (smsize >= sizeof (Heartbeat_t)) + { + const Heartbeat_t *x = (const Heartbeat_t *) submsg; + (void) snprintf (tmp + pos, sizeof (tmp) - pos, " {{%x,%x,%u},%x,%x,%"PRId64",%"PRId64"}", + x->smhdr.submessageId, x->smhdr.flags, x->smhdr.octetsToNextHeader, + x->readerId.u, x->writerId.u, fromSN (x->firstSN), fromSN (x->lastSN)); + } + break; + case SMID_GAP: + if (smsize >= sizeof (Gap_t)) + { + const Gap_t *x = (const Gap_t *) submsg; + (void) snprintf (tmp + pos, sizeof (tmp) - pos, " {{%x,%x,%u},%x,%x,%"PRId64",%"PRId64",%u}", + x->smhdr.submessageId, x->smhdr.flags, x->smhdr.octetsToNextHeader, + x->readerId.u, x->writerId.u, fromSN (x->gapStart), + fromSN (x->gapList.bitmap_base), x->gapList.numbits); + } + break; + case SMID_NACK_FRAG: + if (smsize >= sizeof (NackFrag_t)) + { + const NackFrag_t *x = (const NackFrag_t *) submsg; + (void) snprintf (tmp + pos, sizeof (tmp) - pos, " {{%x,%x,%u},%x,%x,%"PRId64",%u,%u}", + x->smhdr.submessageId, x->smhdr.flags, x->smhdr.octetsToNextHeader, + x->readerId.u, x->writerId.u, fromSN (x->writerSN), + x->fragmentNumberState.bitmap_base, x->fragmentNumberState.numbits); + } + break; + case SMID_HEARTBEAT_FRAG: + if (smsize >= sizeof (HeartbeatFrag_t)) + { + const HeartbeatFrag_t *x = (const HeartbeatFrag_t *) submsg; + (void) snprintf (tmp + pos, sizeof (tmp) - pos, " {{%x,%x,%u},%x,%x,%"PRId64",%u}", + x->smhdr.submessageId, x->smhdr.flags, x->smhdr.octetsToNextHeader, + x->readerId.u, x->writerId.u, fromSN (x->writerSN), + x->lastFragmentNum); + } + break; + case SMID_DATA: + if (smsize >= sizeof (Data_t)) + { + const Data_t *x = (const Data_t *) submsg; + (void) snprintf (tmp + pos, sizeof (tmp) - pos, " {{%x,%x,%u},%x,%u,%x,%x,%"PRId64"}", + x->x.smhdr.submessageId, x->x.smhdr.flags, x->x.smhdr.octetsToNextHeader, + x->x.extraFlags, x->x.octetsToInlineQos, + x->x.readerId.u, x->x.writerId.u, fromSN (x->x.writerSN)); + } + break; + case SMID_DATA_FRAG: + if (smsize >= sizeof (DataFrag_t)) + { + const DataFrag_t *x = (const DataFrag_t *) submsg; + (void) snprintf (tmp + pos, sizeof (tmp) - pos, " {{%x,%x,%u},%x,%u,%x,%x,%"PRId64",%u,%u,%u,%u}", + x->x.smhdr.submessageId, x->x.smhdr.flags, x->x.smhdr.octetsToNextHeader, + x->x.extraFlags, x->x.octetsToInlineQos, + x->x.readerId.u, x->x.writerId.u, fromSN (x->x.writerSN), + x->fragmentStartingNum, x->fragmentsInSubmessage, x->fragmentSize, x->sampleSize); + } + break; + default: + break; + } + + NN_WARNING ("%s\n", tmp); +} + +static struct receiver_state *rst_cow_if_needed (int *rst_live, struct nn_rmsg *rmsg, struct receiver_state *rst) +{ + if (! *rst_live) + return rst; + else + { + struct receiver_state *nrst = nn_rmsg_alloc (rmsg, sizeof (*nrst)); + *nrst = *rst; + *rst_live = 0; + return nrst; + } +} + +static int handle_submsg_sequence +( + ddsi_tran_conn_t conn, + struct thread_state1 * const self, + nn_wctime_t tnowWC, + nn_etime_t tnowE, + const nn_guid_prefix_t * const src_prefix, + const nn_guid_prefix_t * const dst_prefix, + unsigned char * const msg /* NOT const - we may byteswap it */, + const size_t len, + unsigned char * submsg /* aliases somewhere in msg */, + struct nn_rmsg * const rmsg +) +{ + const char *state; + SubmessageKind_t state_smkind; + Header_t * hdr = (Header_t *) msg; + struct receiver_state *rst; + int rst_live, ts_for_latmeas; + nn_ddsi_time_t timestamp; + size_t submsg_size = 0; + unsigned char * end = msg + len; + + /* Receiver state is dynamically allocated with lifetime bound to + the message. Updates cause a new copy to be created if the + current one is "live", i.e., possibly referenced by a + submessage (for now, only Data(Frag)). */ + rst = nn_rmsg_alloc (rmsg, sizeof (*rst)); + memset (rst, 0, sizeof (*rst)); + rst->conn = conn; + rst->src_guid_prefix = *src_prefix; + if (dst_prefix) + { + rst->dst_guid_prefix = *dst_prefix; + } + /* "forme" is a whether the current submessage is intended for this + instance of DDSI2 and is roughly equivalent to + (dst_prefix == 0) || + (ephash_lookup_participant_guid(dst_prefix:1c1) != 0) + they are only roughly equivalent because the second term can become + false at any time. That's ok: it's real purpose is to filter out + discovery data accidentally sent by Cloud */ + rst->forme = 1; + rst->vendor = hdr->vendorid; + rst->protocol_version = hdr->version; + rst_live = 0; + ts_for_latmeas = 0; + timestamp = invalid_ddsi_timestamp; + + while (submsg <= (end - sizeof (SubmessageHeader_t))) + { + Submessage_t *sm = (Submessage_t *) submsg; + int byteswap; + unsigned octetsToNextHeader; + + if (sm->smhdr.flags & SMFLAG_ENDIANNESS) + { + byteswap = ! PLATFORM_IS_LITTLE_ENDIAN; + } + else + { + byteswap = PLATFORM_IS_LITTLE_ENDIAN; + } + if (byteswap) + { + sm->smhdr.octetsToNextHeader = bswap2u (sm->smhdr.octetsToNextHeader); + } + + octetsToNextHeader = sm->smhdr.octetsToNextHeader; + if (octetsToNextHeader != 0) + { + submsg_size = RTPS_SUBMESSAGE_HEADER_SIZE + octetsToNextHeader; + } + else if (sm->smhdr.submessageId == SMID_PAD || sm->smhdr.submessageId == SMID_INFO_TS) + { + submsg_size = RTPS_SUBMESSAGE_HEADER_SIZE; + } + else + { + submsg_size = (unsigned) (end - submsg); + } + /*LC_TRACE (("submsg_size %d\n", submsg_size));*/ + + if (submsg + submsg_size > end) + { + TRACE ((" BREAK (%u %"PRIuSIZE": %p %u)\n", (unsigned) (submsg - msg), submsg_size, msg, (unsigned) len)); + break; + } + + thread_state_awake (self); + state_smkind = sm->smhdr.submessageId; + switch (sm->smhdr.submessageId) + { + case SMID_PAD: + TRACE (("PAD")); + break; + case SMID_ACKNACK: + state = "parse:acknack"; + if (!valid_AckNack (&sm->acknack, submsg_size, byteswap)) + goto malformed; + handle_AckNack (rst, tnowE, &sm->acknack, ts_for_latmeas ? timestamp : invalid_ddsi_timestamp); + ts_for_latmeas = 0; + break; + case SMID_HEARTBEAT: + state = "parse:heartbeat"; + if (!valid_Heartbeat (&sm->heartbeat, submsg_size, byteswap)) + goto malformed; + handle_Heartbeat (rst, tnowE, rmsg, &sm->heartbeat, ts_for_latmeas ? timestamp : invalid_ddsi_timestamp); + ts_for_latmeas = 0; + break; + case SMID_GAP: + state = "parse:gap"; + /* Gap is handled synchronously in principle, but may + sometimes have to record a gap in the reorder admin. The + first case by definition doesn't need to set "rst_live", + the second one avoids that because it doesn't require the + rst after inserting the gap in the admin. */ + if (!valid_Gap (&sm->gap, submsg_size, byteswap)) + goto malformed; + handle_Gap (rst, tnowE, rmsg, &sm->gap); + ts_for_latmeas = 0; + break; + case SMID_INFO_TS: + state = "parse:info_ts"; + if (!valid_InfoTS (&sm->infots, submsg_size, byteswap)) + goto malformed; + handle_InfoTS (&sm->infots, ×tamp); + ts_for_latmeas = 1; + break; + case SMID_INFO_SRC: + state = "parse:info_src"; + if (!valid_InfoSRC (&sm->infosrc, submsg_size, byteswap)) + goto malformed; + rst = rst_cow_if_needed (&rst_live, rmsg, rst); + handle_InfoSRC (rst, &sm->infosrc); + /* no effect on ts_for_latmeas */ + break; + case SMID_INFO_REPLY_IP4: +#if 0 + state = "parse:info_reply_ip4"; +#endif + TRACE (("INFO_REPLY_IP4")); + /* no effect on ts_for_latmeas */ + break; + case SMID_INFO_DST: + state = "parse:info_dst"; + if (!valid_InfoDST (&sm->infodst, submsg_size, byteswap)) + goto malformed; + rst = rst_cow_if_needed (&rst_live, rmsg, rst); + handle_InfoDST (rst, &sm->infodst, dst_prefix); + /* no effect on ts_for_latmeas */ + break; + case SMID_INFO_REPLY: +#if 0 + state = "parse:info_reply"; +#endif + TRACE (("INFO_REPLY")); + /* no effect on ts_for_latmeas */ + break; + case SMID_NACK_FRAG: + state = "parse:nackfrag"; + if (!valid_NackFrag (&sm->nackfrag, submsg_size, byteswap)) + goto malformed; + handle_NackFrag (rst, tnowE, &sm->nackfrag); + ts_for_latmeas = 0; + break; + case SMID_HEARTBEAT_FRAG: + state = "parse:heartbeatfrag"; + if (!valid_HeartbeatFrag (&sm->heartbeatfrag, submsg_size, byteswap)) + goto malformed; + handle_HeartbeatFrag (rst, tnowE, &sm->heartbeatfrag); + ts_for_latmeas = 0; + break; + case SMID_DATA_FRAG: + state = "parse:datafrag"; + { + struct nn_rsample_info sampleinfo; + unsigned char *datap; + sampleinfo.hashash = 0; + /* valid_DataFrag does not validate the payload */ + if (!valid_DataFrag (rst, rmsg, &sm->datafrag, submsg_size, byteswap, &sampleinfo, &datap)) + goto malformed; + sampleinfo.timestamp = timestamp; + sampleinfo.reception_timestamp = tnowWC; + handle_DataFrag (rst, tnowE, rmsg, &sm->datafrag, submsg_size, &sampleinfo, datap); + rst_live = 1; + ts_for_latmeas = 0; + } + break; + case SMID_DATA: + state = "parse:data"; + { + struct nn_rsample_info sampleinfo; + unsigned char *datap; + sampleinfo.hashash = 0; + /* valid_Data does not validate the payload */ + if (!valid_Data (rst, rmsg, &sm->data, submsg_size, byteswap, &sampleinfo, &datap)) + { + goto malformed; + } + sampleinfo.timestamp = timestamp; + sampleinfo.reception_timestamp = tnowWC; + handle_Data (rst, tnowE, rmsg, &sm->data, submsg_size, &sampleinfo, datap); + rst_live = 1; + ts_for_latmeas = 0; + } + break; + + case SMID_PT_INFO_CONTAINER: + if (is_own_vendor (rst->vendor)) + { + state = "parse:pt_info_container"; + TRACE (("PT_INFO_CONTAINER(")); + if (!valid_PT_InfoContainer (&sm->pt_infocontainer, submsg_size, byteswap)) + goto malformed; + switch (sm->pt_infocontainer.id) + { + case PTINFO_ID_ENCRYPT: +#ifdef DDSI_INCLUDE_ENCRYPTION + if (q_security_plugin.decode) + { + /* we have: msg .. submsg .. submsg+submsg_size-1 submsg .. msg+len-1 + our container: data starts immediately following the pt_infocontainer */ + const size_t len1 = submsg_size - sizeof (PT_InfoContainer_t); + unsigned char * const submsg1 = submsg + sizeof (PT_InfoContainer_t); + size_t len2 = decode_container (submsg1, len1); + if ( len2 != 0 ) { + TRACE ((")\n")); + if (handle_submsg_sequence (conn, self, tnowWC, tnowE, src_prefix, dst_prefix, msg, (size_t) (submsg1 - msg) + len2, submsg1, rmsg) < 0) + goto malformed; + } + TRACE (("PT_INFO_CONTAINER END")); + } +#endif /* DDSI_INCLUDE_ENCRYPTION */ + break; + default: + TRACE (("(unknown id %u?)\n", sm->pt_infocontainer.id)); + } + } + break; + case SMID_PT_MSG_LEN: + { +#if 0 + state = "parse:msg_len"; +#endif + TRACE (("MSG_LEN(%u)", ((MsgLen_t*) sm)->length)); + break; + } + case SMID_PT_ENTITY_ID: + { +#if 0 + state = "parse:entity_id"; +#endif + TRACE (("ENTITY_ID")); + break; + } + default: + state = "parse:undefined"; + TRACE (("UNDEFINED(%x)", sm->smhdr.submessageId)); + if (sm->smhdr.submessageId <= 0x7f) + { + /* Other submessages in the 0 .. 0x7f range may be added in + future version of the protocol -- so an undefined code + for the implemented version of the protocol indicates a + malformed message. */ + if (rst->protocol_version.major < RTPS_MAJOR || + (rst->protocol_version.major == RTPS_MAJOR && + rst->protocol_version.minor <= RTPS_MINOR)) + goto malformed; + } + else if (is_own_vendor (rst->vendor)) + { + /* One wouldn't expect undefined stuff from ourselves, + except that we need to be up- and backwards compatible + with ourselves, too! */ +#if 0 + goto malformed; +#endif + } + else + { + /* Ignore other vendors' private submessages */ + } + ts_for_latmeas = 0; + break; + } + submsg += submsg_size; + TRACE (("\n")); + } + if (submsg != end) + { + state = "parse:shortmsg"; + state_smkind = SMID_PAD; + TRACE (("short (size %"PRIuSIZE" exp %p act %p)", submsg_size, submsg, end)); + goto malformed; + } + return 0; + +malformed: + + malformed_packet_received (msg, submsg, len, state, state_smkind, hdr->vendorid); + return -1; +} + +static bool do_packet +( + struct thread_state1 *self, + ddsi_tran_conn_t conn, + const nn_guid_prefix_t * guidprefix, + struct nn_rbufpool *rbpool +) +{ + /* UDP max packet size is 64kB */ + + const size_t maxsz = config.rmsg_chunk_size < 65536 ? config.rmsg_chunk_size : 65536; + const size_t ddsi_msg_len_size = 8; + const size_t stream_hdr_size = RTPS_MESSAGE_HEADER_SIZE + ddsi_msg_len_size; + ssize_t sz; + struct nn_rmsg * rmsg = nn_rmsg_new (rbpool); + unsigned char * buff; + size_t buff_len = maxsz; + Header_t * hdr; + + if (rmsg == NULL) + { + return false; + } + buff = (unsigned char *) NN_RMSG_PAYLOAD (rmsg); + hdr = (Header_t*) buff; + + if (conn->m_stream) + { + MsgLen_t * ml = (MsgLen_t*) (buff + RTPS_MESSAGE_HEADER_SIZE); + + /* + Read in packet header to get size of packet in MsgLen_t, then read in + remainder of packet. + */ + + /* Read in DDSI header plus MSG_LEN sub message that follows it */ + + sz = ddsi_conn_read (conn, buff, stream_hdr_size); + + /* Read in remainder of packet */ + + if (sz > 0) + { + int swap; + + if (ml->smhdr.flags & SMFLAG_ENDIANNESS) + { + swap = ! PLATFORM_IS_LITTLE_ENDIAN; + } + else + { + swap = PLATFORM_IS_LITTLE_ENDIAN; + } + if (swap) + { + ml->length = bswap4u (ml->length); + } + + if (ml->smhdr.submessageId != SMID_PT_MSG_LEN) + { + malformed_packet_received_nosubmsg (buff, sz, "header", hdr->vendorid); + sz = -1; + } + else + { + sz = ddsi_conn_read (conn, buff + stream_hdr_size, ml->length - stream_hdr_size); + if (sz > 0) + { + sz = (ssize_t) ml->length; + } + } + } + } + else + { + /* Get next packet */ + + sz = ddsi_conn_read (conn, buff, buff_len); + } + + if (sz > 0 && !gv.deaf) + { + nn_rmsg_setsize (rmsg, (uint32_t) sz); + assert (vtime_asleep_p (self->vtime)); + + if + ( + (size_t) sz < RTPS_MESSAGE_HEADER_SIZE || + buff[0] != 'R' || buff[1] != 'T' || buff[2] != 'P' || buff[3] != 'S' || + hdr->version.major != RTPS_MAJOR || hdr->version.minor != RTPS_MINOR + ) + { + if (NN_PEDANTIC_P) + malformed_packet_received_nosubmsg (buff, sz, "header", hdr->vendorid); + } + else + { + hdr->guid_prefix = nn_ntoh_guid_prefix (hdr->guid_prefix); + + TRACE (("HDR(%x:%x:%x vendor %u.%u) len %lu\n", + PGUIDPREFIX (hdr->guid_prefix), hdr->vendorid.id[0], hdr->vendorid.id[1], (unsigned long) sz)); + + { + handle_submsg_sequence + ( + conn, + self, + now (), + now_et (), + &hdr->guid_prefix, + guidprefix, + buff, + (size_t) sz, + buff + RTPS_MESSAGE_HEADER_SIZE, + rmsg + ); + } + } + thread_state_asleep (self); + } + nn_rmsg_commit (rmsg); + return (sz > 0); +} + +struct local_participant_desc +{ + ddsi_tran_conn_t m_conn; + nn_guid_prefix_t guid_prefix; +}; + +static int local_participant_cmp (const void *va, const void *vb) +{ + const struct local_participant_desc *a = va; + const struct local_participant_desc *b = vb; + os_handle h1 = ddsi_conn_handle (a->m_conn); + os_handle h2 = ddsi_conn_handle (b->m_conn); + return (h1 == h2) ? 0 : (h1 < h2) ? -1 : 1; +} + +static size_t dedup_sorted_array (void *base, size_t nel, size_t width, int (*compar) (const void *, const void *)) +{ + if (nel <= 1) + return nel; + else + { + char * const end = (char *) base + nel * width; + char *last_unique = base; + char *cursor = (char *) base + width; + size_t n_unique = 1; + while (cursor != end) + { + if (compar (cursor, last_unique) != 0) + { + n_unique++; + last_unique += width; + if (last_unique != cursor) + memcpy (last_unique, cursor, width); + } + cursor += width; + } + return n_unique; + } +} + +struct local_participant_set { + struct local_participant_desc *ps; + unsigned nps; + uint32_t gen; +}; + +static void local_participant_set_init (struct local_participant_set *lps) +{ + lps->ps = NULL; + lps->nps = 0; + lps->gen = os_atomic_ld32 (&gv.participant_set_generation) - 1; +} + +static void local_participant_set_fini (struct local_participant_set *lps) +{ + os_free (lps->ps); +} + +static void rebuild_local_participant_set (struct thread_state1 *self, struct local_participant_set *lps) +{ + struct ephash_enum_participant est; + struct participant *pp; + unsigned nps_alloc; + TRACE (("pp set gen changed: local %u global %"PRIu32"\n", lps->gen, os_atomic_ld32(&gv.participant_set_generation))); + thread_state_awake (self); + restart: + lps->gen = os_atomic_ld32 (&gv.participant_set_generation); + /* Actual local set of participants may never be older than the + local generation count => membar to guarantee the ordering */ + os_atomic_fence_acq (); + nps_alloc = gv.nparticipants; + os_free (lps->ps); + lps->nps = 0; + lps->ps = (nps_alloc == 0) ? NULL : os_malloc (nps_alloc * sizeof (*lps->ps)); + ephash_enum_participant_init (&est); + while ((pp = ephash_enum_participant_next (&est)) != NULL) + { + if (lps->nps == nps_alloc) + { + /* New participants may get added while we do this (or + existing ones removed), so we may have to restart if it + turns out we didn't allocate enough memory [an + alternative would be to realloc on the fly]. */ + ephash_enum_participant_fini (&est); + TRACE ((" need more memory - restarting\n")); + goto restart; + } + else + { + lps->ps[lps->nps].m_conn = pp->m_conn; + lps->ps[lps->nps].guid_prefix = pp->e.guid.prefix; + TRACE ((" pp %x:%x:%x:%x handle %"PRIsock"\n", PGUID (pp->e.guid), ddsi_conn_handle (pp->m_conn))); + lps->nps++; + } + } + ephash_enum_participant_fini (&est); + + /* There is a (very small) probability of a participant + disappearing and new one appearing with the same socket while + we are enumerating, which would cause us to misinterpret the + participant guid prefix for a directed packet without an + explicit destination. Membar because we must have completed + the loop before testing the generation again. */ + os_atomic_fence_acq (); + if (lps->gen != os_atomic_ld32 (&gv.participant_set_generation)) + { + TRACE ((" set changed - restarting\n")); + goto restart; + } + thread_state_asleep (self); + + /* The definition of the hash enumeration allows visiting one + participant multiple times, so guard against that, too. Note + that there's no requirement that the set be ordered on + socket: it is merely a convenient way of finding + duplicates. */ + if (lps->nps) + { + qsort (lps->ps, lps->nps, sizeof (*lps->ps), local_participant_cmp); + lps->nps = (unsigned) dedup_sorted_array (lps->ps, lps->nps, sizeof (*lps->ps), local_participant_cmp); + } + TRACE ((" nparticipants %u\n", lps->nps)); +} + +uint32_t listen_thread (struct ddsi_tran_listener * listener) +{ + ddsi_tran_conn_t conn; + + while (gv.rtps_keepgoing) + { + /* Accept connection from listener */ + + conn = ddsi_listener_accept (listener); + if (conn) + { + os_sockWaitsetAdd (gv.waitset, conn); + os_sockWaitsetTrigger (gv.waitset); + } + } + return 0; +} + +uint32_t recv_thread (struct nn_rbufpool * rbpool) +{ + struct thread_state1 *self = lookup_thread_state (); + struct local_participant_set lps; + unsigned num_fixed = 0; + nn_mtime_t next_thread_cputime = { 0 }; + os_sockWaitsetCtx ctx; + unsigned i; + + local_participant_set_init (&lps); + nn_rbufpool_setowner (rbpool, os_threadIdSelf ()); + + if (gv.m_factory->m_connless) + { + os_sockWaitsetAdd (gv.waitset, gv.disc_conn_uc); + os_sockWaitsetAdd (gv.waitset, gv.data_conn_uc); + num_fixed = 2; + if (config.allowMulticast) + { + os_sockWaitsetAdd (gv.waitset, gv.disc_conn_mc); + os_sockWaitsetAdd (gv.waitset, gv.data_conn_mc); + num_fixed += 2; + } + } + + while (gv.rtps_keepgoing) + { + LOG_THREAD_CPUTIME (next_thread_cputime); + + if (! config.many_sockets_mode) + { + /* no other sockets to check */ + } + else if (os_atomic_ld32 (&gv.participant_set_generation) != lps.gen) + { + /* rebuild local participant set */ + + rebuild_local_participant_set (self, &lps); + + /* and rebuild waitset */ + + os_sockWaitsetPurge (gv.waitset, num_fixed); + for (i = 0; i < lps.nps; i++) + { + if (lps.ps[i].m_conn) + { + os_sockWaitsetAdd (gv.waitset, lps.ps[i].m_conn); + } + } + } + + ctx = os_sockWaitsetWait (gv.waitset); + if (ctx) + { + int idx; + ddsi_tran_conn_t conn; + + while ((idx = os_sockWaitsetNextEvent (ctx, &conn)) >= 0) + { + bool ret; + if (((unsigned)idx < num_fixed) || ! config.many_sockets_mode) + { + ret = do_packet (self, conn, NULL, rbpool); + } + else + { + ret = do_packet (self, conn, &lps.ps[(unsigned)idx - num_fixed].guid_prefix, rbpool); + } + + /* Clean out connection if failed or closed */ + + if (! ret && ! conn->m_connless) + { + os_sockWaitsetRemove (gv.waitset, conn); + ddsi_conn_free (conn); + } + } + } + } + local_participant_set_fini (&lps); + return 0; +} diff --git a/src/core/ddsi/src/q_security.c b/src/core/ddsi/src/q_security.c new file mode 100644 index 0000000..6af39f2 --- /dev/null +++ b/src/core/ddsi/src/q_security.c @@ -0,0 +1,1758 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#ifdef DDSI_INCLUDE_ENCRYPTION + +/* Ordering of the include files is utterly irrational but works. + Don't mess with it: you'll enter Dependency Hell Territory on + WinCE. Here Be Dragons. */ + +#include "ddsi/q_security.h" +#include "ddsi/q_config.h" +#include "ddsi/q_log.h" +#include "ddsi/q_error.h" +#include "os/os_stdlib.h" +#include "os/os_process.h" +#include "os/os_thread.h" +#include "os/os_heap.h" + +#include /* for memcpy */ +#include /* for isspace */ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ddsi/sysdeps.h" + +/* We don't get FILENAME_MAX on WinCE but can't put it in the abstraction + * without complications to the examples so here we go: */ + +#ifndef FILENAME_MAX +#ifdef WINCE +#define FILENAME_MAX 260 +#endif +#endif + +/* Supported URI schema by parser */ +#define URI_FILESCHEMA "file://" + +/* The max. key-length is defined by crypto-lib, here set to 32 bytes by + * OpenSSL */ +#define Q_MAX_KEY_LENGTH EVP_MAX_KEY_LENGTH + +/* The block-size defines the range of sequence-counter, it varies per + * cipher */ +#define Q_BLOWFISH_BLOCK_SIZE (8) +#define Q_AES_BLOCK_SIZE (16) +#define Q_BLOCK_SIZE_MAX (16) + +/** The counter length corresponds to the specific blocksize */ +#define Q_NULL_COUNTER_SIZE (0L) +#define Q_BLOWFISH_COUNTER_SIZE Q_BLOWFISH_BLOCK_SIZE +#define Q_AES_COUNTER_SIZE Q_AES_BLOCK_SIZE + +/* Define macros for 20 bytes digest, but as a single bit-flip shall change + * half of the digests bits, only 12 bytes of them are transfered in security + * header (CHECKME, shall we take lower 12 bytes or higher 12 bytes?) + * Assure the used digest length is 4bytes alligned, to ensure proper alignment of headersize*/ +#define Q_DIGEST_LENGTH (SHA_DIGEST_LENGTH) +#define Q_DIGEST_LENGTH_2 (12) +#define Q_SHA1 SHA1 + +/* For future usage, byte-size of unique value to define lower 4 bytes of each + * counter, shall be chosen randomly */ +#define Q_KEYID_LENGTH 4 + +#define Q_REPORT_OPENSSL_ERR(x) \ +while ( ERR_peek_error() ) \ + NN_ERROR(x "%s", ERR_error_string(ERR_get_error(), NULL)); + + + +typedef unsigned char q_sha1Digest[SHA_DIGEST_LENGTH]; /* 20 bytes + '\0' */ + + +C_STRUCT(q_sha1Header) { + unsigned char hash[Q_DIGEST_LENGTH_2]; /* hash over body and security attributes */ + /* ----- up to here encrypted ------*/ +}; + + +/* class declarations */ +C_CLASS(q_nullHeader); +C_CLASS(q_blowfishHeader); +C_CLASS(q_aesHeader); + + +/* structure declarations */ +C_STRUCT(q_nullHeader) { + q_cipherType cipherType; /* network order */ /* OPTME */ + os_uint32 partitionId; /* network order */ +}; + +C_STRUCT(q_blowfishHeader) { + unsigned char keyId[Q_KEYID_LENGTH]; /* required for re-keying (reserved for future usage) */ + unsigned char counter[Q_BLOWFISH_BLOCK_SIZE]; /* cipher block length */ + q_cipherType cipherType; /* network order */ /* OPTME */ + os_uint32 partitionId; /* network order */ +}; + +C_STRUCT(q_aesHeader) { + unsigned char keyId[Q_KEYID_LENGTH]; /* required for re-keying (reserved for future usage)*/ + unsigned char counter[Q_AES_BLOCK_SIZE]; /* cipher block length */ + q_cipherType cipherType; /* network order */ /* OPTME */ + os_uint32 partitionId; /* network order */ +}; + +/* To prevent fragmentations of heap and costly pointer dereferencing with + * possible lot of cache-misses, we declare a union that allows us to allocate + * a single array of codecs, each entry realizing a different cipher and + * header-size. */ +C_STRUCT(q_securityHeader) { + union { + C_STRUCT(q_nullHeader) null; /* obsolete */ + C_STRUCT(q_blowfishHeader) blowfish; + C_STRUCT(q_aesHeader) aes; + } u; +}; + + +/* a number of error states, each codec within the set can be in: + * + * Q_CODEC_STATE_OK: everything is Ok, encryption/decryption is performed + * + * Q_CODEC_STATE_REQUIRES_REKEYING: same as Q_CODEC_STATE_OK, but codec has + * reached a state which requires re-keying, shall be used as signal for codec + * manegement + * + * Q_CODEC_STATE_DROP_TEMP: drop encodings/decodings temporarily, caused by + * faulty cipher-keys read form file. Updating the key-file will solve the + * problem and release the block. + * + * Q_CODEC_STATE_DROP_PERM: drop encodings/decodings permanently, caused by + * 'un-connected' partitions, or faulty security-profiles, eg. un-known + * cipher. +*/ +typedef enum { + Q_CODEC_STATE_OK=0, + Q_CODEC_STATE_REQUIRES_REKEYING=1, + Q_CODEC_STATE_DROP_TEMP=2, + Q_CODEC_STATE_DROP_PERM=4 +} q_securityCodecState; + +/* if not 0, the codec is blocked */ +#define IS_DROP_STATE(state) \ + ((state)&(Q_CODEC_STATE_DROP_TEMP|Q_CODEC_STATE_DROP_PERM)) + +/* declaration of codec */ +C_CLASS(q_securityPartitionDecoder); +C_CLASS(q_securityPartitionEncoder); + +C_STRUCT(q_securityPartitionDecoder) { + q_securityCodecState state; + char *cipherKeyURL; + q_cipherType cipherType; + os_char * partitionName; + EVP_CIPHER_CTX cipherContext; + /* this codec does hold state and therfor does not require securityHeader + * attributes */ +}; + + +C_STRUCT(q_securityPartitionEncoder){ + q_securityCodecState state; + char *cipherKeyURL; + q_cipherType cipherType; + os_char * partitionName; + EVP_CIPHER_CTX cipherContext; + /* The current state will be appendend to message and will be used by + * receiver to decrypt the message in question. To avoid cache misses + * store the current state close to EVP_CIPHER_CTX */ + C_STRUCT(q_securityHeader) securityHeader; /* holds the state */ +}; + + +C_STRUCT(q_securityDecoderSet) { + os_uint32 nofPartitions; + q_securityPartitionDecoder decoders; +}; + +C_STRUCT(q_securityEncoderSet) { + os_uint32 nofPartitions; + os_uint32 headerSizeMax; + q_securityPartitionEncoder encoders; +}; + + +#if 1 +/* no dumping of buffer before and after en-/decrypting */ +#define DUMP_BUFFER(partitionName,chan,buffer,length,label,counter,counterLength, bufferLength) +#else +/* Use this line to dump to the ddsi tracefile */ +#define DUMP_BUFFER(partitionName,chan,buffer,length,label,counter,counterLength,bufferLenght) tdumpBuffer(partitionName,chan,buffer,length,label,counter,counterLength, bufferLength) +/* Use this line to dump to a file in the /tmp/directory */ +#define DUMP_BUFFER(partitionName,chan,buffer,length,label,counter,counterLength,bufferLenght) dumpBuffer(partitionName,chan,buffer,length,label,counter,counterLength, bufferLength) +#endif + +/** private operations */ + +#if 0 +static void dumpCounter (FILE *of, unsigned char* counter, int counterLength) +{ + int i = 0; + if (counter) { + for (i=0; iheaderSizeMax; +} + + +static c_bool decoderIsBlocked (q_securityPartitionDecoder codec) +{ + return (IS_DROP_STATE(codec->state) > 0); +} + +static c_bool encoderIsBlocked (q_securityPartitionEncoder codec) +{ + return (IS_DROP_STATE(codec->state) > 0); +} + +/* returns -1 on error */ +static short hex2bin(char hexChar) +{ + switch (hexChar) { + case '0': return 0; + case '1': return 1; + case '2': return 2; + case '3': return 3; + case '4': return 4; + case '5': return 5; + case '6': return 6; + case '7': return 7; + case '8': return 8; + case '9': return 9; + + case 'a': return 10; + case 'b': return 11; + case 'c': return 12; + case 'd': return 13; + case 'e': return 14; + case 'f': return 15; + + case 'A': return 10; + case 'B': return 11; + case 'C': return 12; + case 'D': return 13; + case 'E': return 14; + case 'F': return 15; + default: + return -1; /* error */ + } +} + +/* returns NULL on error, eg bad hex-string */ +static c_bool hex2key +( + const char* hexKey, + os_uint32 expectedLength, + unsigned char *result /* out */ ) +{ + size_t i, len=0; + short val; + + len = strlen(hexKey); + + for (i=0; i 0) return "unknown"; + else return "ok"; +} + +/* this function is based on original code of + * components/configuration/parser/code/cfg_parser.y */ + +static c_bool q_securityResolveCipherKeyFromUri +( + const char *uriStr, + os_uint32 expectedLength, + unsigned char *cipherKey /* out buffer */ +) +{ + char *filename; + FILE *file = NULL; + char readBuffer[256]; /*at most strings of 255 chars */ + char *hexStr = NULL; + int ret; + c_bool result = FALSE; + + if ((uriStr != NULL) && + (strncmp(uriStr, URI_FILESCHEMA, strlen(URI_FILESCHEMA)) == 0)) { + + /* TBD: compare file-permissions with uid/gid of this process, the + * file should be protected against read/write by others, otherwise we + * should refuse to read from it */ + const char *justPath = + (char *)(uriStr + strlen(URI_FILESCHEMA)); + + filename = os_strdup (justPath); + file = fopen(filename, "r"); + if (file) { + /* read at most 255 chars from file, this should suffice if the + * secret key has atmost 32 chars */ + ret = fscanf (file, "%255s", readBuffer); + + if (ret != EOF) + { + /* skip leading white spaces */ + for (hexStr=readBuffer; + isspace((unsigned char) *hexStr); + ++hexStr); + + result = hex2key(hexStr, expectedLength, cipherKey); + } + + fclose(file); + } else { + NN_ERROR("q_securityResolveCipherKeyFromUri: Could not open %s",uriStr); + } + + os_free(filename); + + } else if (uriStr != NULL) { + /* seems to be a hex string */ + result = hex2key(uriStr, expectedLength, cipherKey); + } + + return result; +} + + +/* Validate the cipherkey, parsing the hex-string directly or the content of + * file */ +static c_bool q_securityIsValidCipherKeyUri +( + q_cipherType cipherType, + const char* cipherKeyUri +) +{ + unsigned char tmpCipherKey[Q_MAX_KEY_LENGTH]; /* transient */ + os_uint32 expectedLength = q_securityCipherKeyLength(cipherType); + + assert(expectedLength > 0); + + /* we are not interested in the key, jsut doing the syntax check */ + return q_securityResolveCipherKeyFromUri(cipherKeyUri, expectedLength, tmpCipherKey); +} + +/* compare cipherName to known identifiers, comparison is case-insensitive */ +static c_bool q_securityCipherTypeFromString(const char* cipherName, + q_cipherType *cipherType) /* out */ +{ + if (cipherName == NULL) + { + NN_ERROR("q_securityCipherTypeFromString:internal error, empty cipher string"); + *cipherType = Q_CIPHER_UNDEFINED; + return FALSE; + } + + if (os_strcasecmp(cipherName, "null") == 0) { + *cipherType = Q_CIPHER_NULL; + } else if (os_strcasecmp(cipherName, "blowfish") == 0 || + os_strcasecmp(cipherName, "blowfish-sha1") == 0) { + *cipherType = Q_CIPHER_BLOWFISH; + } else if (os_strcasecmp(cipherName, "aes128") == 0 || + os_strcasecmp(cipherName, "aes128-sha1") == 0) { + *cipherType = Q_CIPHER_AES128; + } else if (os_strcasecmp(cipherName, "aes192") == 0 || + os_strcasecmp(cipherName, "aes192-sha1") == 0) { + *cipherType = Q_CIPHER_AES192; + } else if (os_strcasecmp(cipherName, "aes256") == 0 || + os_strcasecmp(cipherName, "aes256-sha1") == 0) { + *cipherType = Q_CIPHER_AES256; +#if 0 + } else if (os_strcasecmp(cipherName, "rsa-null") == 0) { + *cipherType = Q_CIPHER_RSA_WITH_NULL; + } else if (os_strcasecmp(cipherName, "rsa-blowfish") == 0 || + os_strcasecmp(cipherName, "rsa-blowfish-sha1") == 0) { + *cipherType = Q_CIPHER_RSA_WITH_BLOWFISH; + } else if (os_strcasecmp(cipherName, "rsa-aes128") == 0 || + os_strcasecmp(cipherName, "rsa-aes128-sha1") == 0) { + *cipherType = Q_CIPHER_RSA_WITH_AES128; + } else if (os_strcasecmp(cipherName, "rsa-aes192") == 0 || + os_strcasecmp(cipherName, "rsa-aes192-sha1") == 0) { + *cipherType = Q_CIPHER_RSA_WITH_AES192; + } else if (os_strcasecmp(cipherName, "rsa-aes256") == 0 || + os_strcasecmp(cipherName, "rsa-aes256-sha1") == 0) { + *cipherType = Q_CIPHER_RSA_WITH_AES256; +#endif + } else { + *cipherType = Q_CIPHER_UNDEFINED; + return FALSE; + } + return TRUE; +} + +static os_uint32 cipherTypeToHeaderSize(q_cipherType cipherType) { + switch (cipherType) { + case Q_CIPHER_UNDEFINED: + case Q_CIPHER_NONE: + return 0; + case Q_CIPHER_NULL: + return sizeof(C_STRUCT(q_nullHeader)); + + case Q_CIPHER_BLOWFISH: + return sizeof(C_STRUCT(q_sha1Header)) + + sizeof(C_STRUCT(q_blowfishHeader)); + + case Q_CIPHER_AES128: + case Q_CIPHER_AES192: + case Q_CIPHER_AES256: + return sizeof(C_STRUCT(q_sha1Header)) + + sizeof(C_STRUCT(q_aesHeader)); + + default: + assert(0 && "unsupported cipher"); + } + + assert(FALSE); + return 0; +} + +/*these two methods are not static for the moment because of tests*/ + +static +void q_securityRNGSeed (void) +{ + os_time time=os_timeGetMonotonic(); + RAND_seed((const void *)&time.tv_nsec,sizeof(time.tv_nsec)); +} + +static +void q_securityRNGGetRandomNumber(int number_length,unsigned char * randNumber) +{ + RAND_bytes(randNumber,number_length); +} + + +static +c_bool +q_securityPartitionEncoderInit(q_securityPartitionEncoder encoder,struct config_networkpartition_listelem *p) +{ + unsigned char cipherKey[Q_MAX_KEY_LENGTH]; + char *cipherKeyURL = p->securityProfile?p->securityProfile->key:NULL; + os_char * partitionName = p->name; + c_bool connected = (c_bool) p->connected; + q_cipherType cipherType = p->securityProfile?p->securityProfile->cipher: Q_CIPHER_NONE; + os_uint32 hash = p->partitionHash; + os_uint32 partitionId = p->partitionId; + + /* init */ + memset(encoder, 0, sizeof(*encoder)); + memset(cipherKey, 0, sizeof(*cipherKey)); + + + if (!connected) { + TRACE(("Network Partition '%s' (%d) not connected, dropping outbound traffic permanently", partitionName, partitionId)); + + encoder->state = Q_CODEC_STATE_DROP_PERM; + encoder->cipherType = Q_CIPHER_UNDEFINED; + encoder->partitionName = partitionName; + + return TRUE; + } + + assert(cipherType != Q_CIPHER_UNDEFINED); + + { + /* init the cipher */ + + const os_uint32 partitionHashNetworkOrder = htonl(hash); + const q_cipherType cipherTypeNetworkOrder = htonl(cipherType); + const unsigned char *iv = NULL; /* ignored by ECBs ciphers */ + const EVP_CIPHER *cipher = NULL; + const os_uint32 cipherKeyLength = q_securityCipherKeyLength(cipherType); + unsigned char randCounter[Q_KEYID_LENGTH]; + + /*TRACE(("Security Encoder init: partition '%s' (%d) (connected), cipherType %d, cipherKey %s\n", + partitionName,partitionId, cipherType, cipherKeyURL));*/ + + encoder->state = Q_CODEC_STATE_OK; + encoder->cipherKeyURL = cipherKeyURL; /* const, required for re-keying */ + encoder->cipherType = cipherType; + encoder->partitionName = partitionName; /* const */ + + q_securityRNGGetRandomNumber(Q_KEYID_LENGTH, randCounter); + + switch (cipherType) { + case Q_CIPHER_NULL: + case Q_CIPHER_NONE: + cipher = EVP_enc_null(); + encoder->securityHeader.u.null.cipherType = cipherTypeNetworkOrder; + encoder->securityHeader.u.null.partitionId = partitionHashNetworkOrder; + break; + + case Q_CIPHER_BLOWFISH: + cipher = EVP_bf_ecb(); + assert(8 == EVP_CIPHER_block_size(cipher)); + assert(16 == cipherKeyLength); + + encoder->securityHeader.u.blowfish.cipherType = cipherTypeNetworkOrder; + encoder->securityHeader.u.blowfish.partitionId = partitionHashNetworkOrder; + + memcpy(encoder->securityHeader.u.blowfish.counter,&randCounter, sizeof(randCounter)); + break; + + case Q_CIPHER_AES128: + cipher = EVP_aes_128_ecb(); + assert(16 == EVP_CIPHER_block_size(cipher)); + assert(16 == cipherKeyLength); + + encoder->securityHeader.u.aes.cipherType = cipherTypeNetworkOrder; + encoder->securityHeader.u.aes.partitionId = partitionHashNetworkOrder; + + memcpy(encoder->securityHeader.u.aes.counter, &randCounter, sizeof(randCounter)); + break; + + case Q_CIPHER_AES192: + cipher = EVP_aes_192_ecb(); + + assert(16 == EVP_CIPHER_block_size(cipher)); + assert(24 == cipherKeyLength); + + encoder->securityHeader.u.aes.cipherType = cipherTypeNetworkOrder; + encoder->securityHeader.u.aes.partitionId = partitionHashNetworkOrder; + + memcpy(encoder->securityHeader.u.aes.counter, &randCounter, sizeof(randCounter)); + break; + + case Q_CIPHER_AES256: + cipher = EVP_aes_256_ecb(); + + assert(16 == EVP_CIPHER_block_size(cipher)); + assert(32 == cipherKeyLength); + + encoder->securityHeader.u.aes.cipherType = cipherTypeNetworkOrder; + encoder->securityHeader.u.aes.partitionId = partitionHashNetworkOrder; + + memcpy(encoder->securityHeader.u.aes.counter, &randCounter, sizeof(randCounter)); + break; + + default: + assert(0 && "never reach"); + } + + /* intitialize the key-buffer */ + if (cipherType != Q_CIPHER_NULL && cipherType != Q_CIPHER_NONE && + !q_securityResolveCipherKeyFromUri(cipherKeyURL,cipherKeyLength,cipherKey)) { + NN_ERROR("DDSI Security Encoder: dropping traffic of partition '%s' (%d) due to invalid cipher key", + encoder->partitionName, partitionId); + encoder->state = Q_CODEC_STATE_DROP_TEMP; + } + + EVP_CIPHER_CTX_init(&(encoder->cipherContext)); + + EVP_EncryptInit_ex(&(encoder->cipherContext), + cipher, + NULL, + cipherKey, + iv); /* IV is ignored by ECB ciphers */ + } + + return TRUE; +} + + +static +c_bool +q_securityPartitionEncoderFini(q_securityPartitionEncoder encoder) +{ + if (encoder->cipherType != Q_CIPHER_UNDEFINED) { + /* release the cipher */ + EVP_CIPHER_CTX *ctx = NULL; + + ctx = &(encoder->cipherContext); + + EVP_CIPHER_CTX_cleanup(ctx); + } + + return TRUE; +} + +static +c_bool +q_securityPartitionDecoderInit(q_securityPartitionDecoder decoder,struct config_networkpartition_listelem *p) +{ + unsigned char cipherKey[Q_MAX_KEY_LENGTH]; + char *cipherKeyURL = p->securityProfile?p->securityProfile->key:NULL; + os_char * partitionName = p->name; + c_bool connected = (c_bool) p->connected; + q_cipherType cipherType = p->securityProfile?p->securityProfile->cipher: Q_CIPHER_NONE; + os_uint32 partitionId = p->partitionId; + + + /* init */ + memset(decoder, 0, sizeof(*decoder)); + memset(cipherKey, 0, sizeof(cipherKey)); + + if (!connected) { + TRACE(("Network Partition '%s' (%d) not connected, dropping inbound traffic permanently\n", partitionName, partitionId)); + + decoder->state = Q_CODEC_STATE_DROP_PERM; + decoder->cipherType = Q_CIPHER_UNDEFINED; + decoder->partitionName = partitionName; + return TRUE; + } + + + assert(cipherType != Q_CIPHER_UNDEFINED); + + { + /* init the cipher */ + const unsigned char *iv = NULL; /* ignored by ECBs ciphers */ + const EVP_CIPHER *cipher = NULL; + const os_uint32 cipherKeyLength = q_securityCipherKeyLength(cipherType); + + /*TRACE(("Security Decoder init: partition '%s' (%d) (connected), cipherType %d, cipherKey %s \n", + partitionName,partitionId, cipherType, cipherKeyURL));*/ + + decoder->state = Q_CODEC_STATE_OK; + decoder->cipherKeyURL = cipherKeyURL; /* const, required for re-keying */ + decoder->cipherType = cipherType; + decoder->partitionName = partitionName; /* const */ + + switch (cipherType) { + case Q_CIPHER_NULL: + case Q_CIPHER_NONE: + cipher = EVP_enc_null(); + break; + + case Q_CIPHER_BLOWFISH: + cipher = EVP_bf_ecb(); + + assert(8 == EVP_CIPHER_block_size(cipher)); + assert(16 == cipherKeyLength); + break; + + case Q_CIPHER_AES128: + cipher = EVP_aes_128_ecb(); + + assert(16 == EVP_CIPHER_block_size(cipher)); + assert(16 == cipherKeyLength); + break; + + case Q_CIPHER_AES192: + cipher = EVP_aes_192_ecb(); + + assert(16 == EVP_CIPHER_block_size(cipher)); + assert(24 == cipherKeyLength); + break; + + case Q_CIPHER_AES256: + cipher = EVP_aes_256_ecb(); + + assert(16 == EVP_CIPHER_block_size(cipher)); + assert(32 == cipherKeyLength); + break; + + default: + assert(0 && "never reach"); + } + + /* init key-buffer from URL */ + if (cipherType != Q_CIPHER_NULL && cipherType != Q_CIPHER_NONE && + !q_securityResolveCipherKeyFromUri(cipherKeyURL,cipherKeyLength,cipherKey)) { + NN_ERROR("DDSI Security Decoder: dropping traffic of partition '%s' (%d) due to invalid cipher key", + decoder->partitionName, partitionId); + /* can be solved by re-keying, rewriting the file */ + decoder->state = Q_CODEC_STATE_DROP_TEMP; + } + + EVP_CIPHER_CTX_init(&(decoder->cipherContext)); + + EVP_EncryptInit_ex(&(decoder->cipherContext), + cipher, + NULL, + cipherKey, + iv); /* IV is ignored by ECB ciphers */ + + } + + return TRUE; +} + + +static c_bool q_securityPartitionDecoderFini (q_securityPartitionDecoder decoder) +{ + if (decoder->cipherType != Q_CIPHER_UNDEFINED) { + /* release the cipher */ + EVP_CIPHER_CTX *ctx = NULL; + + ctx = &(decoder->cipherContext); + + EVP_CIPHER_CTX_cleanup(ctx); + } + + return 1; /* true */ +} + +/* returns NULL on error */ + +static q_securityEncoderSet q_securityEncoderSetNew (void) +{ + const os_uint32 nofPartitions = config.nof_networkPartitions; + + q_securityEncoderSet result = + os_malloc(sizeof(C_STRUCT(q_securityEncoderSet))); + + if (!result) { + return NULL; + } + + result->nofPartitions = 0; /* init */ + result->headerSizeMax = 0; + if (nofPartitions == 0) { + result->encoders = NULL; + } else { + result->encoders = os_malloc(sizeof(C_STRUCT(q_securityPartitionEncoder)) * nofPartitions); + memset(result->encoders, + 0, + sizeof(C_STRUCT(q_securityPartitionEncoder)) * + nofPartitions); + } + + /* if not done yet, init the RNG within this thread*/ + if(!RAND_status()) { + q_securityRNGSeed(); + } + + /* init each codec per network parition */ + { + q_securityPartitionEncoder currentEncoder = NULL; + os_uint32 headerSizeProfile = 0; + + struct config_networkpartition_listelem *p = config.networkPartitions; + + while (p) { + + currentEncoder = + &(result->encoders[p->partitionId-1]); + + if (p->securityProfile) { + if (!q_securityPartitionEncoderInit(currentEncoder,p)) { + /* the codec config is faulty, the codec has been set into + * DROP_TERMP or DROP_PERM state, depending on the kind of + * fault. Continue to intitialize remaining codecs */ + NN_ERROR("q_securityEncoderSet:failed to initialize codec of partition '%s' (%d)\n", + p->name,p->partitionId ); + } + + TRACE(("Network Partition '%s' (%d) encoder secured by %s cipher, in status '%s'\n", + p->name, + p->partitionId, + cipherTypeAsString(currentEncoder->cipherType), + stateAsString(currentEncoder->state))); + + headerSizeProfile = cipherTypeToHeaderSize(currentEncoder->cipherType); + result->headerSizeMax = (headerSizeProfile > (result->headerSizeMax)) ? headerSizeProfile: (result->headerSizeMax); + result->headerSizeMax = (result->headerSizeMax + 4u) & ~4u; /* enforce mutiple of 4 */ + } else { + memset(currentEncoder, 0, sizeof(*currentEncoder)); + currentEncoder->state = Q_CODEC_STATE_DROP_PERM; + currentEncoder->cipherType = Q_CIPHER_NONE; + currentEncoder->partitionName = p->name; + + TRACE(("Network Partition '%s' (%d) is not secured by a cipher\n", + p->name, + p->partitionId)); + } + /* count up step by step, so in case of error + * q_securityEncoderSetFree will only iterate those already + * intialized */ + ++(result->nofPartitions); + p = p->next; + + } + + + TRACE(("reserving %d bytes for security header\n", result->headerSizeMax)); + + } + + return result; +} + +/* returns NULL on error */ + +static q_securityDecoderSet q_securityDecoderSetNew (void) +{ + q_securityDecoderSet result; + const os_uint32 nofPartitions = config.nof_networkPartitions; + + if (nofPartitions == 0) + { + return NULL; + } + + result = os_malloc (sizeof(C_STRUCT(q_securityDecoderSet))); + result->nofPartitions = 0; + + result->decoders = + os_malloc(sizeof(C_STRUCT(q_securityPartitionDecoder)) * nofPartitions); + + /* init the memory region */ + memset(result->decoders, + 0, + sizeof(C_STRUCT(q_securityPartitionDecoder)) * + nofPartitions); + + /* if not done yet, init the RNG within this thread*/ + if(!RAND_status()) { + q_securityRNGSeed(); + } + + /* init codec per network partition */ + { + q_securityPartitionDecoder currentDecoder = NULL; + + struct config_networkpartition_listelem *p = config.networkPartitions; + + while (p) { + currentDecoder = + &(result->decoders[p->partitionId-1]); + if ( p->securityProfile ) { + if (!q_securityPartitionDecoderInit(currentDecoder,p)) { + /* the codec config is faulty, the codec has been set into + * DROP_TERMP or DROP_PERM state, depending on the kind of + * fault. Continue to intitialize remaining codecs */ + NN_ERROR("q_securityDecoderSet:failed to initialize codec of partition '%s' (%d)\n", + p->name,p->partitionId); + } + + TRACE(("Network Partition '%s' (%d) decoder secured by %s cipher, in status '%s'\n", + p->name, + p->partitionId, + cipherTypeAsString(currentDecoder->cipherType), + stateAsString(currentDecoder->state))); + } else { + memset(currentDecoder, 0, sizeof(*currentDecoder)); + currentDecoder->state = Q_CODEC_STATE_DROP_PERM; + currentDecoder->cipherType = Q_CIPHER_NONE; + currentDecoder->partitionName = p->name; + + TRACE(("Network Partition '%s' (%d) is not secured by a cipher\n", + p->name, + p->partitionId)); + } + /* count up step by step, so in case of error + * q_securityEncoderSetFree will only iterate those already + * intialized */ + ++(result->nofPartitions); + p = p->next; + } + } + + return result; + +} + +static c_bool q_securityEncoderSetFree (q_securityEncoderSet codec) +{ + q_securityPartitionEncoder currentEncoder = NULL; + os_uint32 ix; + + if (!codec) { + /* parameter is NULL */ + return TRUE; + } + + for (ix=0; ixnofPartitions; ++ix) { + currentEncoder = + &(codec->encoders[ix]); + + q_securityPartitionEncoderFini(currentEncoder); + } + os_free(codec->encoders); + os_free(codec); + + return 1; /* true */ +} + +static c_bool q_securityDecoderSetFree (q_securityDecoderSet codec) +{ + q_securityPartitionDecoder currentDecoder = NULL; + os_uint32 ix; + + if (!codec) { + /* parameter is NULL */ + return TRUE; + } + + for (ix=0; ixnofPartitions; ++ix) { + currentDecoder = + &(codec->decoders[ix]); + + q_securityPartitionDecoderFini(currentDecoder); + } + os_free(codec->decoders); + os_free(codec); + + return TRUE; /* true */ +} + +static os_uint32 q_securityEncoderHeaderSize (q_securityEncoderSet codec, os_uint32 partitionId) +{ + assert(partitionId > 0); + if (!codec) { + /* security not initialized or disabled */ + return 0; + } + assert(partitionId <= codec->nofPartitions); + return cipherTypeToHeaderSize(codec->encoders[partitionId-1].cipherType); +} + +static q_cipherType q_securityEncoderCipherType (q_securityEncoderSet codec, os_uint32 partitionId) +{ + assert(partitionId > 0); + if (!codec) { + /* security not initialized or disabled */ + return 0; + } + assert(partitionId <= codec->nofPartitions); + return codec->encoders[partitionId-1].cipherType; +} + + + +/* returns 0 on error, otherwise 1, */ +static c_bool counterEncryptOrDecryptInPlace +( + EVP_CIPHER_CTX *ctx, + unsigned char *counter, /* in/out */ + unsigned char *buffer, + int length +) +{ + int i, j, num; + int where = 0; + int bl = EVP_CIPHER_CTX_block_size(ctx); + unsigned char keyStream[Q_BLOCK_SIZE_MAX]; + + /* <= is correct, so that we handle any possible non-aligned data */ + for (i = 0; i <= length / bl && where < length; ++i) { + /* encrypt the current counter */ + if (!EVP_EncryptUpdate(ctx, keyStream, &num, counter, bl)) { /* ECB encrypts exactly 'bl' bytes */ + + NN_WARNING("Incoming encrypted sub-message dropped: Decrypt failed (bufferLength %u, blockSize %u, where %u)",length, bl, where); + + return FALSE; + } + + /* use the keystream to encrypt a single block of buffer */ + { + if ( ((int) (length - where)) < bl) { + /* non aligned data, encrypt remaining block-fragment only */ + for (j = 0; j < ((int) (length - where)); ++j) { + buffer[where+j] ^= keyStream[j]; + } + } else { + /* default case, encrypt full block */ + for (j = 0; j < bl; ++j) { + buffer[where+j] ^= keyStream[j]; + } + } + } + + /* increment the counter, remember it's an array of single characters */ + for (j = Q_KEYID_LENGTH; j < bl; ++j) { /*the four first bytes=random value. It is kept unchanged */ + if (++(counter[j])) + break; + } + + where += num; + } + + return TRUE; +} + + +static void +attachHeaderAndDoSha1(unsigned char* data, os_uint32 dataLength, + const void *symCipherHeader, os_uint32 symCipherHeaderLength) +{ + const os_uint32 sha1HeaderLength = sizeof(C_STRUCT(q_sha1Header)); + const os_uint32 overallLength = dataLength + + sha1HeaderLength + + symCipherHeaderLength; + + unsigned char md[Q_DIGEST_LENGTH]; + + /* put the fixed attributes into buffer to calculate the digest */ + void *digStart = &(data[dataLength]); + void *cipStart = &(data[dataLength + sha1HeaderLength]); + + memset(digStart, 0, sha1HeaderLength); /* zero out */ + memcpy(cipStart, symCipherHeader, symCipherHeaderLength); + + /* calculate over complete send buffer */ + Q_SHA1(data, overallLength, md); + + /* Finally place the (half of) digest */ + memcpy(digStart, md, Q_DIGEST_LENGTH_2); +} + +static void +attachHeader(unsigned char* data, os_uint32 dataLength, + const void *symCipherHeader, os_uint32 symCipherHeaderLength) +{ + /* pur the fixed attributes into buffer to calculate the digest */ + void *cipStart = &(data[dataLength]); + memcpy(cipStart, symCipherHeader, symCipherHeaderLength); +} + +static c_bool +verifySha1(unsigned char* data, os_uint32 dataLength, void *digStart) +{ + const os_uint32 sha1HeaderLength = sizeof(C_STRUCT(q_sha1Header)); + C_STRUCT(q_sha1Header) sha1Header; + unsigned char md[Q_DIGEST_LENGTH]; + + /* backup the sha1 digest */ + memcpy(&sha1Header, digStart, sha1HeaderLength); + + /* zero out the bytes in buffer */ + memset(digStart, 0, sha1HeaderLength); + + /* verify digest */ + Q_SHA1(data, dataLength, md); + + return !memcmp(md, sha1Header.hash, Q_DIGEST_LENGTH_2); +} + +static c_bool q_securityEncodeInPlace_Generic +( + q_securityPartitionEncoder encoder, + os_uint32 partitionId, /* debugging */ + unsigned char *buffer, + os_uint32 *dataLength, /* in/out */ + os_uint32 bufferLength /* for debug */ +) +{ + const os_uint32 overallHeaderSize = cipherTypeToHeaderSize(encoder->cipherType); + + EVP_CIPHER_CTX *ctx = &(encoder->cipherContext); + unsigned char *plainText = buffer; + os_uint32 plainTextLength = *dataLength; + c_bool result = TRUE; + + + TRACE((":ENCRYPT:'%s'(%d)",encoder->partitionName, partitionId)); + + (void) bufferLength; + + switch (encoder->cipherType) { + case Q_CIPHER_NULL: { + const os_uint32 symCipherHeaderLength = sizeof(C_STRUCT(q_nullHeader)); + + q_nullHeader symCipherHeader = &((encoder->securityHeader).u.null); + + DUMP_BUFFER(encoder->partitionName, partitionId, buffer, *dataLength, "encode->", + NULL, Q_NULL_COUNTER_SIZE, bufferLength); + + attachHeader(plainText, plainTextLength, + symCipherHeader, symCipherHeaderLength); + + TRACE((":NULL:%s", result?"OK":"ERROR")); /* debug */ + + DUMP_BUFFER(encoder->partitionName, partitionId, buffer, *dataLength+overallHeaderSize, "<-encode", NULL, + Q_NULL_COUNTER_SIZE, bufferLength); + } + break; + + case Q_CIPHER_BLOWFISH: { + const os_uint32 symCipherHeaderLength = sizeof(C_STRUCT(q_blowfishHeader)); + q_blowfishHeader symCipherHeader = &((encoder->securityHeader).u.blowfish); + unsigned char *counter = symCipherHeader->counter; + + DUMP_BUFFER(encoder->partitionName, partitionId, buffer, *dataLength, "encode->", counter, + Q_BLOWFISH_COUNTER_SIZE, bufferLength); + + attachHeaderAndDoSha1(plainText, plainTextLength, + symCipherHeader, symCipherHeaderLength); + + /* encrypt load and digest */ + result = counterEncryptOrDecryptInPlace(ctx, + counter, + plainText, + (int) (plainTextLength + sizeof(C_STRUCT(q_sha1Header)))); + + TRACE((":BLF:%s", result?"OK":"ERROR")); /* debug */ + + DUMP_BUFFER(encoder->partitionName, partitionId, buffer, *dataLength+overallHeaderSize, "<-encode", counter, + Q_BLOWFISH_COUNTER_SIZE, bufferLength); + } + break; + + case Q_CIPHER_AES128: + case Q_CIPHER_AES192: + case Q_CIPHER_AES256: { + const os_uint32 symCipherHeaderLength = sizeof(C_STRUCT(q_aesHeader)); + q_aesHeader symCipherHeader = &((encoder->securityHeader).u.aes); + unsigned char *counter = symCipherHeader->counter; + + DUMP_BUFFER(encoder->partitionName, partitionId, buffer, *dataLength, "encode->", counter, + Q_AES_COUNTER_SIZE, bufferLength); + + attachHeaderAndDoSha1(plainText, plainTextLength, + symCipherHeader, symCipherHeaderLength); + + /* encrypt load and digest */ + result = counterEncryptOrDecryptInPlace(ctx, + counter, + plainText, + (int) (plainTextLength + sizeof(C_STRUCT(q_sha1Header)))); + + TRACE((":AES:%s", result?"OK":"ERROR")); /* debug */ + + DUMP_BUFFER(encoder->partitionName, partitionId, buffer, *dataLength+overallHeaderSize, "<-encode", counter, + Q_AES_COUNTER_SIZE, bufferLength); + } + break; + default: + assert(0 && "do not reach"); + } + TRACE((":(%d->%d)", *dataLength, *dataLength + overallHeaderSize)); + + *dataLength += overallHeaderSize; + return result; +} + + +static c_bool q_securityDecodeInPlace_Generic +( + q_securityPartitionDecoder decoder, + os_uint32 partitionId, + unsigned char *buffer, + os_uint32 *dataLength, /* in/out */ + q_cipherType sendersCipherType, + os_uint32 bufferLength +) +{ + const os_uint32 overallHeaderSize = cipherTypeToHeaderSize(sendersCipherType); + EVP_CIPHER_CTX *ctx = &(decoder->cipherContext); + c_bool result = TRUE; + + TRACE((":DECRYPT:'%s'(%d)",decoder->partitionName, partitionId)); + + (void) bufferLength; + + switch (sendersCipherType) { + case Q_CIPHER_NULL: + { + DUMP_BUFFER(decoder->partitionName, partitionId, buffer, *dataLength, "decode->", + NULL, + Q_NULL_COUNTER_SIZE, bufferLength); + + /* nothing todo here, just decreasing the buffer length at end of this function */ + TRACE((":NULL:%s", result?"OK":"ERROR")); /* debug */ + + DUMP_BUFFER(decoder->partitionName, partitionId, buffer, *dataLength - overallHeaderSize, "<-decode", + NULL, + Q_NULL_COUNTER_SIZE, bufferLength); + } + break; + + case Q_CIPHER_BLOWFISH: + { + const os_uint32 sha1HeaderLength = sizeof(C_STRUCT(q_sha1Header)); + const os_uint32 cipherTextLength = *dataLength - overallHeaderSize + sha1HeaderLength; + C_STRUCT(q_blowfishHeader) symCipherHeader; + + void *cipherText = buffer; + void *sha1HeaderStart = &(buffer[*dataLength - overallHeaderSize]); + void *symCipherHeaderStart = &(buffer[*dataLength - overallHeaderSize + sha1HeaderLength]); + + /* copy from buffer into aligned memory */ + memcpy(&symCipherHeader, symCipherHeaderStart, sizeof(C_STRUCT(q_blowfishHeader))); + + DUMP_BUFFER(decoder->partitionName, partitionId, buffer, *dataLength, "decode->", symCipherHeader.counter,Q_BLOWFISH_COUNTER_SIZE, bufferLength); + + /* decrypt the load and the digest */ + result = counterEncryptOrDecryptInPlace(ctx, + symCipherHeader.counter, + cipherText, + (int) cipherTextLength); + if (result) { + /* will zero out the sha1Header values in buffer */ + result = verifySha1(cipherText, *dataLength, sha1HeaderStart); + TRACE((":BLF:%s", result?"OK":"ERROR")); /* debug */ + if (!result) { + NN_WARNING("Incoming encrypted sub-message dropped: Decrypt (blowfish) verification failed for partition '%s' - possible Key-mismatch", decoder->partitionName); + } + } + + DUMP_BUFFER(decoder->partitionName, partitionId, buffer, *dataLength - overallHeaderSize, "<-decode",symCipherHeader.counter, Q_BLOWFISH_COUNTER_SIZE, bufferLength); + } + break; + + case Q_CIPHER_AES128: + case Q_CIPHER_AES192: + case Q_CIPHER_AES256: + { + const os_uint32 sha1HeaderLength = sizeof(C_STRUCT(q_sha1Header)); + const os_uint32 cipherTextLength = *dataLength - overallHeaderSize + sha1HeaderLength; + C_STRUCT(q_aesHeader) symCipherHeader; + + void *cipherText = buffer; + void *sha1HeaderStart = &(buffer[*dataLength - overallHeaderSize]); + void *symCipherHeaderStart = &(buffer[*dataLength - overallHeaderSize + sha1HeaderLength]); + + /* copy from buffer into aligned memory */ + memcpy(&symCipherHeader, symCipherHeaderStart, sizeof(C_STRUCT(q_aesHeader))); + + DUMP_BUFFER(decoder->partitionName, partitionId, buffer, *dataLength, "decode->",symCipherHeader.counter, Q_AES_COUNTER_SIZE, bufferLength); + + /* decrypt the load and the digest */ + result = counterEncryptOrDecryptInPlace(ctx, + symCipherHeader.counter, + cipherText, + (int) cipherTextLength); + if ( result){ + /* will zero out the sha1Header values in buffer */ + result = verifySha1(cipherText, *dataLength, sha1HeaderStart); + TRACE((":AES:%s", result?"OK":"ERROR")); /* debug */ + if (!result) { + NN_WARNING("Incoming encrypted sub-message dropped: Decrypt (AES) verification failed for partition '%s' - possible Key-mismatch", decoder->partitionName); + } + } + + DUMP_BUFFER(decoder->partitionName, partitionId, buffer, *dataLength - overallHeaderSize, "<-decode", symCipherHeader.counter, Q_AES_COUNTER_SIZE, bufferLength); + } + break; + default: + assert(0 && "do not reach"); + } + + TRACE((":(%d->%d)", *dataLength, *dataLength - overallHeaderSize)); + + *dataLength -= overallHeaderSize; /* out */ + + return result; +} + + + +/* returns 0 on error, otherwise 1, + @param codec the security context object + @param partitionId defines the security policy to be used + @param buffer with content, with reserved space at end + @param fragmentLength overall length of buffer + @param dataLength the occupied space of buffer, must leave enough space for security attribute header */ + +static c_bool q_securityEncodeInPlace +( + q_securityEncoderSet codec, + os_uint32 partitionId, + void *buffer, + os_uint32 fragmentLength, + os_uint32 *dataLength /* in/out */ +) +{ + q_securityPartitionEncoder encoder = NULL; + os_uint32 overallHeaderSize; + c_bool result = FALSE; + + assert(codec); + + + if (partitionId == 0 || partitionId > codec->nofPartitions) { + /* if partitionId is larger than number of partitions, network service + * seems to be in undefined state */ + NN_ERROR("q_securityEncodeInPlace:Sending message blocked, bad partitionid '%d'", + partitionId); + return FALSE; + } + + encoder = &(codec->encoders[partitionId-1]); + + + if (encoderIsBlocked(encoder)) { + NN_ERROR("q_securityEncodeInPlace:Sending message blocked, encoder of partitionid '%d' in bad state", + partitionId); + return FALSE; + } + + if (*dataLength <= 0) { + NN_WARNING("q_securityEncodeInPlace:encoder called with empty buffer"); + return FALSE; + } + + overallHeaderSize = cipherTypeToHeaderSize(encoder->cipherType); + + if (*dataLength + overallHeaderSize > fragmentLength) { + NN_ERROR("q_securityEncodeInPlace:sending message of %"PA_PRIu32" bytes overlaps with reserved space of %"PA_PRIu32" bytes", + *dataLength, overallHeaderSize); + return FALSE; + } + + assert(sizeof(os_uint32) == sizeof(os_uint32)); + + /* do the encoding now */ + result = q_securityEncodeInPlace_Generic(encoder, partitionId, buffer, dataLength, fragmentLength); + + return result; +} + +static c_bool q_securityGetHashFromCipherText +( + unsigned char* buffer, + os_uint32 dataLength, + os_uint32 *hash, + q_cipherType *sendersCipherType +) +{ + C_STRUCT(q_nullHeader) header; + + const os_uint32 headerSize = sizeof(C_STRUCT(q_nullHeader)); + + unsigned char* end = NULL; + + assert(dataLength >= headerSize); + assert(sizeof(os_uint32) == sizeof(os_uint32)); + + end = &(buffer[dataLength - headerSize]); + + memcpy(&header, end, headerSize); + *hash = ntohl(header.partitionId); + *sendersCipherType = ntohl(header.cipherType); + return TRUE; +} + +/* returns 0 on error, otherwise 1, + @param codec the security context object + @param buffer containing the ciphertext + @param fragmentLength overall length of buffer + @param dataLength the occupied space within buffer, on return it contains the length of plaintext in buffer */ +static c_bool q_securityDecodeInPlace +( + q_securityDecoderSet codec, + void *buffer, + size_t fragmentLength, + size_t *dataLength /* in/out */ +) +{ + q_securityPartitionDecoder decoder = NULL; + c_bool result = FALSE; + os_uint32 hash; + os_uint32 partitionId = 0; + os_uint32 dataLength32 = (os_uint32) *dataLength; + os_uint32 overallHeaderSize; + q_cipherType sendersCipherType; + struct config_networkpartition_listelem *p = config.networkPartitions; + + assert(codec); + + q_securityGetHashFromCipherText(buffer,dataLength32,&hash,&sendersCipherType); + + /* lookup hash in config to determine partitionId */ + while(p && ! partitionId) { + if (p->partitionHash == hash) partitionId = p->partitionId; + p = p->next; + } + + if ((partitionId < 1) || (partitionId > codec->nofPartitions)) { + NN_WARNING("Incoming encrypted sub-message dropped, bad partition hash '%u'", hash); + return FALSE; + } + + decoder = &(codec->decoders[partitionId-1]); + + overallHeaderSize = cipherTypeToHeaderSize(sendersCipherType); + + if (sendersCipherType!=decoder->cipherType) { + NN_WARNING("Incoming encrypted sub-message dropped: cipherType mismatch (%d != %d) for partition '%s'", sendersCipherType,decoder->cipherType, decoder->partitionName); + return FALSE; + } + if (decoderIsBlocked(decoder)) { + NN_WARNING("Incoming encrypted sub-message dropped: decoder is blocked for partition '%s'", decoder->partitionName); + return FALSE; + } + + if (overallHeaderSize > dataLength32) { + NN_WARNING("Incoming encrypted sub-message dropped: submessage too small(%"PA_PRIu32" bytes),for partition '%s'", dataLength32, decoder->partitionName); + return FALSE; + } + + result = q_securityDecodeInPlace_Generic(decoder, partitionId, buffer, &dataLength32, sendersCipherType, (os_uint32) fragmentLength); + *dataLength = dataLength32; + + return result; +} + +/* + * Substitute for the sendmsg call that send the message encrypted: + * iov[0] contains the RTPS header and is not encrypted + * iov[1] contains the security header and is also not encrypted + * iov[2..n] are concatenated into one buffer + * Buffer is encrypted and will be the new third iov. + * The size of the encrypted data is set in the second iov as the "octets to next message" + * + */ + +static os_ssize_t q_security_sendmsg +( + ddsi_tran_conn_t conn, + struct msghdr *message, + q_securityEncoderSet *codec, + os_uint32 encoderId, + os_uint32 flags +) +{ + char stbuf[2048], *buf; + os_uint32 sz, data_size; + os_ssize_t ret = ERR_UNSPECIFIED; + PT_InfoContainer_t * securityHeader = (PT_InfoContainer_t*) message->msg_iov[1].iov_base; + unsigned i; + +#if SYSDEPS_MSGHDR_ACCRIGHTS + assert (message->msg_accrightslen == 0); +#else + assert (message->msg_controllen == 0); +#endif + assert (message->msg_iovlen > 2); + /* first determine the size of the message, then select the + on-stack buffer or allocate one on the heap ... */ + sz = q_securityEncoderSetHeaderSize (*codec); /* reserve appropriate headersize */ + for (i = 2; i < (unsigned) message->msg_iovlen; i++) + { + sz += (os_uint32) message->msg_iov[i].iov_len; + } + if (sz <= sizeof (stbuf)) + { + buf = stbuf; + } + else + { + buf = os_malloc (sz); + } + /* ... then copy data into buffer */ + data_size = 0; + for (i = 2; i < (unsigned) message->msg_iovlen; i++) + { + memcpy (buf + data_size, message->msg_iov[i].iov_base, message->msg_iov[i].iov_len); + data_size += (os_uint32) message->msg_iov[i].iov_len; + } + sz = data_size + q_securityEncoderSetHeaderSize (*codec); + + /* Encrypt the buf in place with the given encoder */ + + if (q_securityEncodeInPlace (*codec, encoderId, buf, sz, &data_size)) + { + os_size_t nbytes; + /* replace encrypted buffer into iov */ + + message->msg_iov[2].iov_base = buf; + message->msg_iov[2].iov_len = data_size; + message->msg_iovlen = 3; + /* correct size in security header */ + securityHeader->smhdr.octetsToNextHeader = (unsigned short) (data_size + 4); + + /* send the encrypted data to the connection */ + + nbytes = message->msg_iov[0].iov_len + message->msg_iov[1].iov_len + message->msg_iov[2].iov_len; + if (!gv.mute) + ret = ddsi_conn_write (conn, message, nbytes, flags); + else + { + TRACE (("(dropped)")); + ret = (os_ssize_t) nbytes; + } + } + + if (buf != stbuf) + { + os_free (buf); + } + return ret; +} + +void ddsi_security_plugin (void) +{ + q_security_plugin.encode = q_securityEncodeInPlace; + q_security_plugin.decode = q_securityDecodeInPlace; + q_security_plugin.new_encoder = q_securityEncoderSetNew; + q_security_plugin.new_decoder = q_securityDecoderSetNew; + q_security_plugin.free_encoder = q_securityEncoderSetFree; + q_security_plugin.free_decoder = q_securityDecoderSetFree; + q_security_plugin.send_encoded = q_security_sendmsg; + q_security_plugin.cipher_type = cipherTypeAsString; + q_security_plugin.cipher_type_from_string = q_securityCipherTypeFromString; + q_security_plugin.header_size = q_securityEncoderHeaderSize; + q_security_plugin.encoder_type = q_securityEncoderCipherType; + q_security_plugin.valid_uri = q_securityIsValidCipherKeyUri; +} + +#else + +int ddsi_dummy_val = 0; + +#endif /* DDSI_INCLUDE_ENCRYPTION */ diff --git a/src/core/ddsi/src/q_servicelease.c b/src/core/ddsi/src/q_servicelease.c new file mode 100644 index 0000000..8c7cb8d --- /dev/null +++ b/src/core/ddsi/src/q_servicelease.c @@ -0,0 +1,240 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include + +#include "os/os.h" + +#include "ddsi/q_servicelease.h" +#include "ddsi/q_config.h" +#include "ddsi/q_log.h" +#include "ddsi/q_thread.h" +#include "ddsi/q_time.h" +#include "ddsi/q_unused.h" +#include "ddsi/q_error.h" +#include "ddsi/q_globals.h" /* for mattr, cattr */ + +#include "ddsi/sysdeps.h" /* for getrusage() */ + +static void nn_retrieve_lease_settings (os_time *sleepTime) +{ + const float leaseSec = config.servicelease_expiry_time; + float sleepSec = leaseSec * config.servicelease_update_factor; + + /* Run at no less than 1Hz: internal liveliness monitoring is slaved + to this interval as well. 1Hz lease renewals and liveliness + checks is no large burden, and performing liveliness checks once + a second is a lot more useful than doing it once every few + seconds. Besides -- we're now also gathering CPU statistics. */ + if (sleepSec > 1.0f) + sleepSec = 1.0f; + + sleepTime->tv_sec = (int32_t) sleepSec; + sleepTime->tv_nsec = (int32_t) ((sleepSec - (float) sleepTime->tv_sec) * 1e9f); +} + +struct alive_wd { + char alive; + vtime_t wd; +}; + +struct nn_servicelease { + os_time sleepTime; + int keepgoing; + struct alive_wd *av_ary; + void (*renew_cb) (void *arg); + void *renew_arg; + + os_mutex lock; + os_cond cond; + struct thread_state1 *ts; +}; + +static uint32_t lease_renewal_thread (struct nn_servicelease *sl) +{ + /* Do not check more often than once every 100ms (no particular + reason why it has to be 100ms), regardless of the lease settings. + Note: can't trust sl->self, may have been scheduled before the + assignment. */ + const int64_t min_progress_check_intv = 100 * T_MILLISECOND; + struct thread_state1 *self = lookup_thread_state (); + nn_mtime_t next_thread_cputime = { 0 }; + nn_mtime_t tlast = { 0 }; + int was_alive = 1; + unsigned i; + for (i = 0; i < thread_states.nthreads; i++) + { + sl->av_ary[i].alive = 1; + sl->av_ary[i].wd = thread_states.ts[i].watchdog - 1; + } + os_mutexLock (&sl->lock); + while (sl->keepgoing) + { + unsigned n_alive = 0; + nn_mtime_t tnow = now_mt (); + + LOG_THREAD_CPUTIME (next_thread_cputime); + + TRACE (("servicelease: tnow %"PRId64":", tnow.v)); + + /* Check progress only if enough time has passed: there is no + guarantee that os_cond_timedwait wont ever return early, and we + do want to avoid spurious warnings. */ + if (tnow.v < tlast.v + min_progress_check_intv) + { + n_alive = thread_states.nthreads; + } + else + { + tlast = tnow; + for (i = 0; i < thread_states.nthreads; i++) + { + if (thread_states.ts[i].state != THREAD_STATE_ALIVE) + n_alive++; + else + { + vtime_t vt = thread_states.ts[i].vtime; + vtime_t wd = thread_states.ts[i].watchdog; + int alive = vtime_asleep_p (vt) || vtime_asleep_p (wd) || vtime_gt (wd, sl->av_ary[i].wd); + n_alive += (unsigned) alive; + TRACE ((" %u(%s):%c:%u:%u->%u:", i, thread_states.ts[i].name, alive ? 'a' : 'd', vt, sl->av_ary[i].wd, wd)); + sl->av_ary[i].wd = wd; + if (sl->av_ary[i].alive != alive) + { + const char *name = thread_states.ts[i].name; + const char *msg; + if (!alive) + msg = "failed to make progress"; + else + msg = "once again made progress"; + NN_WARNING ("thread %s %s\n", name ? name : "(anon)", msg); + sl->av_ary[i].alive = (char) alive; + } + } + } + } + + /* Only renew the lease if all threads are alive, so that one + thread blocking for a while but not too extremely long will + cause warnings for that thread in the log file, but won't cause + the DDSI2 service to be marked as dead. */ + if (n_alive == thread_states.nthreads) + { + TRACE ((": [%u] renewing\n", n_alive)); + /* FIXME: perhaps it would be nice to control automatic + liveliness updates from here. + FIXME: should terminate failure of renew_cb() */ + sl->renew_cb (sl->renew_arg); + was_alive = 1; + } + else + { + TRACE ((": [%u] NOT renewing\n", n_alive)); + if (was_alive) + log_stack_traces (); + was_alive = 0; + } + +#if SYSDEPS_HAVE_GETRUSAGE + /* If getrusage() is available, use it to log CPU and memory + statistics to the trace. Getrusage() can't fail if the + parameters are valid, and these are by the book. Still we + check. */ + if (config.enabled_logcats & LC_TIMING) + { + struct rusage u; + if (getrusage (RUSAGE_SELF, &u) == 0) + { + nn_log (LC_TIMING, + "rusage: utime %d.%06d stime %d.%06d maxrss %ld data %ld vcsw %ld ivcsw %ld\n", + (int) u.ru_utime.tv_sec, (int) u.ru_utime.tv_usec, + (int) u.ru_stime.tv_sec, (int) u.ru_stime.tv_usec, + u.ru_maxrss, u.ru_idrss, u.ru_nvcsw, u.ru_nivcsw); + } + } +#endif + + os_condTimedWait (&sl->cond, &sl->lock, &sl->sleepTime); + + /* We are never active in a way that matters for the garbage + collection of old writers, &c. */ + thread_state_asleep (self); + } + os_mutexUnlock (&sl->lock); + return 0; +} + +static void dummy_renew_cb (UNUSED_ARG (void *arg)) +{ +} + +struct nn_servicelease *nn_servicelease_new (void (*renew_cb) (void *arg), void *renew_arg) +{ + struct nn_servicelease *sl; + + sl = os_malloc (sizeof (*sl)); + nn_retrieve_lease_settings (&sl->sleepTime); + sl->keepgoing = -1; + sl->renew_cb = renew_cb ? renew_cb : dummy_renew_cb; + sl->renew_arg = renew_arg; + sl->ts = NULL; + + if ((sl->av_ary = os_malloc (thread_states.nthreads * sizeof (*sl->av_ary))) == NULL) + goto fail_vtimes; + /* service lease update thread initializes av_ary */ + + os_mutexInit (&sl->lock); + os_condInit (&sl->cond, &sl->lock); + return sl; + + fail_vtimes: + os_free (sl); + return NULL; +} + +int nn_servicelease_start_renewing (struct nn_servicelease *sl) +{ + os_mutexLock (&sl->lock); + assert (sl->keepgoing == -1); + sl->keepgoing = 1; + os_mutexUnlock (&sl->lock); + + sl->ts = create_thread ("lease", (uint32_t (*) (void *)) lease_renewal_thread, sl); + if (sl->ts == NULL) + goto fail_thread; + return 0; + + fail_thread: + sl->keepgoing = -1; + return ERR_UNSPECIFIED; +} + +void nn_servicelease_statechange_barrier (struct nn_servicelease *sl) +{ + os_mutexLock (&sl->lock); + os_mutexUnlock (&sl->lock); +} + +void nn_servicelease_free (struct nn_servicelease *sl) +{ + if (sl->keepgoing != -1) + { + os_mutexLock (&sl->lock); + sl->keepgoing = 0; + os_condSignal (&sl->cond); + os_mutexUnlock (&sl->lock); + join_thread (sl->ts); + } + os_condDestroy (&sl->cond); + os_mutexDestroy (&sl->lock); + os_free (sl->av_ary); + os_free (sl); +} diff --git a/src/core/ddsi/src/q_sockwaitset.c b/src/core/ddsi/src/q_sockwaitset.c new file mode 100644 index 0000000..484a88f --- /dev/null +++ b/src/core/ddsi/src/q_sockwaitset.c @@ -0,0 +1,657 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#if defined (WIN32) || defined (OSPL_LINUX) +#define FD_SETSIZE 4096 +#endif + +#include +#include + +#include "os/os.h" + +#include "ddsi/q_sockwaitset.h" +#include "ddsi/q_config.h" +#include "ddsi/q_log.h" +#include "ddsi/ddsi_tran.h" + +#define WAITSET_DELTA 8 + +#ifdef __VXWORKS__ +#include +#include +#include +#include +#define OSPL_PIPENAMESIZE 26 +#endif + +#ifdef WINCE + +struct os_sockWaitsetCtx +{ + ddsi_tran_conn_t conns[MAXIMUM_WAIT_OBJECTS]; /* connections and listeners */ + WSAEVENT events[MAXIMUM_WAIT_OBJECTS]; /* events associated with sockets */ + int index; /* last wakeup index, or -1 */ + unsigned n; /* sockets/events [0 .. n-1] are occupied */ +}; + +struct os_sockWaitset +{ + os_mutex mutex; /* concurrency guard */ + struct os_sockWaitsetCtx ctx; + struct os_sockWaitsetCtx ctx0; +}; + +os_sockWaitset os_sockWaitsetNew (void) +{ + os_sockWaitset ws = os_malloc (sizeof (*ws)); + ws->ctx.conns[0] = NULL; + ws->ctx.events[0] = WSACreateEvent (); + ws->ctx.n = 1; + ws->ctx.index = -1; + os_mutexInit (&ws->mutex); + return ws; +} + +void os_sockWaitsetFree (os_sockWaitset ws) +{ + unsigned i; + + for (i = 0; i < ws->ctx.n; i++) + { + WSACloseEvent (ws->ctx.events[i]); + } + os_mutexDestroy (&ws->mutex); + os_free (ws); +} + +void os_sockWaitsetPurge (os_sockWaitset ws, unsigned index) +{ + unsigned i; + + os_mutexLock (&ws->mutex); + for (i = index + 1; i < ws->ctx.n; i++) + { + ws->ctx.conns[i] = NULL; + if (!WSACloseEvent (ws->ctx.events[i])) + { + NN_WARNING ("os_sockWaitsetPurge: WSACloseEvent (%x failed, error %d", (os_uint32) ws->ctx.events[i], os_getErrno ()); + } + } + ws->ctx.n = index + 1; + os_mutexUnlock (&ws->mutex); +} + +void os_sockWaitsetRemove (os_sockWaitset ws, ddsi_tran_conn_t conn) +{ + unsigned i; + + os_mutexLock (&ws->mutex); + for (i = 0; i < ws->ctx.n; i++) + { + if (conn == ws->ctx.conns[i]) + { + WSACloseEvent (ws->ctx.events[i]); + ws->ctx.n--; + if (i != ws->ctx.n) + { + ws->ctx.events[i] = ws->ctx.events[ws->ctx.n]; + ws->ctx.conns[i] = ws->ctx.conns[ws->ctx.n]; + } + break; + } + } + os_mutexUnlock (&ws->mutex); +} + +void os_sockWaitsetTrigger (os_sockWaitset ws) +{ + if (! WSASetEvent (ws->ctx.events[0])) + { + NN_WARNING ("os_sockWaitsetTrigger: WSASetEvent(%x) failed, error %d", (os_uint32) ws->ctx.events[0], os_getErrno ()); + } +} + +void os_sockWaitsetAdd (os_sockWaitset ws, ddsi_tran_conn_t conn) +{ + WSAEVENT ev; + os_socket sock = ddsi_conn_handle (conn); + unsigned idx; + + os_mutexLock (&ws->mutex); + + for (idx = 0; idx < ws->ctx.n; idx++) + { + if (ws->ctx.conns[idx] == conn) + break; + } + if (idx == ws->ctx.n) + { + assert (ws->n < MAXIMUM_WAIT_OBJECTS); + + ev = WSACreateEvent (); + assert (ev != WSA_INVALID_EVENT); + + if (WSAEventSelect (sock, ev, FD_READ) == SOCKET_ERROR) + { + NN_WARNING ("os_sockWaitsetAdd: WSAEventSelect(%x,%x) failed, error %d", (os_uint32) sock, (os_uint32) ev, os_getErrno ()); + WSACloseEvent (ev); + assert (0); + } + ws->ctx.conns[ws->ctx.n] = conn; + ws->ctx.events[ws->ctx.n] = ev; + ws->ctx.n++; + } + + os_mutexUnlock (&ws->mutex); +} + +os_sockWaitsetCtx os_sockWaitsetWait (os_sockWaitset ws) +{ + unsigned idx; + + assert (ws->index == -1); + + os_mutexLock (&ws->mutex); + ws->ctx0 = ws->ctx; + os_mutexUnlock (&ws->mutex); + + if ((idx = WSAWaitForMultipleEvents (ws->ctx0.n, ws->ctx0.events, FALSE, WSA_INFINITE, FALSE)) == WSA_WAIT_FAILED) + { + NN_WARNING ("os_sockWaitsetWait: WSAWaitForMultipleEvents(%d,...,0,0,0) failed, error %d", ws->ctx0.n, os_getErrno ()); + return NULL; + } + +#ifndef WAIT_IO_COMPLETION /* curious omission in the WinCE headers */ +#define TEMP_DEF_WAIT_IO_COMPLETION +#define WAIT_IO_COMPLETION 0xc0L +#endif + if (idx >= WSA_WAIT_EVENT_0 && idx < WSA_WAIT_EVENT_0 + ws->ctx0.n) + { + ws->ctx0.index = idx - WSA_WAIT_EVENT_0; + if (ws->ctx0.index == 0) + { + /* pretend a spurious wakeup */ + WSAResetEvent (ws->ctx0.events[0]); + ws->ctx0.index = -1; + } + return &ws->ctx0; + } + + if (idx == WAIT_IO_COMPLETION) + { + /* Presumably can't happen with alertable = FALSE */ + NN_WARNING ("os_sockWaitsetWait: WSAWaitForMultipleEvents(%d,...,0,0,0) returned unexpected WAIT_IO_COMPLETION", ws->ctx0.n); + } + else + { + NN_WARNING ("os_sockWaitsetWait: WSAWaitForMultipleEvents(%d,...,0,0,0) returned unrecognised %d", ws->ctx0.n, idx); + } +#ifdef TEMP_DEF_WAIT_IO_COMPLETION +#undef WAIT_IO_COMPLETION +#undef TEMP_DEF_WAIT_IO_COMPLETION +#endif + return NULL; +} + +/* This implementation follows the pattern of simply looking at the + socket that triggered the wakeup; alternatively, one could scan the + entire set as we do for select(). If the likelihood of two sockets + having an event simultaneously is small, this is better, but if it + is large, the lower indices may get a disproportionally large + amount of attention. */ + +int os_sockWaitsetNextEvent (os_sockWaitsetCtx ctx, ddsi_tran_conn_t * conn) +{ + assert (-1 <= ctx->index && ctx->index < ctx->n); + assert (0 < ctx->n && ctx->n <= ctx->sz); + if (ctx->index == -1) + { + return -1; + } + else + { + WSANETWORKEVENTS nwev; + int idx = ctx->index; + os_handle handle; + + ctx->index = -1; + handle = ddsi_conn_handle (ctx->conns[idx]); + if (WSAEnumNetworkEvents (handle, ctx->events[idx], &nwev) == SOCKET_ERROR) + { + int err = os_getErrno (); + if (err != WSAENOTSOCK) + { + /* May have a wakeup and a close in parallel, so the handle + need not exist anymore. */ + NN_ERROR ("os_sockWaitsetNextEvent: WSAEnumNetworkEvents(%x,%x,...) failed, error %d", (os_uint32) handle, (os_uint32) ctx->events[idx], err); + } + return -1; + } + + *conn = ctx->conns[idx]; + return idx - 1; + } +} + +#else /* WINCE */ + +#if defined (_WIN32) + +static int pipe (os_handle fd[2]) +{ + struct sockaddr_in addr; + socklen_t asize = sizeof (addr); + os_socket listener = socket (AF_INET, SOCK_STREAM, 0); + os_socket s1 = socket (AF_INET, SOCK_STREAM, 0); + os_socket s2 = Q_INVALID_SOCKET; + + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl (INADDR_LOOPBACK); + addr.sin_port = 0; + if (bind (listener, (struct sockaddr *) &addr, sizeof (addr)) == -1) + { + goto fail; + } + if (getsockname (listener, (struct sockaddr *) &addr, &asize) == -1) + { + goto fail; + } + if (listen (listener, 1) == -1) + { + goto fail; + } + if (connect (s1, (struct sockaddr *) &addr, sizeof (addr)) == -1) + { + goto fail; + } + if ((s2 = accept (listener, 0, 0)) == -1) + { + goto fail; + } + + closesocket (listener); + + /* Equivalent to FD_CLOEXEC */ + + SetHandleInformation ((HANDLE) s1, HANDLE_FLAG_INHERIT, 0); + SetHandleInformation ((HANDLE) s2, HANDLE_FLAG_INHERIT, 0); + + fd[0] = s1; + fd[1] = s2; + + return 0; + +fail: + + closesocket (listener); + closesocket (s1); + closesocket (s2); + + return -1; +} + +#else + +#ifndef __VXWORKS__ +#if defined (AIX) || defined (__Lynx__) || defined (__QNX__) +#include +#elif ! defined(INTEGRITY) +#include +#endif +#endif /* __VXWORKS__ */ + +#ifndef _WRS_KERNEL +#include +#endif +#ifdef __sun +#include +#include +#include +#endif + +#endif /* _WIN32 */ + +typedef struct os_sockWaitsetSet +{ + ddsi_tran_conn_t * conns; /* connections in set */ + os_handle * fds; /* file descriptors in set */ + unsigned sz; /* max number of fds in context */ + unsigned n; /* actual number of fds in context */ +} os_sockWaitsetSet; + +struct os_sockWaitsetCtx +{ + os_sockWaitsetSet set; /* set of connections and descriptors */ + unsigned index; /* cursor for enumerating */ + fd_set rdset; /* read file descriptors */ +}; + +struct os_sockWaitset +{ + os_handle pipe[2]; /* pipe used for triggering */ + os_mutex mutex; /* concurrency guard */ + int fdmax_plus_1; /* value for first parameter of select() */ + os_sockWaitsetSet set; /* set of descriptors handled next */ + struct os_sockWaitsetCtx ctx; /* set of descriptors being handled */ +}; + +static void os_sockWaitsetNewSet (os_sockWaitsetSet * set) +{ + set->fds = os_malloc (WAITSET_DELTA * sizeof (*set->fds)); + set->conns = os_malloc (WAITSET_DELTA * sizeof (*set->conns)); + set->sz = WAITSET_DELTA; + set->n = 1; +} + +static void os_sockWaitsetNewCtx (os_sockWaitsetCtx ctx) +{ + os_sockWaitsetNewSet (&ctx->set); + FD_ZERO (&ctx->rdset); +} + +os_sockWaitset os_sockWaitsetNew (void) +{ + int result; + os_sockWaitset ws = os_malloc (sizeof (*ws)); + + os_sockWaitsetNewSet (&ws->set); + os_sockWaitsetNewCtx (&ws->ctx); + +#if ! defined (_WIN32) + ws->fdmax_plus_1 = 0; +#else + ws->fdmax_plus_1 = FD_SETSIZE; +#endif + +#if defined (VXWORKS_RTP) || defined (_WRS_KERNEL) + { + char pipename[OSPL_PIPENAMESIZE]; + int pipecount=0; + do + { + snprintf ((char*)&pipename, sizeof(pipename), "/pipe/ospl%d", pipecount++ ); + } + while ((result = pipeDevCreate ((char*) &pipename, 1, 1)) == -1 && os_getErrno() == EINVAL); + if (result != -1) + { + result = open ((char*) &pipename, O_RDWR, 0644); + if (result != -1) + { + ws->pipe[0] = result; + result =open ((char*) &pipename, O_RDWR, 0644); + if (result != -1) + { + ws->pipe[1] = result; + } + else + { + close (ws->pipe[0]); + pipeDevDelete (pipename, 0); + } + } + } + } +#else + result = pipe (ws->pipe); +#endif + assert (result != -1); + (void) result; + + ws->set.fds[0] = ws->pipe[0]; + ws->set.conns[0] = NULL; + +#if ! defined (VXWORKS_RTP) && ! defined ( _WRS_KERNEL ) && ! defined (_WIN32) + fcntl (ws->pipe[0], F_SETFD, fcntl (ws->pipe[0], F_GETFD) | FD_CLOEXEC); + fcntl (ws->pipe[1], F_SETFD, fcntl (ws->pipe[1], F_GETFD) | FD_CLOEXEC); +#endif + FD_SET (ws->set.fds[0], &ws->ctx.rdset); +#if ! defined (_WIN32) + ws->fdmax_plus_1 = ws->set.fds[0] + 1; +#endif + + os_mutexInit (&ws->mutex); + + return ws; +} + +static void os_sockWaitsetGrow (os_sockWaitsetSet * set) +{ + set->sz += WAITSET_DELTA; + set->conns = os_realloc (set->conns, set->sz * sizeof (*set->conns)); + set->fds = os_realloc (set->fds, set->sz * sizeof (*set->fds)); +} + +static void os_sockWaitsetFreeSet (os_sockWaitsetSet * set) +{ + os_free (set->fds); + os_free (set->conns); +} + +static void os_sockWaitsetFreeCtx (os_sockWaitsetCtx ctx) +{ + os_sockWaitsetFreeSet (&ctx->set); +} + +void os_sockWaitsetFree (os_sockWaitset ws) +{ +#ifdef VXWORKS_RTP + char nameBuf[OSPL_PIPENAMESIZE]; + ioctl (ws->pipe[0], FIOGETNAME, &nameBuf); +#endif +#if defined (_WIN32) + closesocket (ws->pipe[0]); + closesocket (ws->pipe[1]); +#else + close (ws->pipe[0]); + close (ws->pipe[1]); +#endif +#ifdef VXWORKS_RTP + pipeDevDelete ((char*) &nameBuf, 0); +#endif + os_sockWaitsetFreeSet (&ws->set); + os_sockWaitsetFreeCtx (&ws->ctx); + os_mutexDestroy (&ws->mutex); + os_free (ws); +} + +void os_sockWaitsetTrigger (os_sockWaitset ws) +{ + char buf = 0; + int n; + int err; + +#if defined (_WIN32) + n = send (ws->pipe[1], &buf, 1, 0); +#else + n = (int) write (ws->pipe[1], &buf, 1); +#endif + if (n != 1) + { + err = os_getErrno (); + NN_WARNING ("os_sockWaitsetTrigger: read failed on trigger pipe, errno = %d", err); + } +} + +void os_sockWaitsetAdd (os_sockWaitset ws, ddsi_tran_conn_t conn) +{ + os_handle handle = ddsi_conn_handle (conn); + os_sockWaitsetSet * set = &ws->set; + unsigned idx; + + assert (handle >= 0); +#if ! defined (_WIN32) + assert (handle < FD_SETSIZE); +#endif + + os_mutexLock (&ws->mutex); + for (idx = 0; idx < set->n; idx++) + { + if (set->conns[idx] == conn) + break; + } + if (idx == set->n) + { + if (set->n == set->sz) + { + os_sockWaitsetGrow (set); + } +#if ! defined (_WIN32) + if ((int) handle >= ws->fdmax_plus_1) + { + ws->fdmax_plus_1 = handle + 1; + } +#endif + set->conns[set->n] = conn; + set->fds[set->n] = handle; + set->n++; + } + os_mutexUnlock (&ws->mutex); +} + +void os_sockWaitsetPurge (os_sockWaitset ws, unsigned index) +{ + unsigned i; + os_sockWaitsetSet * set = &ws->set; + + os_mutexLock (&ws->mutex); + if (index + 1 <= set->n) + { + for (i = index + 1; i < set->n; i++) + { + set->conns[i] = NULL; + set->fds[i] = 0; + } + set->n = index + 1; + } + os_mutexUnlock (&ws->mutex); +} + +void os_sockWaitsetRemove (os_sockWaitset ws, ddsi_tran_conn_t conn) +{ + unsigned i; + os_sockWaitsetSet * set = &ws->set; + + os_mutexLock (&ws->mutex); + for (i = 0; i < set->n; i++) + { + if (conn == set->conns[i]) + { + set->n--; + if (i != set->n) + { + set->fds[i] = set->fds[set->n]; + set->conns[i] = set->conns[set->n]; + } + break; + } + } + os_mutexUnlock (&ws->mutex); +} + +os_sockWaitsetCtx os_sockWaitsetWait (os_sockWaitset ws) +{ + int n; + unsigned u; + int err; + int fdmax; + fd_set * rdset = NULL; + os_sockWaitsetCtx ctx = &ws->ctx; + os_sockWaitsetSet * dst = &ctx->set; + os_sockWaitsetSet * src = &ws->set; + + os_mutexLock (&ws->mutex); + + fdmax = ws->fdmax_plus_1; + + /* Copy context to working context */ + + while (dst->sz < src->sz) + { + os_sockWaitsetGrow (dst); + } + dst->n = src->n; + + for (u = 0; u < src->sz; u++) + { + dst->conns[u] = src->conns[u]; + dst->fds[u] = src->fds[u]; + } + + os_mutexUnlock (&ws->mutex); + + /* Copy file descriptors into select read set */ + + rdset = &ctx->rdset; + FD_ZERO (rdset); + for (u = 0; u < dst->n; u++) + { + FD_SET (dst->fds[u], rdset); + } + + do + { + n = select (fdmax, rdset, NULL, NULL, NULL); + if (n < 0) + { + err = os_getErrno (); + if ((err != os_sockEINTR) && (err != os_sockEAGAIN)) + { + NN_WARNING ("os_sockWaitsetWait: select failed, errno = %d", err); + break; + } + } + } + while (n == -1); + + if (n > 0) + { + /* this simply skips the trigger fd */ + ctx->index = 1; + if (FD_ISSET (dst->fds[0], rdset)) + { + char buf; + int n1; +#if defined (_WIN32) + n1 = recv (dst->fds[0], &buf, 1, 0); +#else + n1 = (int) read (dst->fds[0], &buf, 1); +#endif + if (n1 != 1) + { + err = os_getErrno (); + NN_WARNING ("os_sockWaitsetWait: read failed on trigger pipe, errno = %d", err); + assert (0); + } + } + return ctx; + } + + return NULL; +} + +int os_sockWaitsetNextEvent (os_sockWaitsetCtx ctx, ddsi_tran_conn_t * conn) +{ + while (ctx->index < ctx->set.n) + { + unsigned idx = ctx->index++; + os_handle fd = ctx->set.fds[idx]; + assert(idx > 0); + if (FD_ISSET (fd, &ctx->rdset)) + { + *conn = ctx->set.conns[idx]; + + return (int) (idx - 1); + } + } + return -1; +} +#endif /* WINCE */ diff --git a/src/core/ddsi/src/q_thread.c b/src/core/ddsi/src/q_thread.c new file mode 100644 index 0000000..283d724 --- /dev/null +++ b/src/core/ddsi/src/q_thread.c @@ -0,0 +1,357 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include + +#include "os/os.h" + +#include "ddsi/q_thread.h" +#include "ddsi/q_servicelease.h" +#include "ddsi/q_error.h" +#include "ddsi/q_log.h" +#include "ddsi/q_config.h" +#include "ddsi/q_globals.h" +#include "ddsi/sysdeps.h" + +static char main_thread_name[] = "main"; + +struct thread_states thread_states; +os_threadLocal struct thread_state1 *tsd_thread_state; + + +_Ret_bytecap_(size) +void * os_malloc_aligned_cacheline (_In_ size_t size) +{ + /* This wastes some space, but we use it only once and it isn't a + huge amount of memory, just a little over a cache line. + Alternatively, we good use valloc() and have it aligned to a page + boundary, but that one isn't part of the O/S abstraction layer + ... */ + const uintptr_t clm1 = CACHE_LINE_SIZE - 1; + uintptr_t ptrA; + void **pptr; + void *ptr; + ptr = os_malloc (size + CACHE_LINE_SIZE + sizeof (void *)); + ptrA = ((uintptr_t) ptr + sizeof (void *) + clm1) & ~clm1; + pptr = (void **) ptrA; + pptr[-1] = ptr; + return (void *) ptrA; +} + +static void os_free_aligned ( _Pre_maybenull_ _Post_invalid_ void *ptr) +{ + if (ptr) { + void **pptr = ptr; + os_free (pptr[-1]); + } +} + +void thread_states_init (_In_ unsigned maxthreads) +{ + unsigned i; + + os_mutexInit (&thread_states.lock); + thread_states.nthreads = maxthreads; + thread_states.ts = + os_malloc_aligned_cacheline (maxthreads * sizeof (*thread_states.ts)); + memset (thread_states.ts, 0, maxthreads * sizeof (*thread_states.ts)); +/* The compiler doesn't realize that ts is large enough. */ +OS_WARNING_MSVC_OFF(6386); + for (i = 0; i < thread_states.nthreads; i++) + { + thread_states.ts[i].state = THREAD_STATE_ZERO; + thread_states.ts[i].vtime = 1; + thread_states.ts[i].watchdog = 1; + thread_states.ts[i].lb = NULL; + thread_states.ts[i].name = NULL; + } +OS_WARNING_MSVC_ON(6386); +} + +void thread_states_fini (void) +{ + unsigned i; + for (i = 0; i < thread_states.nthreads; i++) + assert (thread_states.ts[i].state != THREAD_STATE_ALIVE); + os_mutexDestroy (&thread_states.lock); + os_free_aligned (thread_states.ts); + + /* All spawned threads are gone, but the main thread is still alive, + downgraded to an ordinary thread (we're on it right now). We + don't want to lose the ability to log messages, so set ts to a + NULL pointer and rely on lookup_thread_state()'s checks + thread_states.ts. */ + thread_states.ts = NULL; +} + +static void +cleanup_thread_state( + _In_opt_ void *data) +{ + struct thread_state1 *ts = get_thread_state(os_threadIdSelf()); + + assert(ts->state == THREAD_STATE_ALIVE); + assert(vtime_asleep_p(ts->vtime)); + reset_thread_state(ts); + os_reportExit(); /* FIXME: Should not be here! */ +} + +_Ret_valid_ struct thread_state1 * +lookup_thread_state( + void) +{ + struct thread_state1 *ts1 = NULL; + char tname[128]; + os_threadId tid; + + if ((ts1 = tsd_thread_state) == NULL) { + if ((ts1 = lookup_thread_state_real()) == NULL) { + /* this situation only arises for threads that were not created + using create_thread, aka application threads. since registering + thread state should be fully automatic the name will simply be + the identifier */ + tid = os_threadIdSelf(); + (void)snprintf( + tname, sizeof(tname), "0x%"PRIxMAX, os_threadIdToInteger(tid)); + os_mutexLock(&thread_states.lock); + ts1 = init_thread_state(tname); + if (ts1 != NULL) { + ts1->lb = 0; + ts1->extTid = tid; + ts1->tid = tid; + nn_log (LC_INFO, "started application thread %s\n", tname); + os_threadCleanupPush(&cleanup_thread_state, NULL); + } + os_mutexUnlock(&thread_states.lock); + } + + tsd_thread_state = ts1; + } + + assert(ts1 != NULL); + + return ts1; +} + +_Success_(return != NULL) _Ret_maybenull_ +struct thread_state1 *lookup_thread_state_real (void) +{ + if (thread_states.ts) { + os_threadId tid = os_threadIdSelf (); + unsigned i; + for (i = 0; i < thread_states.nthreads; i++) { + if (os_threadEqual (thread_states.ts[i].tid, tid)) { + return &thread_states.ts[i]; + } + } + } + return NULL; +} + +struct thread_context { + struct thread_state1 *self; + uint32_t (*f) (_In_opt_ void *arg); + void *arg; +}; + +static uint32_t create_thread_wrapper (_In_ _Post_invalid_ struct thread_context *ctxt) +{ + uint32_t ret; + ctxt->self->tid = os_threadIdSelf (); + ret = ctxt->f (ctxt->arg); + logbuf_free (ctxt->self->lb); + os_free (ctxt); + return ret; +} + +static int find_free_slot (_In_z_ const char *name) +{ + unsigned i; + int cand; + for (i = 0, cand = -1; i < thread_states.nthreads; i++) + { + if (thread_states.ts[i].state != THREAD_STATE_ALIVE) + cand = (int) i; + if (thread_states.ts[i].state == THREAD_STATE_ZERO) + break; + } + if (cand == -1) + NN_FATAL ("create_thread: %s: no free slot\n", name ? name : "(anon)"); + return cand; +} + +void upgrade_main_thread (void) +{ + int cand; + struct thread_state1 *ts1; + os_mutexLock (&thread_states.lock); + if ((cand = find_free_slot ("name")) < 0) + abort (); + ts1 = &thread_states.ts[cand]; + if (ts1->state == THREAD_STATE_ZERO) + assert (vtime_asleep_p (ts1->vtime)); + ts1->state = THREAD_STATE_ALIVE; + ts1->tid = os_threadIdSelf (); + ts1->lb = logbuf_new (); + ts1->name = main_thread_name; + os_mutexUnlock (&thread_states.lock); + tsd_thread_state = ts1; +} + +const struct config_thread_properties_listelem *lookup_thread_properties (_In_z_ const char *name) +{ + const struct config_thread_properties_listelem *e; + for (e = config.thread_properties; e != NULL; e = e->next) + if (strcmp (e->name, name) == 0) + break; + return e; +} + +struct thread_state1 * init_thread_state (_In_z_ const char *tname) +{ + int cand; + struct thread_state1 *ts; + + if ((cand = find_free_slot (tname)) < 0) + return NULL; + + ts = &thread_states.ts[cand]; + if (ts->state == THREAD_STATE_ZERO) + assert (vtime_asleep_p (ts->vtime)); + ts->name = os_strdup (tname); + ts->state = THREAD_STATE_ALIVE; + + return ts; +} + +_Success_(return != NULL) +_Ret_maybenull_ +struct thread_state1 *create_thread (_In_z_ const char *name, _In_ uint32_t (*f) (void *arg), _In_opt_ void *arg) +{ + struct config_thread_properties_listelem const * const tprops = lookup_thread_properties (name); + os_threadAttr tattr; + struct thread_state1 *ts1; + os_threadId tid; + struct thread_context *ctxt; + ctxt = os_malloc (sizeof (*ctxt)); + os_mutexLock (&thread_states.lock); + + ts1 = init_thread_state (name); + + if (ts1 == NULL) + goto fatal; + + ts1->lb = logbuf_new (); + ctxt->self = ts1; + ctxt->f = f; + ctxt->arg = arg; + os_threadAttrInit (&tattr); + if (tprops != NULL) + { + if (!tprops->sched_priority.isdefault) + tattr.schedPriority = tprops->sched_priority.value; + tattr.schedClass = tprops->sched_class; /* explicit default value in the enum */ + if (!tprops->stack_size.isdefault) + tattr.stackSize = tprops->stack_size.value; + } + TRACE (("create_thread: %s: class %d priority %d stack %u\n", name, (int) tattr.schedClass, tattr.schedPriority, tattr.stackSize)); + + if (os_threadCreate (&tid, name, &tattr, (os_threadRoutine)&create_thread_wrapper, ctxt) != os_resultSuccess) + { + ts1->state = THREAD_STATE_ZERO; + NN_FATAL ("create_thread: %s: os_threadCreate failed\n", name); + goto fatal; + } + nn_log (LC_INFO, "started new thread 0x%"PRIxMAX" : %s\n", os_threadIdToInteger (tid), name); + ts1->extTid = tid; /* overwrite the temporary value with the correct external one */ + os_mutexUnlock (&thread_states.lock); + return ts1; + fatal: + os_mutexUnlock (&thread_states.lock); + os_free (ctxt); + abort (); + return NULL; +} + +static void reap_thread_state (_Inout_ struct thread_state1 *ts1, _In_ int sync_with_servicelease) +{ + os_mutexLock (&thread_states.lock); + ts1->state = THREAD_STATE_ZERO; + if (sync_with_servicelease) + nn_servicelease_statechange_barrier (gv.servicelease); + if (ts1->name != main_thread_name) + os_free (ts1->name); + os_mutexUnlock (&thread_states.lock); +} + +_Success_(return == 0) +int join_thread (_Inout_ struct thread_state1 *ts1) +{ + int ret; + assert (ts1->state == THREAD_STATE_ALIVE); + if (os_threadWaitExit (ts1->extTid, NULL) == os_resultSuccess) + ret = 0; + else + ret = ERR_UNSPECIFIED; + assert (vtime_asleep_p (ts1->vtime)); + reap_thread_state (ts1, 1); + return ret; +} + +void reset_thread_state (_Inout_opt_ struct thread_state1 *ts1) +{ + if (ts1) + { + reap_thread_state (ts1, 1); + ts1->name = NULL; + } +} + +void downgrade_main_thread (void) +{ + struct thread_state1 *ts1 = lookup_thread_state (); + thread_state_asleep (ts1); + logbuf_free (ts1->lb); + /* no need to sync with service lease: already stopped */ + reap_thread_state (ts1, 0); + tsd_thread_state = NULL; +} + +struct thread_state1 *get_thread_state (_In_ os_threadId id) +{ + unsigned i; + struct thread_state1 *ts = NULL; + + for (i = 0; i < thread_states.nthreads; i++) + { + if (os_threadEqual (thread_states.ts[i].extTid, id)) + { + ts = &thread_states.ts[i]; + break; + } + } + return ts; +} + +void log_stack_traces (void) +{ + unsigned i; + for (i = 0; i < thread_states.nthreads; i++) + { + if (thread_states.ts[i].state == THREAD_STATE_ALIVE) + { + log_stacktrace (thread_states.ts[i].name, thread_states.ts[i].tid); + } + } +} + diff --git a/src/core/ddsi/src/q_thread_inlines.c b/src/core/ddsi/src/q_thread_inlines.c new file mode 100644 index 0000000..0403137 --- /dev/null +++ b/src/core/ddsi/src/q_thread_inlines.c @@ -0,0 +1,15 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#define SUPPRESS_THREAD_INLINES + +#include "ddsi/q_thread.h" +#include "ddsi/q_thread_template.h" diff --git a/src/core/ddsi/src/q_time.c b/src/core/ddsi/src/q_time.c new file mode 100644 index 0000000..e602ec0 --- /dev/null +++ b/src/core/ddsi/src/q_time.c @@ -0,0 +1,224 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include + +#include "os/os.h" + +#include "ddsi/q_time.h" + +const nn_ddsi_time_t invalid_ddsi_timestamp = { -1, UINT32_MAX }; +const nn_ddsi_time_t ddsi_time_infinite = { INT32_MAX, UINT32_MAX }; +#if DDSI_DURATION_ACCORDING_TO_SPEC +const nn_duration_t duration_infinite = { INT32_MAX, INT32_MAX }; +#else +const nn_duration_t duration_infinite = { INT32_MAX, UINT32_MAX }; +#endif + +nn_wctime_t now (void) +{ + /* This function uses the wall clock. + * This clock is not affected by time spent in suspend mode. + * This clock is affected when the real time system clock jumps + * forwards/backwards */ + os_time tv; + nn_wctime_t t; + tv = os_timeGet (); + t.v = ((int64_t) tv.tv_sec * T_SECOND + tv.tv_nsec); + return t; +} + +nn_mtime_t now_mt (void) +{ + /* This function uses the monotonic clock. + * This clock stops while the system is in suspend mode. + * This clock is not affected by any jumps of the realtime clock. */ + os_time tv; + nn_mtime_t t; + tv = os_timeGetMonotonic (); + t.v = ((int64_t) tv.tv_sec * T_SECOND + tv.tv_nsec); + return t; +} + +nn_etime_t now_et (void) +{ + /* This function uses the elapsed clock. + * This clock is not affected by any jumps of the realtime clock. + * This clock does NOT stop when the system is in suspend mode. + * This clock stops when the system is shut down, and starts when the system is restarted. + * When restarted, there are no assumptions about the initial value of clock. */ + os_time tv; + nn_etime_t t; + tv = os_timeGetElapsed (); + t.v = ((int64_t) tv.tv_sec * T_SECOND + tv.tv_nsec); + return t; +} + +static void time_to_sec_usec (_Out_ int * __restrict sec, _Out_ int * __restrict usec, _In_ int64_t t) +{ + *sec = (int) (t / T_SECOND); + *usec = (int) (t % T_SECOND) / 1000; +} + +void mtime_to_sec_usec (_Out_ int * __restrict sec, _Out_ int * __restrict usec, _In_ nn_mtime_t t) +{ + time_to_sec_usec (sec, usec, t.v); +} + +void wctime_to_sec_usec (_Out_ int * __restrict sec, _Out_ int * __restrict usec, _In_ nn_wctime_t t) +{ + time_to_sec_usec (sec, usec, t.v); +} + +void etime_to_sec_usec (_Out_ int * __restrict sec, _Out_ int * __restrict usec, _In_ nn_etime_t t) +{ + time_to_sec_usec (sec, usec, t.v); +} + + +nn_mtime_t mtime_round_up (nn_mtime_t t, int64_t round) +{ + /* This function rounds up t to the nearest next multiple of round. + t is nanoseconds, round is milliseconds. Avoid functions from + maths libraries to keep code portable */ + assert (t.v >= 0 && round >= 0); + if (round == 0 || t.v == T_NEVER) + { + return t; + } + else + { + int64_t remainder = t.v % round; + if (remainder == 0) + { + return t; + } + else + { + nn_mtime_t u; + u.v = t.v + round - remainder; + return u; + } + } +} + +static int64_t add_duration_to_time (int64_t t, int64_t d) +{ + uint64_t sum; + assert (t >= 0 && d >= 0); + sum = (uint64_t)t + (uint64_t)d; + return sum >= T_NEVER ? T_NEVER : (int64_t)sum; +} + +nn_mtime_t add_duration_to_mtime (nn_mtime_t t, int64_t d) +{ + /* assumed T_NEVER <=> MAX_INT64 */ + nn_mtime_t u; + u.v = add_duration_to_time (t.v, d); + return u; +} + +nn_wctime_t add_duration_to_wctime (nn_wctime_t t, int64_t d) +{ + /* assumed T_NEVER <=> MAX_INT64 */ + nn_wctime_t u; + u.v = add_duration_to_time (t.v, d); + return u; +} + +nn_etime_t add_duration_to_etime (nn_etime_t t, int64_t d) +{ + /* assumed T_NEVER <=> MAX_INT64 */ + nn_etime_t u; + u.v = add_duration_to_time (t.v, d); + return u; +} + +int valid_ddsi_timestamp (nn_ddsi_time_t t) +{ + return t.seconds != invalid_ddsi_timestamp.seconds && t.fraction != invalid_ddsi_timestamp.fraction; +} + +static nn_ddsi_time_t nn_to_ddsi_time (int64_t t) +{ + if (t == T_NEVER) + return ddsi_time_infinite; + else + { + /* ceiling(ns * 2^32/10^9) -- can't change the ceiling to round-to-nearest + because that would break backwards compatibility, but round-to-nearest + of the inverse is correctly rounded anyway, so it shouldn't ever matter. */ + nn_ddsi_time_t x; + int ns = (int) (t % T_SECOND); + x.seconds = (int) (t / T_SECOND); + x.fraction = (unsigned) (((T_SECOND-1) + ((int64_t) ns << 32)) / T_SECOND); + return x; + } +} + +nn_ddsi_time_t nn_wctime_to_ddsi_time (nn_wctime_t t) +{ + return nn_to_ddsi_time (t.v); +} + +static int64_t nn_from_ddsi_time (nn_ddsi_time_t x) +{ + if (x.seconds == ddsi_time_infinite.seconds && x.fraction == ddsi_time_infinite.fraction) + return T_NEVER; + else + { + /* Round-to-nearest conversion of DDSI time fraction to nanoseconds */ + int ns = (int) (((int64_t) 2147483648u + (int64_t) x.fraction * T_SECOND) >> 32); + return x.seconds * (int64_t) T_SECOND + ns; + } +} + +nn_wctime_t nn_wctime_from_ddsi_time (nn_ddsi_time_t x) +{ + nn_wctime_t t; + t.v = nn_from_ddsi_time (x); + return t; +} + +#if DDSI_DURATION_ACCORDING_TO_SPEC +nn_duration_t nn_to_ddsi_duration (int64_t t) +{ + if (t == T_NEVER) + return duration_infinite; + else + { + nn_duration_t x; + x.sec = (int) (t / T_SECOND); + x.nanosec = (int) (t % T_SECOND); + return x; + } +} + +int64_t nn_from_ddsi_duration (nn_duration_t x) +{ + int64_t t; + if (x.sec == duration_infinite.sec && x.nanosec == duration_infinite.nanosec) + t = T_NEVER; + else + t = x.sec * T_SECOND + x.nanosec; + return t; +} +#else +nn_duration_t nn_to_ddsi_duration (int64_t x) +{ + return nn_to_ddsi_time (x); +} + +int64_t nn_from_ddsi_duration (nn_duration_t x) +{ + return nn_from_ddsi_time (x); +} +#endif diff --git a/src/core/ddsi/src/q_transmit.c b/src/core/ddsi/src/q_transmit.c new file mode 100644 index 0000000..e1e3c88 --- /dev/null +++ b/src/core/ddsi/src/q_transmit.c @@ -0,0 +1,1156 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include + +#include "os/os.h" + +#include "util/ut_avl.h" +#include "ddsi/q_whc.h" +#include "ddsi/q_entity.h" +#include "ddsi/q_addrset.h" +#include "ddsi/q_xmsg.h" +#include "ddsi/q_bswap.h" +#include "ddsi/q_misc.h" +#include "ddsi/q_thread.h" +#include "ddsi/q_xevent.h" +#include "ddsi/q_time.h" +#include "ddsi/q_config.h" +#include "ddsi/q_globals.h" +#include "ddsi/q_error.h" +#include "ddsi/q_transmit.h" +#include "ddsi/q_entity.h" +#include "ddsi/q_unused.h" +#include "ddsi/q_hbcontrol.h" +#include "ddsi/q_static_assert.h" + +#include "ddsi/ddsi_ser.h" + +#include "ddsi/sysdeps.h" + +#if __STDC_VERSION__ >= 199901L +#define POS_INFINITY_DOUBLE INFINITY +#elif defined HUGE_VAL +/* Hope for the best -- the only consequence of getting this wrong is + that T_NEVER may be printed as a fugly value instead of as +inf. */ +#define POS_INFINITY_DOUBLE (HUGE_VAL + HUGE_VAL) +#else +#define POS_INFINITY_DOUBLE 1e1000 +#endif + +static const struct wr_prd_match *root_rdmatch (const struct writer *wr) +{ + return ut_avlRoot (&wr_readers_treedef, &wr->readers); +} + +static int have_reliable_subs (const struct writer *wr) +{ + if (ut_avlIsEmpty (&wr->readers) || root_rdmatch (wr)->min_seq == MAX_SEQ_NUMBER) + return 0; + else + return 1; +} + +void writer_hbcontrol_init (struct hbcontrol *hbc) +{ + hbc->t_of_last_write.v = 0; + hbc->t_of_last_hb.v = 0; + hbc->t_of_last_ackhb.v = 0; + hbc->tsched.v = T_NEVER; + hbc->hbs_since_last_write = 0; + hbc->last_packetid = 0; +} + +static void writer_hbcontrol_note_hb (struct writer *wr, nn_mtime_t tnow, int ansreq) +{ + struct hbcontrol * const hbc = &wr->hbcontrol; + + if (ansreq) + hbc->t_of_last_ackhb = tnow; + hbc->t_of_last_hb = tnow; + + /* Count number of heartbeats since last write, used to lower the + heartbeat rate. Overflow doesn't matter, it'll just revert to a + highish rate for a short while. */ + hbc->hbs_since_last_write++; +} + +int64_t writer_hbcontrol_intv (const struct writer *wr, UNUSED_ARG (nn_mtime_t tnow)) +{ + struct hbcontrol const * const hbc = &wr->hbcontrol; + int64_t ret = config.const_hb_intv_sched; + size_t n_unacked; + + if (hbc->hbs_since_last_write > 2) + { + unsigned cnt = hbc->hbs_since_last_write; + while (cnt-- > 2 && 2 * ret < config.const_hb_intv_sched_max) + ret *= 2; + } + + n_unacked = whc_unacked_bytes (wr->whc); + if (n_unacked >= wr->whc_low + 3 * (wr->whc_high - wr->whc_low) / 4) + ret /= 2; + if (n_unacked >= wr->whc_low + (wr->whc_high - wr->whc_low) / 2) + ret /= 2; + if (wr->throttling) + ret /= 2; + if (ret < config.const_hb_intv_sched_min) + ret = config.const_hb_intv_sched_min; + return ret; +} + +void writer_hbcontrol_note_asyncwrite (struct writer *wr, nn_mtime_t tnow) +{ + struct hbcontrol * const hbc = &wr->hbcontrol; + nn_mtime_t tnext; + + /* Reset number of heartbeats since last write: that means the + heartbeat rate will go back up to the default */ + hbc->hbs_since_last_write = 0; + + /* We know this is new data, so we want a heartbeat event after one + base interval */ + tnext.v = tnow.v + config.const_hb_intv_sched; + if (tnext.v < hbc->tsched.v) + { + /* Insertion of a message with WHC locked => must now have at + least one unacked msg if there are reliable readers, so must + have a heartbeat scheduled. Do so now */ + hbc->tsched = tnext; + resched_xevent_if_earlier (wr->heartbeat_xevent, tnext); + } +} + +int writer_hbcontrol_must_send (const struct writer *wr, nn_mtime_t tnow /* monotonic */) +{ + struct hbcontrol const * const hbc = &wr->hbcontrol; + return (tnow.v >= hbc->t_of_last_hb.v + writer_hbcontrol_intv (wr, tnow)); +} + +struct nn_xmsg *writer_hbcontrol_create_heartbeat (struct writer *wr, nn_mtime_t tnow, int hbansreq, int issync) +{ + struct nn_xmsg *msg; + const nn_guid_t *prd_guid; + + ASSERT_MUTEX_HELD (&wr->e.lock); + assert (wr->reliable); + assert (hbansreq >= 0); + + if ((msg = nn_xmsg_new (gv.xmsgpool, &wr->e.guid.prefix, sizeof (InfoTS_t) + sizeof (Heartbeat_t), NN_XMSG_KIND_CONTROL)) == NULL) + /* out of memory at worst slows down traffic */ + return NULL; + + if (ut_avlIsEmpty (&wr->readers) || wr->num_reliable_readers == 0) + { + /* Not really supposed to come here, at least not for the first + case. Secondly, there really seems to be little use for + optimising reliable writers with only best-effort readers. And + in any case, it is always legal to multicast a heartbeat from a + reliable writer. */ + prd_guid = NULL; + } + else if (wr->seq != root_rdmatch (wr)->max_seq) + { + /* If the writer is ahead of its readers, multicast. Couldn't care + less about the pessimal cases such as multicasting when there + is one reliable reader & multiple best-effort readers. See + comment above. */ + prd_guid = NULL; + } + else + { + const int n_unacked = wr->num_reliable_readers - root_rdmatch (wr)->num_reliable_readers_where_seq_equals_max; + assert (n_unacked >= 0); + if (n_unacked == 0) + prd_guid = NULL; + else + { + assert (root_rdmatch (wr)->arbitrary_unacked_reader.entityid.u != NN_ENTITYID_UNKNOWN); + if (n_unacked > 1) + prd_guid = NULL; + else + prd_guid = &(root_rdmatch (wr)->arbitrary_unacked_reader); + } + } + + TRACE (("writer_hbcontrol: wr %x:%x:%x:%x ", PGUID (wr->e.guid))); + if (prd_guid == NULL) + TRACE (("multicasting ")); + else + TRACE (("unicasting to prd %x:%x:%x:%x ", PGUID (*prd_guid))); + TRACE (("(rel-prd %d seq-eq-max %d seq %"PRId64" maxseq %"PRId64")\n", + wr->num_reliable_readers, + ut_avlIsEmpty (&wr->readers) ? -1 : root_rdmatch (wr)->num_reliable_readers_where_seq_equals_max, + wr->seq, + ut_avlIsEmpty (&wr->readers) ? (seqno_t) -1 : root_rdmatch (wr)->max_seq)); + + if (prd_guid == NULL) + { + nn_xmsg_setdstN (msg, wr->as, wr->as_group); +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + nn_xmsg_setencoderid (msg, wr->partition_id); +#endif + add_Heartbeat (msg, wr, hbansreq, to_entityid (NN_ENTITYID_UNKNOWN), issync); + } + else + { + struct proxy_reader *prd; + if ((prd = ephash_lookup_proxy_reader_guid (prd_guid)) == NULL) + { + TRACE (("writer_hbcontrol: wr %x:%x:%x:%x unknown prd %x:%x:%x:%x\n", PGUID (wr->e.guid), PGUID (*prd_guid))); + nn_xmsg_free (msg); + return NULL; + } + /* set the destination explicitly to the unicast destination and the fourth + param of add_Heartbeat needs to be the guid of the reader */ + if (nn_xmsg_setdstPRD (msg, prd) < 0) + { + nn_xmsg_free (msg); + return NULL; + } +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + nn_xmsg_setencoderid (msg, wr->partition_id); +#endif + add_Heartbeat (msg, wr, hbansreq, prd_guid->entityid, issync); + } + + writer_hbcontrol_note_hb (wr, tnow, hbansreq); + return msg; +} + +static int writer_hbcontrol_ack_required_generic (const struct writer *wr, nn_mtime_t tlast, nn_mtime_t tnow, int piggyback) +{ + struct hbcontrol const * const hbc = &wr->hbcontrol; + const int64_t hb_intv_ack = config.const_hb_intv_sched; + + if (piggyback) + { + /* If it is likely that a heartbeat requiring an ack will go out + shortly after the sample was written, it is better to piggyback + it onto the sample. The current idea is that a write shortly + before the next heartbeat will go out should have one + piggybacked onto it, so that the scheduled heartbeat can be + suppressed. */ + if (tnow.v >= tlast.v + 4 * hb_intv_ack / 5) + return 2; + } + else + { + /* For heartbeat events use a slightly longer interval */ + if (tnow.v >= tlast.v + hb_intv_ack) + return 2; + } + + if (whc_unacked_bytes (wr->whc) >= wr->whc_low + (wr->whc_high - wr->whc_low) / 2) + { + if (tnow.v >= hbc->t_of_last_ackhb.v + config.const_hb_intv_sched_min) + return 2; + else if (tnow.v >= hbc->t_of_last_ackhb.v + config.const_hb_intv_min) + return 1; + } + + return 0; +} + +int writer_hbcontrol_ack_required (const struct writer *wr, nn_mtime_t tnow) +{ + struct hbcontrol const * const hbc = &wr->hbcontrol; + return writer_hbcontrol_ack_required_generic (wr, hbc->t_of_last_write, tnow, 0); +} + +struct nn_xmsg *writer_hbcontrol_piggyback (struct writer *wr, nn_mtime_t tnow, unsigned packetid, int *hbansreq) +{ + struct hbcontrol * const hbc = &wr->hbcontrol; + unsigned last_packetid; + nn_mtime_t tlast; + struct nn_xmsg *msg; + + tlast = hbc->t_of_last_write; + last_packetid = hbc->last_packetid; + + hbc->t_of_last_write = tnow; + hbc->last_packetid = packetid; + + /* Update statistics, intervals, scheduling of heartbeat event, + &c. -- there's no real difference between async and sync so we + reuse the async version. */ + writer_hbcontrol_note_asyncwrite (wr, tnow); + + *hbansreq = writer_hbcontrol_ack_required_generic (wr, tlast, tnow, 1); + if (*hbansreq >= 2) { + /* So we force a heartbeat in - but we also rely on our caller to + send the packet out */ + msg = writer_hbcontrol_create_heartbeat (wr, tnow, *hbansreq, 1); + } else if (last_packetid != packetid) { + /* If we crossed a packet boundary since the previous write, + piggyback a heartbeat, with *hbansreq determining whether or + not an ACK is needed. We don't force the packet out either: + this is just to ensure a regular flow of ACKs for cleaning up + the WHC & for allowing readers to NACK missing samples. */ + msg = writer_hbcontrol_create_heartbeat (wr, tnow, *hbansreq, 1); + } else { + *hbansreq = 0; + msg = NULL; + } + + if (msg) + { + TRACE (("heartbeat(wr %x:%x:%x:%x%s) piggybacked, resched in %g s (min-ack %"PRId64"%s, avail-seq %"PRId64", xmit %"PRId64")\n", + PGUID (wr->e.guid), + *hbansreq ? "" : " final", + (hbc->tsched.v == T_NEVER) ? POS_INFINITY_DOUBLE : (double) (hbc->tsched.v - tnow.v) / 1e9, + ut_avlIsEmpty (&wr->readers) ? -1 : root_rdmatch (wr)->min_seq, + ut_avlIsEmpty (&wr->readers) || root_rdmatch (wr)->all_have_replied_to_hb ? "" : "!", + whc_empty (wr->whc) ? -1 : whc_max_seq (wr->whc), READ_SEQ_XMIT(wr))); + } + + return msg; +} + +void add_Heartbeat (struct nn_xmsg *msg, struct writer *wr, int hbansreq, nn_entityid_t dst, int issync) +{ + struct nn_xmsg_marker sm_marker; + Heartbeat_t * hb; + seqno_t max = 0, min = 1; + + ASSERT_MUTEX_HELD (&wr->e.lock); + + assert (wr->reliable); + assert (hbansreq >= 0); + + if (config.meas_hb_to_ack_latency) + { + /* If configured to measure heartbeat-to-ack latency, we must add + a timestamp. No big deal if it fails. */ + nn_xmsg_add_timestamp (msg, now ()); + } + + hb = nn_xmsg_append (msg, &sm_marker, sizeof (Heartbeat_t)); + nn_xmsg_submsg_init (msg, sm_marker, SMID_HEARTBEAT); + + if (!hbansreq) + hb->smhdr.flags |= HEARTBEAT_FLAG_FINAL; + + hb->readerId = nn_hton_entityid (dst); + hb->writerId = nn_hton_entityid (wr->e.guid.entityid); + if (whc_empty (wr->whc)) + { + /* Really don't have data. Fake one at the current wr->seq. + We're not really allowed to generate heartbeats when the WHC is + empty, but it appears RTI sort-of needs them ... Now we use + GAPs, and allocate a sequence number specially for that. */ + assert (config.respond_to_rti_init_zero_ack_with_invalid_heartbeat || wr->seq >= 1); + max = wr->seq; + min = max; + if (config.respond_to_rti_init_zero_ack_with_invalid_heartbeat) + { + min += 1; + } + } + else + { + seqno_t seq_xmit; + min = whc_min_seq (wr->whc); + max = wr->seq; + seq_xmit = READ_SEQ_XMIT(wr); + assert (min <= max); + /* Informing readers of samples that haven't even been transmitted makes little sense, + but for transient-local data, we let the first heartbeat determine the time at which + we trigger wait_for_historical_data, so it had better be correct */ + if (!issync && seq_xmit < max && !wr->handle_as_transient_local) + { + /* When: queue data ; queue heartbeat ; transmit data ; update + seq_xmit, max may be < min. But we must never advertise the + minimum available sequence number incorrectly! */ + if (seq_xmit >= min) { + /* Advertise some but not all data */ + max = seq_xmit; + } else if (config.respond_to_rti_init_zero_ack_with_invalid_heartbeat) { + /* if we can generate an empty heartbeat => do so. */ + max = min - 1; + } else { + /* claim the existence of a sample we possibly haven't set + yet, at worst this causes a retransmission (but the + NackDelay usually takes care of that). */ + max = min; + } + } + } + hb->firstSN = toSN (min); + hb->lastSN = toSN (max); + + hb->count = ++wr->hbcount; + + nn_xmsg_submsg_setnext (msg, sm_marker); +} + +static int create_fragment_message_simple (struct writer *wr, seqno_t seq, struct serdata *serdata, struct nn_xmsg **pmsg) +{ + const size_t expected_inline_qos_size = 4+8+20+4 + 32; + struct nn_xmsg_marker sm_marker; + const unsigned char contentflag = (ddsi_serdata_is_empty (serdata) ? 0 : ddsi_serdata_is_key (serdata) ? DATA_FLAG_KEYFLAG : DATA_FLAG_DATAFLAG); + Data_t *data; + + ASSERT_MUTEX_HELD (&wr->e.lock); + + if ((*pmsg = nn_xmsg_new (gv.xmsgpool, &wr->e.guid.prefix, sizeof (InfoTimestamp_t) + sizeof (Data_t) + expected_inline_qos_size, NN_XMSG_KIND_DATA)) == NULL) + return ERR_OUT_OF_MEMORY; + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + /* use the partition_id from the writer to select the proper encoder */ + nn_xmsg_setencoderid (*pmsg, wr->partition_id); +#endif + + nn_xmsg_setdstN (*pmsg, wr->as, wr->as_group); + nn_xmsg_setmaxdelay (*pmsg, nn_from_ddsi_duration (wr->xqos->latency_budget.duration)); + nn_xmsg_add_timestamp (*pmsg, serdata->v.msginfo.timestamp); + data = nn_xmsg_append (*pmsg, &sm_marker, sizeof (Data_t)); + + nn_xmsg_submsg_init (*pmsg, sm_marker, SMID_DATA); + data->x.smhdr.flags = (unsigned char) (data->x.smhdr.flags | contentflag); + data->x.extraFlags = 0; + data->x.readerId = to_entityid (NN_ENTITYID_UNKNOWN); + data->x.writerId = nn_hton_entityid (wr->e.guid.entityid); + data->x.writerSN = toSN (seq); + data->x.octetsToInlineQos = (unsigned short) ((char*) (data+1) - ((char*) &data->x.octetsToInlineQos + 2)); + + if (wr->reliable) + nn_xmsg_setwriterseq (*pmsg, &wr->e.guid, seq); + + /* Adding parameters means potential reallocing, so sm, ddcmn now likely become invalid */ + if (wr->include_keyhash) + nn_xmsg_addpar_keyhash (*pmsg, serdata); + if (serdata->v.msginfo.statusinfo) + nn_xmsg_addpar_statusinfo (*pmsg, serdata->v.msginfo.statusinfo); + if (nn_xmsg_addpar_sentinel_ifparam (*pmsg) > 0) + { + data = nn_xmsg_submsg_from_marker (*pmsg, sm_marker); + data->x.smhdr.flags |= DATAFRAG_FLAG_INLINE_QOS; + } + + nn_xmsg_serdata (*pmsg, serdata, 0, ddsi_serdata_size (serdata)); + nn_xmsg_submsg_setnext (*pmsg, sm_marker); + return 0; +} + +int create_fragment_message (struct writer *wr, seqno_t seq, const struct nn_plist *plist, struct serdata *serdata, unsigned fragnum, struct proxy_reader *prd, struct nn_xmsg **pmsg, int isnew) +{ + /* We always fragment into FRAGMENT_SIZEd fragments, which are near + the smallest allowed fragment size & can't be bothered (yet) to + put multiple fragments into one DataFrag submessage if it makes + sense to send large messages, as it would e.g. on GigE with jumbo + frames. If the sample is small enough to fit into one Data + submessage, we require fragnum = 0 & generate a Data instead of a + DataFrag. + + Note: fragnum is 0-based here, 1-based in DDSI. But 0-based is + much easier ... + + Expected inline QoS size: header(4) + statusinfo(8) + keyhash(20) + + sentinel(4). Plus some spare cos I can't be bothered. */ + const int set_smhdr_flags_asif_data = config.buggy_datafrag_flags_mode; + const size_t expected_inline_qos_size = 4+8+20+4 + 32; + struct nn_xmsg_marker sm_marker; + void *sm; + Data_DataFrag_common_t *ddcmn; + int fragging; + unsigned fragstart, fraglen; + enum nn_xmsg_kind xmsg_kind = isnew ? NN_XMSG_KIND_DATA : NN_XMSG_KIND_DATA_REXMIT; + int ret = 0; + + ASSERT_MUTEX_HELD (&wr->e.lock); + + if (fragnum * config.fragment_size >= ddsi_serdata_size (serdata) && ddsi_serdata_size (serdata) > 0) + { + /* This is the first chance to detect an attempt at retransmitting + an non-existent fragment, which a malicious (or buggy) remote + reader can trigger. So we return an error instead of asserting + as we used to. */ + return ERR_INVALID; + } + + fragging = (config.fragment_size < ddsi_serdata_size (serdata)); + + if ((*pmsg = nn_xmsg_new (gv.xmsgpool, &wr->e.guid.prefix, sizeof (InfoTimestamp_t) + sizeof (DataFrag_t) + expected_inline_qos_size, xmsg_kind)) == NULL) + return ERR_OUT_OF_MEMORY; + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + /* use the partition_id from the writer to select the proper encoder */ + nn_xmsg_setencoderid (*pmsg, wr->partition_id); +#endif + + if (prd) + { + if (nn_xmsg_setdstPRD (*pmsg, prd) < 0) + { + nn_xmsg_free (*pmsg); + *pmsg = NULL; + return ERR_NO_ADDRESS; + } + /* retransmits: latency budget doesn't apply */ + } + else + { + nn_xmsg_setdstN (*pmsg, wr->as, wr->as_group); + nn_xmsg_setmaxdelay (*pmsg, nn_from_ddsi_duration (wr->xqos->latency_budget.duration)); + } + + /* Timestamp only needed once, for the first fragment */ + if (fragnum == 0) + { + nn_xmsg_add_timestamp (*pmsg, serdata->v.msginfo.timestamp); + } + + sm = nn_xmsg_append (*pmsg, &sm_marker, fragging ? sizeof (DataFrag_t) : sizeof (Data_t)); + ddcmn = sm; + + if (!fragging) + { + const unsigned char contentflag = (ddsi_serdata_is_empty (serdata) ? 0 : ddsi_serdata_is_key (serdata) ? DATA_FLAG_KEYFLAG : DATA_FLAG_DATAFLAG); + Data_t *data = sm; + nn_xmsg_submsg_init (*pmsg, sm_marker, SMID_DATA); + ddcmn->smhdr.flags = (unsigned char) (ddcmn->smhdr.flags | contentflag); + + fragstart = 0; + fraglen = ddsi_serdata_size (serdata); + ddcmn->octetsToInlineQos = (unsigned short) ((char*) (data+1) - ((char*) &ddcmn->octetsToInlineQos + 2)); + + if (wr->reliable) + nn_xmsg_setwriterseq (*pmsg, &wr->e.guid, seq); + } + else + { + const unsigned char contentflag = + set_smhdr_flags_asif_data + ? (ddsi_serdata_is_key (serdata) ? DATA_FLAG_KEYFLAG : DATA_FLAG_DATAFLAG) + : (ddsi_serdata_is_key (serdata) ? DATAFRAG_FLAG_KEYFLAG : 0); + DataFrag_t *frag = sm; + /* empty means size = 0, which means it never needs fragmenting */ + assert (!ddsi_serdata_is_empty (serdata)); + nn_xmsg_submsg_init (*pmsg, sm_marker, SMID_DATA_FRAG); + ddcmn->smhdr.flags = (unsigned char) (ddcmn->smhdr.flags | contentflag); + + frag->fragmentStartingNum = fragnum + 1; + frag->fragmentsInSubmessage = 1; + frag->fragmentSize = (unsigned short) config.fragment_size; + frag->sampleSize = ddsi_serdata_size (serdata); + + fragstart = fragnum * config.fragment_size; +#if MULTIPLE_FRAGS_IN_SUBMSG /* ugly hack for testing only */ + if (fragstart + config.fragment_size < ddsi_serdata_size (serdata) && + fragstart + 2 * config.fragment_size >= ddsi_serdata_size (serdata)) + frag->fragmentsInSubmessage++; + ret = frag->fragmentsInSubmessage; +#endif + + fraglen = config.fragment_size * frag->fragmentsInSubmessage; + if (fragstart + fraglen > ddsi_serdata_size (serdata)) + fraglen = ddsi_serdata_size (serdata) - fragstart; + ddcmn->octetsToInlineQos = (unsigned short) ((char*) (frag+1) - ((char*) &ddcmn->octetsToInlineQos + 2)); + + if (wr->reliable && (!isnew || fragstart + fraglen == ddsi_serdata_size (serdata))) + { + /* only set for final fragment for new messages; for rexmits we + want it set for all so we can do merging. FIXME: I guess the + writer should track both seq_xmit and the fragment number + ... */ + nn_xmsg_setwriterseq_fragid (*pmsg, &wr->e.guid, seq, fragnum + frag->fragmentsInSubmessage - 1); + } + } + + ddcmn->extraFlags = 0; + ddcmn->readerId = nn_hton_entityid (prd ? prd->e.guid.entityid : to_entityid (NN_ENTITYID_UNKNOWN)); + ddcmn->writerId = nn_hton_entityid (wr->e.guid.entityid); + ddcmn->writerSN = toSN (seq); + + if (xmsg_kind == NN_XMSG_KIND_DATA_REXMIT) + nn_xmsg_set_data_readerId (*pmsg, &ddcmn->readerId); + + Q_STATIC_ASSERT_CODE (DATA_FLAG_INLINE_QOS == DATAFRAG_FLAG_INLINE_QOS); + assert (!(ddcmn->smhdr.flags & DATAFRAG_FLAG_INLINE_QOS)); + + if (fragnum == 0) + { + int rc; + /* Adding parameters means potential reallocing, so sm, ddcmn now likely become invalid */ + if (wr->include_keyhash) + { + nn_xmsg_addpar_keyhash (*pmsg, serdata); + } + if (serdata->v.msginfo.statusinfo) + { + nn_xmsg_addpar_statusinfo (*pmsg, serdata->v.msginfo.statusinfo); + } + rc = nn_xmsg_addpar_sentinel_ifparam (*pmsg); + if (rc > 0) + { + ddcmn = nn_xmsg_submsg_from_marker (*pmsg, sm_marker); + ddcmn->smhdr.flags |= DATAFRAG_FLAG_INLINE_QOS; + } + } + + nn_xmsg_serdata (*pmsg, serdata, fragstart, fraglen); + nn_xmsg_submsg_setnext (*pmsg, sm_marker); +#if 0 + TRACE (("queue data%s %x:%x:%x:%x #%lld/%u[%u..%u)\n", + fragging ? "frag" : "", PGUID (wr->e.guid), + seq, fragnum+1, fragstart, fragstart + fraglen)); +#endif + + return ret; +} + +static void create_HeartbeatFrag (struct writer *wr, seqno_t seq, unsigned fragnum, struct proxy_reader *prd, struct nn_xmsg **pmsg) +{ + struct nn_xmsg_marker sm_marker; + HeartbeatFrag_t *hbf; + ASSERT_MUTEX_HELD (&wr->e.lock); + if ((*pmsg = nn_xmsg_new (gv.xmsgpool, &wr->e.guid.prefix, sizeof (HeartbeatFrag_t), NN_XMSG_KIND_CONTROL)) == NULL) + return; /* ignore out-of-memory: HeartbeatFrag is only advisory anyway */ +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + nn_xmsg_setencoderid (*pmsg, wr->partition_id); +#endif + if (prd) + { + if (nn_xmsg_setdstPRD (*pmsg, prd) < 0) + { + /* HeartbeatFrag is only advisory anyway */ + nn_xmsg_free (*pmsg); + *pmsg = NULL; + return; + } + } + else + { + nn_xmsg_setdstN (*pmsg, wr->as, wr->as_group); + } + hbf = nn_xmsg_append (*pmsg, &sm_marker, sizeof (HeartbeatFrag_t)); + nn_xmsg_submsg_init (*pmsg, sm_marker, SMID_HEARTBEAT_FRAG); + hbf->readerId = nn_hton_entityid (prd ? prd->e.guid.entityid : to_entityid (NN_ENTITYID_UNKNOWN)); + hbf->writerId = nn_hton_entityid (wr->e.guid.entityid); + hbf->writerSN = toSN (seq); + hbf->lastFragmentNum = fragnum + 1; /* network format is 1 based */ + + hbf->count = ++wr->hbfragcount; + + nn_xmsg_submsg_setnext (*pmsg, sm_marker); +} + +#if 0 +static int must_skip_frag (const char *frags_to_skip, unsigned frag) +{ + /* one based, for easier reading of logs */ + char str[14]; + int n, m; + if (frags_to_skip == NULL) + return 0; + n = snprintf (str, sizeof (str), ",%u,", frag + 1); + if (strstr (frags_to_skip, str)) + return 1; /* somewhere in middle */ + if (strncmp (frags_to_skip, str+1, (size_t)n-1) == 0) + return 1; /* first in list */ + str[--n] = 0; /* drop trailing comma */ + if (strcmp (frags_to_skip, str+1) == 0) + return 1; /* only one */ + m = (int)strlen (frags_to_skip); + if (m >= n && strcmp (frags_to_skip + m - n, str) == 0) + return 1; /* last one in list */ + return 0; +} +#endif + +static void transmit_sample_lgmsg_unlocked (struct nn_xpack *xp, struct writer *wr, seqno_t seq, const struct nn_plist *plist, serdata_t serdata, struct proxy_reader *prd, int isnew, unsigned nfrags) +{ + unsigned i; +#if 0 + const char *frags_to_skip = getenv ("SKIPFRAGS"); +#endif + assert(xp); + + for (i = 0; i < nfrags; i++) + { + struct nn_xmsg *fmsg = NULL; + struct nn_xmsg *hmsg = NULL; + int ret; +#if 0 + if (must_skip_frag (frags_to_skip, i)) + continue; +#endif + /* Ignore out-of-memory errors: we can't do anything about it, and + eventually we'll have to retry. But if a packet went out and + we haven't yet completed transmitting a fragmented message, add + a HeartbeatFrag. */ + os_mutexLock (&wr->e.lock); + ret = create_fragment_message (wr, seq, plist, serdata, i, prd, &fmsg, isnew); + if (ret >= 0) + { + if (nfrags > 1 && i + 1 < nfrags) + create_HeartbeatFrag (wr, seq, i, prd, &hmsg); + } + os_mutexUnlock (&wr->e.lock); + + if(fmsg) nn_xpack_addmsg (xp, fmsg, 0); + if(hmsg) nn_xpack_addmsg (xp, hmsg, 0); + +#if MULTIPLE_FRAGS_IN_SUBMSG /* ugly hack for testing only */ + if (ret > 1) + i += ret-1; +#endif + } + + /* Note: wr->heartbeat_xevent != NULL <=> wr is reliable */ + if (wr->heartbeat_xevent) + { + struct nn_xmsg *msg = NULL; + int hbansreq; + os_mutexLock (&wr->e.lock); + msg = writer_hbcontrol_piggyback + (wr, ddsi_serdata_twrite (serdata), nn_xpack_packetid (xp), &hbansreq); + os_mutexUnlock (&wr->e.lock); + if (msg) + { + nn_xpack_addmsg (xp, msg, 0); + if (hbansreq >= 2) + nn_xpack_send (xp, true); + } + } +} + +static void transmit_sample_unlocks_wr (struct nn_xpack *xp, struct writer *wr, seqno_t seq, const struct nn_plist *plist, serdata_t serdata, struct proxy_reader *prd, int isnew) +{ + /* on entry: &wr->e.lock held; on exit: lock no longer held */ + struct nn_xmsg *fmsg; + unsigned sz; + assert(xp); + + sz = ddsi_serdata_size (serdata); + if (sz > config.fragment_size || !isnew || plist != NULL || prd != NULL) + { + unsigned nfrags; + os_mutexUnlock (&wr->e.lock); + nfrags = (sz + config.fragment_size - 1) / config.fragment_size; + transmit_sample_lgmsg_unlocked (xp, wr, seq, plist, serdata, prd, isnew, nfrags); + return; + } + else if (create_fragment_message_simple (wr, seq, serdata, &fmsg) < 0) + { + os_mutexUnlock (&wr->e.lock); + return; + } + else + { + int hbansreq = 0; + struct nn_xmsg *hmsg; + + /* Note: wr->heartbeat_xevent != NULL <=> wr is reliable */ + if (wr->heartbeat_xevent) + hmsg = writer_hbcontrol_piggyback (wr, ddsi_serdata_twrite (serdata), nn_xpack_packetid (xp), &hbansreq); + else + hmsg = NULL; + + os_mutexUnlock (&wr->e.lock); + nn_xpack_addmsg (xp, fmsg, 0); + if(hmsg) + nn_xpack_addmsg (xp, hmsg, 0); + if (hbansreq >= 2) + nn_xpack_send (xp, true); + } +} + +int enqueue_sample_wrlock_held (struct writer *wr, seqno_t seq, const struct nn_plist *plist, serdata_t serdata, struct proxy_reader *prd, int isnew) +{ + unsigned i, sz, nfrags; + int enqueued = 1; + + ASSERT_MUTEX_HELD (&wr->e.lock); + + sz = ddsi_serdata_size (serdata); + nfrags = (sz + config.fragment_size - 1) / config.fragment_size; + if (nfrags == 0) + { + /* end-of-transaction messages are empty, but still need to be sent */ + nfrags = 1; + } + for (i = 0; i < nfrags && enqueued; i++) + { + struct nn_xmsg *fmsg = NULL; + struct nn_xmsg *hmsg = NULL; + /* Ignore out-of-memory errors: we can't do anything about it, and + eventually we'll have to retry. But if a packet went out and + we haven't yet completed transmitting a fragmented message, add + a HeartbeatFrag. */ + if (create_fragment_message (wr, seq, plist, serdata, i, prd, &fmsg, isnew) >= 0) + { + if (nfrags > 1 && i + 1 < nfrags) + create_HeartbeatFrag (wr, seq, i, prd, &hmsg); + } + if (isnew) + { + if(fmsg) qxev_msg (wr->evq, fmsg); + if(hmsg) qxev_msg (wr->evq, hmsg); + } + else + { + /* Implementations that never use NACKFRAG are allowed by the specification, and for such a peer, we must always force out the full sample on a retransmit request. I am not aware of any such implementations so leaving the override flag in, but not actually using it at the moment. Should set force = (i != 0) for "known bad" implementations. */ + const int force = 0; + if(fmsg) + { + enqueued = qxev_msg_rexmit_wrlock_held (wr->evq, fmsg, force); + } + /* Functioning of the system is not dependent on getting the + HeartbeatFrags out, so never force them into the queue. */ + if(hmsg) + { + if (enqueued > 1) + qxev_msg (wr->evq, hmsg); + else + nn_xmsg_free (hmsg); + } + } + } + return enqueued ? 0 : -1; +} + +static int insert_sample_in_whc (struct writer *wr, seqno_t seq, struct nn_plist *plist, serdata_t serdata, struct tkmap_instance *tk) +{ + /* returns: < 0 on error, 0 if no need to insert in whc, > 0 if inserted */ + int do_insert, insres, res; + + ASSERT_MUTEX_HELD (&wr->e.lock); + + if (config.enabled_logcats & LC_TRACE) + { + char ppbuf[1024]; + int tmp; + const char *tname = wr->topic ? wr->topic->name : "(null)"; + const char *ttname = wr->topic ? wr->topic->typename : "(null)"; + ppbuf[0] = '\0'; + tmp = sizeof (ppbuf) - 1; + nn_log (LC_TRACE, "write_sample %x:%x:%x:%x #%"PRId64"", PGUID (wr->e.guid), seq); + if (plist != 0 && (plist->present & PP_COHERENT_SET)) + nn_log (LC_TRACE, " C#%"PRId64"", fromSN (plist->coherent_set_seqno)); + nn_log (LC_TRACE, ": ST%u %s/%s:%s%s\n", + serdata->v.msginfo.statusinfo, tname, ttname, + ppbuf, tmp < (int) sizeof (ppbuf) ? "" : " (trunc)"); + } + + assert (wr->reliable || have_reliable_subs (wr) == 0); + + if (wr->reliable && have_reliable_subs (wr)) + do_insert = 1; + else if (wr->handle_as_transient_local || wr->startup_mode) + do_insert = 1; + else + do_insert = 0; + + if (!do_insert) + res = 0; + else if ((insres = whc_insert (wr->whc, writer_max_drop_seq (wr), seq, plist, serdata, tk)) < 0) + res = insres; + else + res = 1; + +#ifndef NDEBUG + if (wr->e.guid.entityid.u == NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER) + { + if (whc_findmax (wr->whc) == NULL) + assert (wr->c.pp->builtins_deleted); + } +#endif + return res; +} + +static int writer_may_continue (const struct writer *wr) +{ + return (whc_unacked_bytes (wr->whc) <= wr->whc_low && !wr->retransmitting) || (wr->state != WRST_OPERATIONAL); +} + + +static os_result throttle_writer (struct nn_xpack *xp, struct writer *wr) +{ + /* Sleep (cond_wait) without updating the thread's vtime: the + garbage collector won't free the writer while we leave it + unchanged. Alternatively, we could decide to go back to sleep, + allow garbage collection and check the writers existence every + time we get woken up. That would preclude the use of a condition + variable embedded in "struct writer", of course. + + For normal data that would be okay, because the thread forwarding + data from the network queue to rtps_write() simply uses the gid + and doesn't mind if the writer is freed halfway through (although + we would have to specify it may do so it!); but for internal + data, it would be absolutely unacceptable if they were ever to + take the path that would increase vtime. + + Currently, rtps_write/throttle_writer are used only by the normal + data forwarding path, the internal ones use write_sample(). Not + worth the bother right now. + + Therefore, we don't check the writer is still there after waking + up. + + Used to block on a combination of |xeventq| and |whc|, but that + is hard now that we use a per-writer condition variable. So + instead, wait until |whc| is small enough, then wait for + |xeventq|. The reasoning is that the WHC won't grow + spontaneously the way the xevent queue does. + + If the |whc| is dropping with in a configurable timeframe + (default 1 second) all connected readers that still haven't acked + all data, are considered "non-responsive" and data is no longer + resent to them, until a ACKNACK is received from that + reader. This implicitly clears the whc and unblocks the + writer. */ + + os_result result = os_resultSuccess; + nn_mtime_t tnow = now_mt (); + const nn_mtime_t abstimeout = add_duration_to_mtime (tnow, nn_from_ddsi_duration (wr->xqos->reliability.max_blocking_time)); + size_t n_unacked = whc_unacked_bytes (wr->whc); + + { + nn_vendorid_t ownvendorid = MY_VENDOR_ID; + ASSERT_MUTEX_HELD (&wr->e.lock); + assert (wr->throttling == 0); + assert (vtime_awake_p (lookup_thread_state ()->vtime)); + assert (!is_builtin_entityid(wr->e.guid.entityid, ownvendorid)); + } + + nn_log (LC_THROTTLE, "writer %x:%x:%x:%x waiting for whc to shrink below low-water mark (whc %"PRIuSIZE" low=%u high=%u)\n", PGUID (wr->e.guid), n_unacked, wr->whc_low, wr->whc_high); + wr->throttling = 1; + wr->throttle_count++; + + /* Force any outstanding packet out: there will be a heartbeat + requesting an answer in it. FIXME: obviously, this is doing + things the wrong way round ... */ + if (xp) + { + struct nn_xmsg *hbmsg = writer_hbcontrol_create_heartbeat (wr, tnow, 1, 1); + os_mutexUnlock (&wr->e.lock); + if (hbmsg) + { + nn_xpack_addmsg (xp, hbmsg, 0); + } + nn_xpack_send (xp, true); + os_mutexLock (&wr->e.lock); + } + + while (gv.rtps_keepgoing && !writer_may_continue (wr)) + { + int64_t reltimeout; + tnow = now_mt (); + reltimeout = abstimeout.v - tnow.v; + result = os_resultTimeout; + if (reltimeout > 0) + { + os_time timeout; + timeout.tv_sec = (int32_t) (reltimeout / T_SECOND); + timeout.tv_nsec = (int32_t) (reltimeout % T_SECOND); + thread_state_asleep (lookup_thread_state()); + result = os_condTimedWait (&wr->throttle_cond, &wr->e.lock, &timeout); + thread_state_awake (lookup_thread_state()); + } + if (result == os_resultTimeout) + { + break; + } + } + + wr->throttling = 0; + if (wr->state != WRST_OPERATIONAL) + { + /* gc_delete_writer may be waiting */ + os_condBroadcast (&wr->throttle_cond); + } + + n_unacked = whc_unacked_bytes (wr->whc); + nn_log (LC_THROTTLE, "writer %x:%x:%x:%x done waiting for whc to shrink below low-water mark (whc %"PRIuSIZE" low=%u high=%u)\n", PGUID (wr->e.guid), n_unacked, wr->whc_low, wr->whc_high); + return result; +} + +static int maybe_grow_whc (struct writer *wr) +{ + if (!wr->retransmitting && config.whc_adaptive && wr->whc_high < config.whc_highwater_mark) + { + nn_etime_t tnow = now_et(); + nn_etime_t tgrow = add_duration_to_etime (wr->t_whc_high_upd, 10 * T_MILLISECOND); + if (tnow.v >= tgrow.v) + { + uint32_t m = (config.whc_highwater_mark - wr->whc_high) / 32; + wr->whc_high = (m == 0) ? config.whc_highwater_mark : wr->whc_high + m; + wr->t_whc_high_upd = tnow; + return 1; + } + } + return 0; +} + +static int write_sample_eot (struct nn_xpack *xp, struct writer *wr, struct nn_plist *plist, serdata_t serdata, struct tkmap_instance *tk, int end_of_txn, int gc_allowed) +{ + int r; + seqno_t seq; + nn_mtime_t tnow; + + /* If GC not allowed, we must be sure to never block when writing. That is only the case for (true, aggressive) KEEP_LAST writers, and also only if there is no limit to how much unacknowledged data the WHC may contain. */ + assert(gc_allowed || (wr->xqos->history.kind == NN_KEEP_LAST_HISTORY_QOS && wr->aggressive_keep_last && wr->whc_low == INT32_MAX)); + + if (ddsi_serdata_size (serdata) > config.max_sample_size) + { + char ppbuf[1024]; + int tmp; + const char *tname = wr->topic ? wr->topic->name : "(null)"; + const char *ttname = wr->topic ? wr->topic->typename : "(null)"; + ppbuf[0] = '\0'; + tmp = sizeof (ppbuf) - 1; + NN_WARNING ("dropping oversize (%u > %u) sample from local writer %x:%x:%x:%x %s/%s:%s%s\n", + ddsi_serdata_size (serdata), config.max_sample_size, + PGUID (wr->e.guid), tname, ttname, ppbuf, + tmp < (int) sizeof (ppbuf) ? "" : " (trunc)"); + r = ERR_INVALID_DATA; + goto drop; + } + + os_mutexLock (&wr->e.lock); + + if (end_of_txn) + { + wr->cs_seq = 0; + } + + /* If WHC overfull, block. */ + { + size_t unacked_bytes = whc_unacked_bytes (wr->whc); + if (unacked_bytes > wr->whc_high) + { + os_result ores; + assert(gc_allowed); /* also see beginning of the function */ + if (config.prioritize_retransmit && wr->retransmitting) + ores = throttle_writer (xp, wr); + else + { + maybe_grow_whc (wr); + if (unacked_bytes <= wr->whc_high) + ores = os_resultSuccess; + else + ores = throttle_writer (xp, wr); + } + if (ores == os_resultTimeout) + { + os_mutexUnlock (&wr->e.lock); + r = ERR_TIMEOUT; + goto drop; + } + } + } + + /* Always use the current monotonic time */ + tnow = now_mt (); + ddsi_serdata_set_twrite (serdata, tnow); + + seq = ++wr->seq; + if (wr->cs_seq != 0) + { + if (plist == NULL) + { + plist = os_malloc (sizeof (*plist)); + nn_plist_init_empty (plist); + } + assert (!(plist->present & PP_COHERENT_SET)); + plist->present |= PP_COHERENT_SET; + plist->coherent_set_seqno = toSN (wr->cs_seq); + } + + if ((r = insert_sample_in_whc (wr, seq, plist, serdata, tk)) < 0) + { + /* Failure of some kind */ + os_mutexUnlock (&wr->e.lock); + if (plist != NULL) + { + nn_plist_fini (plist); + os_free (plist); + } + } + else + { + /* Note the subtlety of enqueueing with the lock held but + transmitting without holding the lock. Still working on + cleaning that up. */ + if (xp) + { + /* If all reliable readers disappear between unlocking the writer and + * creating the message, the WHC will free the plist (if any). Currently, + * plist's are only used for coherent sets, which is assumed to be rare, + * which in turn means that an extra copy doesn't hurt too badly ... */ + nn_plist_t plist_stk, *plist_copy; + if (plist == NULL) + plist_copy = NULL; + else + { + plist_copy = &plist_stk; + nn_plist_copy (plist_copy, plist); + } + transmit_sample_unlocks_wr (xp, wr, seq, plist_copy, serdata, NULL, 1); + if (plist_copy) + nn_plist_fini (plist_copy); + } + else + { + if (wr->heartbeat_xevent) + writer_hbcontrol_note_asyncwrite (wr, tnow); + enqueue_sample_wrlock_held (wr, seq, plist, serdata, NULL, 1); + os_mutexUnlock (&wr->e.lock); + } + + /* If not actually inserted, WHC didn't take ownership of plist */ + if (r == 0 && plist != NULL) + { + nn_plist_fini (plist); + os_free (plist); + } + } + +drop: + /* FIXME: shouldn't I move the ddsi_serdata_unref call to the callers? */ + ddsi_serdata_unref (serdata); + return r; +} + +int write_sample_gc (struct nn_xpack *xp, struct writer *wr, serdata_t serdata, struct tkmap_instance *tk) +{ + return write_sample_eot (xp, wr, NULL, serdata, tk, 0, 1); +} + +int write_sample_nogc (struct nn_xpack *xp, struct writer *wr, serdata_t serdata, struct tkmap_instance *tk) +{ + return write_sample_eot (xp, wr, NULL, serdata, tk, 0, 0); +} + +int write_sample_gc_notk (struct nn_xpack *xp, struct writer *wr, serdata_t serdata) +{ + struct tkmap_instance *tk; + int res; + tk = (ddsi_plugin.rhc_lookup_fn) (serdata); + res = write_sample_eot (xp, wr, NULL, serdata, tk, 0, 1); + (ddsi_plugin.rhc_unref_fn) (tk); + return res; +} + +int write_sample_nogc_notk (struct nn_xpack *xp, struct writer *wr, serdata_t serdata) +{ + struct tkmap_instance *tk; + int res; + tk = (ddsi_plugin.rhc_lookup_fn) (serdata); + res = write_sample_eot (xp, wr, NULL, serdata, tk, 0, 0); + (ddsi_plugin.rhc_unref_fn) (tk); + return res; +} + diff --git a/src/core/ddsi/src/q_whc.c b/src/core/ddsi/src/q_whc.c new file mode 100644 index 0000000..317dfe2 --- /dev/null +++ b/src/core/ddsi/src/q_whc.c @@ -0,0 +1,1085 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include + +#include "os/os.h" +#include "ddsi/ddsi_ser.h" +#include "ddsi/q_unused.h" +#include "ddsi/q_config.h" +#include "ddsi/q_whc.h" +#include "q__osplser.h" +#include "dds__tkmap.h" + +/* Avoiding all nn_log-related activities when LC_WHC is not set + (and it hardly ever is, as it is not even included in "trace") + saves a couple of % CPU on a high-rate publisher - that's worth + it. So we need a macro & a support function. */ +static int trace_whc (const char *fmt, ...) +{ + va_list ap; + va_start (ap, fmt); + nn_vlog (LC_WHC, fmt, ap); + va_end (ap); + return 0; +} +#define TRACE_WHC(args) ((config.enabled_logcats & LC_WHC) ? (trace_whc args) : 0) + +/* Hash + interval tree adminitration of samples-by-sequence number + * - by definition contains all samples in WHC (unchanged from older versions) + * Circular array of samples per instance, inited to all 0 + * - length is max(durability_service.history_depth, history.depth), KEEP_ALL => as-if 0 + * - no instance index if above length 0 + * - each sample (i.e., whc_node): backpointer into index + * - maintain index of latest sample, end of history then trivially follows from index arithmetic + * Overwriting in insert drops them from index, depending on "aggressiveness" from by-seq + * - special case for no readers (i.e. no ACKs) and history > transient-local history + * - cleaning up after ACKs has additional pruning stage for same case + */ + +static void insert_whcn_in_hash (struct whc *whc, struct whc_node *whcn); +static void whc_delete_one (struct whc *whc, struct whc_node *whcn); +static int compare_seq (const void *va, const void *vb); +static unsigned whc_remove_acked_messages_full (struct whc *whc, seqno_t max_drop_seq, struct whc_node **deferred_free_list); + +static const ut_avlTreedef_t whc_seq_treedef = + UT_AVL_TREEDEF_INITIALIZER (offsetof (struct whc_intvnode, avlnode), offsetof (struct whc_intvnode, min), compare_seq, 0); + +#if USE_EHH +static uint32_t whc_seq_entry_hash (const void *vn) +{ + const struct whc_seq_entry *n = vn; + /* we hash the lower 32 bits, on the assumption that with 4 billion + samples in between there won't be significant correlation */ + const uint64_t c = UINT64_C(16292676669999574021); + const uint32_t x = (uint32_t) n->seq; + return (unsigned) ((x * c) >> 32); +} + +static int whc_seq_entry_eq (const void *va, const void *vb) +{ + const struct whc_seq_entry *a = va; + const struct whc_seq_entry *b = vb; + return a->seq == b->seq; +} +#else +static uint32_t whc_node_hash (const void *vn) +{ + const struct whc_node *n = vn; + /* we hash the lower 32 bits, on the assumption that with 4 billion + samples in between there won't be significant correlation */ + const uint64_t c = UINT64_C(16292676669999574021); + const uint32_t x = (uint32_t) n->seq; + return (unsigned) ((x * c) >> 32); +} + +static int whc_node_eq (const void *va, const void *vb) +{ + const struct whc_node *a = va; + const struct whc_node *b = vb; + return a->seq == b->seq; +} +#endif + +static uint32_t whc_idxnode_hash_key (const void *vn) +{ + const struct whc_idxnode *n = vn; + return (uint32_t)n->iid; +} + +static int whc_idxnode_eq_key (const void *va, const void *vb) +{ + const struct whc_idxnode *a = va; + const struct whc_idxnode *b = vb; + return (a->iid == b->iid); +} + +static int compare_seq (const void *va, const void *vb) +{ + const seqno_t *a = va; + const seqno_t *b = vb; + return (*a == *b) ? 0 : (*a < *b) ? -1 : 1; +} + +static struct whc_node *whc_findmax_procedurally (const struct whc *whc) +{ + if (whc->seq_size == 0) + return NULL; + else if (whc->open_intv->first) + { + /* last is only valid iff first != NULL */ + return whc->open_intv->last; + } + else + { + struct whc_intvnode *intv = ut_avlFindPred (&whc_seq_treedef, &whc->seq, whc->open_intv); + assert (intv && intv->first); + return intv->last; + } +} + +static void check_whc (const struct whc *whc) +{ + /* there's much more we can check, but it gets expensive quite + quickly: all nodes but open_intv non-empty, non-overlapping and + non-contiguous; min & maxp1 of intervals correct; each interval + contiguous; all samples in seq & in seqhash; tlidx \subseteq seq; + seq-number ordered list correct; &c. */ + assert (whc->open_intv != NULL); + assert (whc->open_intv == ut_avlFindMax (&whc_seq_treedef, &whc->seq)); + assert (ut_avlFindSucc (&whc_seq_treedef, &whc->seq, whc->open_intv) == NULL); + if (whc->maxseq_node) + { + assert (whc->maxseq_node->next_seq == NULL); + } + if (whc->open_intv->first) + { + assert (whc->open_intv->last); + assert (whc->maxseq_node == whc->open_intv->last); + assert (whc->open_intv->min < whc->open_intv->maxp1); + assert (whc->maxseq_node->seq + 1 == whc->open_intv->maxp1); + } + else + { + assert (whc->open_intv->min == whc->open_intv->maxp1); + } + assert (whc->maxseq_node == whc_findmax_procedurally (whc)); + +#if 1 && !defined(NDEBUG) + { + struct whc_intvnode *firstintv; + struct whc_node *cur; + seqno_t prevseq = 0; + firstintv = ut_avlFindMin (&whc_seq_treedef, &whc->seq); + assert (firstintv); + cur = firstintv->first; + while (cur) + { + assert (cur->seq > prevseq); + prevseq = cur->seq; + assert (whc_findseq (whc, cur->seq) == cur); + cur = cur->next_seq; + } + } +#endif +} + +static void insert_whcn_in_hash (struct whc *whc, struct whc_node *whcn) +{ + /* precondition: whcn is not in hash */ +#if USE_EHH + struct whc_seq_entry e = { .seq = whcn->seq, .whcn = whcn }; + if (!ut_ehhAdd (whc->seq_hash, &e)) + assert(0); +#else + if (!ut_hhAdd (whc->seq_hash, whcn)) + assert(0); +#endif +} + +static void remove_whcn_from_hash (struct whc *whc, struct whc_node *whcn) +{ + /* precondition: whcn is in hash */ +#if USE_EHH + struct whc_seq_entry e = { .seq = whcn->seq }; + if (!ut_ehhRemove(whc->seq_hash, &e)) + assert(0); +#else + if (!ut_hhRemove(whc->seq_hash, whcn)) + assert(0); +#endif +} + +struct whc_node *whc_findseq (const struct whc *whc, seqno_t seq) +{ +#if USE_EHH + struct whc_seq_entry e = { .seq = seq }, *r; + if ((r = ut_ehhLookup(whc->seq_hash, &e)) != NULL) + return r->whcn; + else + return NULL; +#else + struct whc_node template; + template.seq = seq; + return ut_hhLookup(whc->seq_hash, &template); +#endif +} + + +struct whc *whc_new (int is_transient_local, unsigned hdepth, unsigned tldepth, size_t sample_overhead) +{ + struct whc *whc; + struct whc_intvnode *intv; + + assert((hdepth == 0 || tldepth <= hdepth) || is_transient_local); + + whc = os_malloc (sizeof (*whc)); + whc->is_transient_local = is_transient_local ? 1 : 0; + whc->hdepth = hdepth; + whc->tldepth = tldepth; + whc->idxdepth = hdepth > tldepth ? hdepth : tldepth; + whc->seq_size = 0; + whc->max_drop_seq = 0; + whc->unacked_bytes = 0; + whc->total_bytes = 0; + whc->sample_overhead = sample_overhead; +#if USE_EHH + whc->seq_hash = ut_ehhNew (sizeof (struct whc_seq_entry), 32, whc_seq_entry_hash, whc_seq_entry_eq); +#else + whc->seq_hash = ut_hhNew(32, whc_node_hash, whc_node_eq); +#endif + + if (whc->idxdepth > 0) + whc->idx_hash = ut_hhNew(32, whc_idxnode_hash_key, whc_idxnode_eq_key); + else + whc->idx_hash = NULL; + + /* seq interval tree: always has an "open" node */ + ut_avlInit (&whc_seq_treedef, &whc->seq); + intv = os_malloc (sizeof (*intv)); + intv->min = intv->maxp1 = 1; + intv->first = intv->last = NULL; + ut_avlInsert (&whc_seq_treedef, &whc->seq, intv); + whc->open_intv = intv; + whc->maxseq_node = NULL; + + /* hack */ + nn_freelist_init (&whc->freelist, UINT32_MAX, offsetof (struct whc_node, next_seq)); + + check_whc (whc); + return whc; +} + +static void free_whc_node_contents (struct whc *whc, struct whc_node *whcn) +{ + ddsi_serdata_unref (whcn->serdata); + if (whcn->plist) { + nn_plist_fini (whcn->plist); + os_free (whcn->plist); + } +} + +void whc_free (struct whc *whc) +{ + /* Freeing stuff without regards for maintaining data structures */ + check_whc (whc); + + if (whc->idx_hash) + { + struct ut_hhIter it; + struct whc_idxnode *n; + for (n = ut_hhIterFirst(whc->idx_hash, &it); n != NULL; n = ut_hhIterNext(&it)) + os_free(n); + ut_hhFree(whc->idx_hash); + } + + { + struct whc_node *whcn = whc->maxseq_node; + while (whcn) + { + struct whc_node *tmp = whcn; +/* The compiler doesn't realize that whcn->prev_seq is always initialized. */ +OS_WARNING_MSVC_OFF(6001); + whcn = whcn->prev_seq; +OS_WARNING_MSVC_ON(6001); + free_whc_node_contents (whc, tmp); + os_free (tmp); + } + } + + ut_avlFree (&whc_seq_treedef, &whc->seq, os_free); + nn_freelist_fini (&whc->freelist, os_free); + +#if USE_EHH + ut_ehhFree (whc->seq_hash); +#else + ut_hhFree (whc->seq_hash); +#endif + os_free (whc); +} + +int whc_empty (const struct whc *whc) +{ + return whc->seq_size == 0; +} + +seqno_t whc_min_seq (const struct whc *whc) +{ + /* precond: whc not empty */ + const struct whc_intvnode *intv; + check_whc (whc); + assert (!whc_empty (whc)); + intv = ut_avlFindMin (&whc_seq_treedef, &whc->seq); + assert (intv); + /* not empty, open node may be anything but is (by definition) + findmax, and whc is claimed to be non-empty, so min interval + can't be empty */ + assert (intv->maxp1 > intv->min); + return intv->min; +} + +struct whc_node *whc_findmax (const struct whc *whc) +{ + check_whc (whc); + return (struct whc_node *) whc->maxseq_node; +} + +seqno_t whc_max_seq (const struct whc *whc) +{ + /* precond: whc not empty */ + check_whc (whc); + assert (!whc_empty (whc)); + assert (whc->maxseq_node != NULL); + return whc->maxseq_node->seq; +} + +static struct whc_node *find_nextseq_intv (struct whc_intvnode **p_intv, const struct whc *whc, seqno_t seq) +{ + struct whc_node *n; + struct whc_intvnode *intv; + if ((n = whc_findseq (whc, seq)) == NULL) + { + /* don't know seq => lookup interval with min > seq (intervals are + contiguous, so if we don't know seq, an interval [X,Y) with X < + SEQ < Y can't exist */ +#ifndef NDEBUG + { + struct whc_intvnode *predintv = ut_avlLookupPredEq (&whc_seq_treedef, &whc->seq, &seq); + assert (predintv == NULL || predintv->maxp1 <= seq); + } +#endif + if ((intv = ut_avlLookupSuccEq (&whc_seq_treedef, &whc->seq, &seq)) == NULL) { + assert (ut_avlLookupPredEq (&whc_seq_treedef, &whc->seq, &seq) == whc->open_intv); + return NULL; + } else if (intv->min < intv->maxp1) { /* only if not empty interval */ + assert (intv->min > seq); + *p_intv = intv; + return intv->first; + } else { /* but note: only open_intv may be empty */ + assert (intv == whc->open_intv); + return NULL; + } + } + else if (n->next_seq == NULL) + { + assert (n == whc->maxseq_node); + return NULL; + } + else + { + assert (whc->maxseq_node != NULL); + assert (n->seq < whc->maxseq_node->seq); + n = n->next_seq; + *p_intv = ut_avlLookupPredEq (&whc_seq_treedef, &whc->seq, &n->seq); + return n; + } +} + +seqno_t whc_next_seq (const struct whc *whc, seqno_t seq) +{ + struct whc_node *n; + struct whc_intvnode *intv; + check_whc (whc); + if ((n = find_nextseq_intv (&intv, whc, seq)) == NULL) + return MAX_SEQ_NUMBER; + else + return n->seq; +} + +struct whc_node* whc_next_node(const struct whc *whc, seqno_t seq) +{ + struct whc_intvnode *intv; + check_whc (whc); + return find_nextseq_intv(&intv, whc, seq); +} + +static void delete_one_sample_from_idx (struct whc *whc, struct whc_node *whcn) +{ + struct whc_idxnode * const idxn = whcn->idxnode; + assert (idxn != NULL); + assert (idxn->hist[idxn->headidx] != NULL); + assert (idxn->hist[whcn->idxnode_pos] == whcn); + if (whcn->idxnode_pos != idxn->headidx) + idxn->hist[whcn->idxnode_pos] = NULL; + else + { +#ifndef NDEBUG + unsigned i; + for (i = 0; i < whc->idxdepth; i++) + assert (i == idxn->headidx || idxn->hist[i] == NULL); +#endif + if (!ut_hhRemove (whc->idx_hash, idxn)) + assert (0); + dds_tkmap_instance_unref(idxn->tk); + os_free (idxn); + } + whcn->idxnode = NULL; +} + +static void free_one_instance_from_idx (struct whc *whc, seqno_t max_drop_seq, struct whc_idxnode *idxn) +{ + unsigned i; + for (i = 0; i < whc->idxdepth; i++) + { + if (idxn->hist[i]) + { + struct whc_node *oldn = idxn->hist[i]; + oldn->idxnode = NULL; + if (oldn->seq <= max_drop_seq) + { + TRACE_WHC((" prune tl whcn %p\n", (void *)oldn)); + assert(oldn != whc->maxseq_node); + whc_delete_one (whc, oldn); + } + } + } + os_free(idxn); +} + +static void delete_one_instance_from_idx (struct whc *whc, seqno_t max_drop_seq, struct whc_idxnode *idxn) +{ + if (!ut_hhRemove (whc->idx_hash, idxn)) + assert (0); + free_one_instance_from_idx (whc, max_drop_seq, idxn); +} + +static int whcn_in_tlidx (const struct whc *whc, const struct whc_idxnode *idxn, unsigned pos) +{ + if (idxn == NULL) + return 0; + else + { + unsigned d = (idxn->headidx + (pos > idxn->headidx ? whc->idxdepth : 0)) - pos; + assert (d < whc->idxdepth); + return d < whc->tldepth; + } +} + +void whc_downgrade_to_volatile (struct whc *whc) +{ + seqno_t old_max_drop_seq; + struct whc_node *deferred_free_list; + + /* We only remove them from whc->tlidx: we don't remove them from + whc->seq yet. That'll happen eventually. */ + check_whc (whc); + + if (whc->idxdepth == 0) + { + /* if not maintaining an index at all, this is nonsense */ + return; + } + + assert (!whc->is_transient_local); + if (whc->tldepth > 0) + { + assert(whc->hdepth == 0 || whc->tldepth <= whc->hdepth); + whc->tldepth = 0; + if (whc->hdepth == 0) + { + struct ut_hhIter it; + struct whc_idxnode *n; + for (n = ut_hhIterFirst(whc->idx_hash, &it); n != NULL; n = ut_hhIterNext(&it)) + free_one_instance_from_idx (whc, 0, n); + ut_hhFree(whc->idx_hash); + whc->idxdepth = 0; + whc->idx_hash = NULL; + } + } + + /* Immediately drop them from the WHC (used to delay it until the + next ack); but need to make sure remove_acked_messages processes + them all. */ + old_max_drop_seq = whc->max_drop_seq; + whc->max_drop_seq = 0; + whc_remove_acked_messages_full (whc, old_max_drop_seq, &deferred_free_list); + whc_free_deferred_free_list (whc, deferred_free_list); + assert (whc->max_drop_seq == old_max_drop_seq); +} + +static size_t whcn_size (const struct whc *whc, const struct whc_node *whcn) +{ + size_t sz = ddsi_serdata_size (whcn->serdata); + return sz + ((sz + config.fragment_size - 1) / config.fragment_size) * whc->sample_overhead; +} + +static void whc_delete_one_intv (struct whc *whc, struct whc_intvnode **p_intv, struct whc_node **p_whcn) +{ + /* Removes *p_whcn, possibly deleting or splitting *p_intv, as the + case may be. Does *NOT* update whc->seq_size. *p_intv must be + the interval containing *p_whcn (&& both must actually exist). + + Returns: + - 0 if delete failed (only possible cause is memory exhaustion), + in which case *p_intv & *p_whcn are undefined; + - 1 if successful, in which case *p_intv & *p_whcn are set + correctly for the next sample in sequence number order */ + struct whc_intvnode *intv = *p_intv; + struct whc_node *whcn = *p_whcn; + assert (whcn->seq >= intv->min && whcn->seq < intv->maxp1); + *p_whcn = whcn->next_seq; + + /* If it is in the tlidx, take it out. Transient-local data never gets here */ + if (whcn->idxnode) + delete_one_sample_from_idx (whc, whcn); + if (whcn->unacked) + { + assert (whc->unacked_bytes >= whcn->size); + whc->unacked_bytes -= whcn->size; + whcn->unacked = 0; + } + + /* Take it out of seqhash; deleting it from the list ordered on + sequence numbers is left to the caller (it has to be done unconditionally, + but remove_acked_messages defers it until the end or a skipped node). */ + remove_whcn_from_hash (whc, whcn); + + /* We may have introduced a hole & have to split the interval + node, or we may have nibbled of the first one, or even the + last one. */ + if (whcn == intv->first) + { + if (whcn == intv->last && intv != whc->open_intv) + { + struct whc_intvnode *tmp = intv; + *p_intv = ut_avlFindSucc (&whc_seq_treedef, &whc->seq, intv); + /* only sample in interval and not the open interval => delete interval */ + ut_avlDelete (&whc_seq_treedef, &whc->seq, tmp); + os_free (tmp); + } + else + { + intv->first = whcn->next_seq; + intv->min++; + assert (intv->first != NULL || intv == whc->open_intv); + assert (intv->min < intv->maxp1 || intv == whc->open_intv); + assert ((intv->first == NULL) == (intv->min == intv->maxp1)); + } + } + else if (whcn == intv->last) + { + /* well, at least it isn't the first one & so the interval is + still non-empty and we don't have to drop the interval */ + assert (intv->min < whcn->seq); + assert (whcn->prev_seq); + assert (whcn->prev_seq->seq + 1 == whcn->seq); + intv->last = whcn->prev_seq; + intv->maxp1--; + *p_intv = ut_avlFindSucc (&whc_seq_treedef, &whc->seq, intv); + } + else + { + /* somewhere in the middle => split the interval (ideally, + would split it lazily, but it really is a transient-local + issue only, and so we can (for now) get away with splitting + it greedily */ + struct whc_intvnode *new_intv; + ut_avlIPath_t path; + + new_intv = os_malloc (sizeof (*new_intv)); + + /* new interval starts at the next node */ + assert (whcn->next_seq); + assert (whcn->seq + 1 == whcn->next_seq->seq); + new_intv->first = whcn->next_seq; + new_intv->last = intv->last; + new_intv->min = whcn->seq + 1; + new_intv->maxp1 = intv->maxp1; + intv->last = whcn->prev_seq; + intv->maxp1 = whcn->seq; + assert (intv->min < intv->maxp1); + assert (new_intv->min < new_intv->maxp1); + + /* insert new node & continue the loop with intv set to the + new interval */ + if (ut_avlLookupIPath (&whc_seq_treedef, &whc->seq, &new_intv->min, &path) != NULL) + assert (0); + ut_avlInsertIPath (&whc_seq_treedef, &whc->seq, new_intv, &path); + + if (intv == whc->open_intv) + whc->open_intv = new_intv; + *p_intv = new_intv; + } +} + +static void whc_delete_one (struct whc *whc, struct whc_node *whcn) +{ + struct whc_intvnode *intv; + struct whc_node *whcn_tmp = whcn; + intv = ut_avlLookupPredEq (&whc_seq_treedef, &whc->seq, &whcn->seq); + assert (intv != NULL); + whc_delete_one_intv (whc, &intv, &whcn); + if (whcn_tmp->prev_seq) + whcn_tmp->prev_seq->next_seq = whcn_tmp->next_seq; + if (whcn_tmp->next_seq) + whcn_tmp->next_seq->prev_seq = whcn_tmp->prev_seq; + whcn_tmp->next_seq = NULL; + whc_free_deferred_free_list (whc, whcn_tmp); + whc->seq_size--; +} + +void whc_free_deferred_free_list (struct whc *whc, struct whc_node *deferred_free_list) +{ + if (deferred_free_list) + { + struct whc_node *cur, *last; + uint32_t n = 0; + for (cur = deferred_free_list, last = NULL; cur; last = cur, cur = cur->next_seq) + { + n++; + free_whc_node_contents (whc, cur); + } + cur = nn_freelist_pushmany (&whc->freelist, deferred_free_list, last, n); + while (cur) + { + struct whc_node *tmp = cur; + cur = cur->next_seq; + os_free (tmp); + } + } +} + +static unsigned whc_remove_acked_messages_noidx (struct whc *whc, seqno_t max_drop_seq, struct whc_node **deferred_free_list) +{ + struct whc_intvnode *intv; + struct whc_node *whcn; + unsigned ndropped = 0; + + /* In the trivial case of an empty WHC, get out quickly */ + if (max_drop_seq <= whc->max_drop_seq || whc->maxseq_node == NULL) + { + if (max_drop_seq > whc->max_drop_seq) + whc->max_drop_seq = max_drop_seq; + *deferred_free_list = NULL; + return 0; + } + + /* If simple, we have always dropped everything up to whc->max_drop_seq, + and there can only be a single interval */ +#ifndef NDEBUG + whcn = find_nextseq_intv (&intv, whc, whc->max_drop_seq); + assert (whcn == NULL || whcn->prev_seq == NULL); + assert (ut_avlIsSingleton (&whc->seq)); +#endif + intv = whc->open_intv; + + /* Drop everything up to and including max_drop_seq, or absent that one, + the highest available sequence number (which then must be less) */ + if ((whcn = whc_findseq (whc, max_drop_seq)) == NULL) + { + whcn = whc->maxseq_node; + assert (whcn->seq < max_drop_seq); + } + + *deferred_free_list = intv->first; + ndropped = (unsigned) (whcn->seq - intv->min + 1); + + intv->first = whcn->next_seq; + intv->min = max_drop_seq + 1; + if (whcn->next_seq == NULL) + { + whc->maxseq_node = NULL; + intv->maxp1 = intv->min; + } + else + { + assert (whcn->next_seq->seq == max_drop_seq + 1); + whcn->next_seq->prev_seq = NULL; + } + whcn->next_seq = NULL; + + assert (whcn->total_bytes - (*deferred_free_list)->total_bytes + (*deferred_free_list)->size <= whc->unacked_bytes); + whc->unacked_bytes -= (size_t) (whcn->total_bytes - (*deferred_free_list)->total_bytes + (*deferred_free_list)->size); + for (whcn = *deferred_free_list; whcn; whcn = whcn->next_seq) + { + remove_whcn_from_hash (whc, whcn); + assert (whcn->unacked); + } + + assert (ndropped <= whc->seq_size); + whc->seq_size -= ndropped; + whc->max_drop_seq = max_drop_seq; + return ndropped; +} + +static unsigned whc_remove_acked_messages_full (struct whc *whc, seqno_t max_drop_seq, struct whc_node **deferred_free_list) +{ + struct whc_intvnode *intv; + struct whc_node *whcn; + struct whc_node *prev_seq; + struct whc_node deferred_list_head, *last_to_free = &deferred_list_head; + unsigned ndropped = 0; + + if (whc->is_transient_local && whc->tldepth == 0) + { + /* KEEP_ALL on transient local, so we can never ever delete anything */ + TRACE_WHC((" KEEP_ALL transient-local: do nothing\n")); + *deferred_free_list = NULL; + return 0; + } + + whcn = find_nextseq_intv (&intv, whc, whc->max_drop_seq); + deferred_list_head.next_seq = NULL; + prev_seq = whcn ? whcn->prev_seq : NULL; + while (whcn && whcn->seq <= max_drop_seq) + { + TRACE_WHC((" whcn %p %"PRId64, (void *) whcn, whcn->seq)); + if (whcn_in_tlidx(whc, whcn->idxnode, whcn->idxnode_pos)) + { + /* quickly skip over samples in tlidx */ + TRACE_WHC((" tl:keep")); + if (whcn->unacked) + { + assert (whc->unacked_bytes >= whcn->size); + whc->unacked_bytes -= whcn->size; + whcn->unacked = 0; + } + + if (whcn == intv->last) + intv = ut_avlFindSucc (&whc_seq_treedef, &whc->seq, intv); + if (prev_seq) + prev_seq->next_seq = whcn; + whcn->prev_seq = prev_seq; + prev_seq = whcn; + whcn = whcn->next_seq; + } + else + { + TRACE_WHC((" delete")); + last_to_free->next_seq = whcn; + last_to_free = last_to_free->next_seq; + whc_delete_one_intv (whc, &intv, &whcn); + ndropped++; + } + TRACE_WHC(("\n")); + } + if (prev_seq) + prev_seq->next_seq = whcn; + if (whcn) + whcn->prev_seq = prev_seq; + last_to_free->next_seq = NULL; + *deferred_free_list = deferred_list_head.next_seq; + + /* If the history is deeper than durability_service.history (but not KEEP_ALL), then there + may be old samples in this instance, samples that were retained because they were within + the T-L history but that are not anymore. Writing new samples will eventually push these + out, but if the difference is large and the update rate low, it may take a long time. + Thus, we had better prune them. */ + if (whc->tldepth > 0 && whc->idxdepth > whc->tldepth) + { + assert(whc->hdepth == whc->idxdepth); + TRACE_WHC((" idxdepth %u > tldepth %u > 0 -- must prune\n", whc->idxdepth, whc->tldepth)); + + /* Do a second pass over the sequence number range we just processed: this time we only + encounter samples that were retained because of the transient-local durability setting + (the rest has been dropped already) and we prune old samples in the instance */ + whcn = find_nextseq_intv (&intv, whc, whc->max_drop_seq); + while (whcn && whcn->seq <= max_drop_seq) + { + struct whc_idxnode * const idxn = whcn->idxnode; + unsigned cnt, idx; + + TRACE_WHC((" whcn %p %"PRId64" idxn %p prune_seq %"PRId64":", (void *)whcn, whcn->seq, (void *)idxn, idxn->prune_seq)); + + assert(whcn_in_tlidx(whc, idxn, whcn->idxnode_pos)); + assert (idxn->prune_seq <= max_drop_seq); + + if (idxn->prune_seq == max_drop_seq) + { + TRACE_WHC((" already pruned\n")); + whcn = whcn->next_seq; + continue; + } + idxn->prune_seq = max_drop_seq; + + idx = idxn->headidx; + cnt = whc->idxdepth - whc->tldepth; + while (cnt--) + { + struct whc_node *oldn; + if (++idx == whc->idxdepth) + idx = 0; + if ((oldn = idxn->hist[idx]) != NULL) + { + /* Delete it - but this may not result in deleting the index node as + there must still be a more recent one available */ +#ifndef NDEBUG + struct whc_node whcn_template; + union { + struct whc_idxnode idxn; + char pad[sizeof(struct whc_idxnode) + sizeof(struct whc_node *)]; + } template; + template.idxn.headidx = 0; + template.idxn.hist[0] = &whcn_template; + whcn_template.serdata = ddsi_serdata_ref(oldn->serdata); + assert(oldn->seq < whcn->seq); +#endif + TRACE_WHC((" del %p %"PRId64, (void *) oldn, oldn->seq)); + whc_delete_one (whc, oldn); +#ifndef NDEBUG + assert(ut_hhLookup(whc->idx_hash, &template) == idxn); + ddsi_serdata_unref(whcn_template.serdata); +#endif + } + } + TRACE_WHC(("\n")); + whcn = whcn->next_seq; + } + } + + assert (ndropped <= whc->seq_size); + whc->seq_size -= ndropped; + + /* lazy people do it this way: */ + whc->maxseq_node = whc_findmax_procedurally (whc); + whc->max_drop_seq = max_drop_seq; + return ndropped; +} + +unsigned whc_remove_acked_messages (struct whc *whc, seqno_t max_drop_seq, struct whc_node **deferred_free_list) +{ + assert (max_drop_seq < MAX_SEQ_NUMBER); + assert (max_drop_seq >= whc->max_drop_seq); + + TRACE_WHC(("whc_remove_acked_messages(%p max_drop_seq %"PRId64")\n", (void *)whc, max_drop_seq)); + TRACE_WHC((" whc: [%"PRId64",%"PRId64"] max_drop_seq %"PRId64" h %u tl %u\n", + whc_empty(whc) ? (seqno_t)-1 : whc_min_seq(whc), + whc_empty(whc) ? (seqno_t)-1 : whc_max_seq(whc), + whc->max_drop_seq, whc->hdepth, whc->tldepth)); + + check_whc (whc); + + if (whc->idxdepth == 0) + { + return whc_remove_acked_messages_noidx (whc, max_drop_seq, deferred_free_list); + } + else + { + return whc_remove_acked_messages_full (whc, max_drop_seq, deferred_free_list); + } +} + +struct whc_node *whc_findkey (const struct whc *whc, const struct serdata *serdata_key) +{ + union { + struct whc_idxnode idxn; + char pad[sizeof(struct whc_idxnode) + sizeof(struct whc_node *)]; + } template; + struct whc_idxnode *n; + check_whc (whc); + template.idxn.iid = dds_tkmap_lookup(gv.m_tkmap, serdata_key); + n = ut_hhLookup (whc->idx_hash, &template.idxn); + if (n == NULL) + return NULL; + else + { + assert (n->hist[n->headidx]); + return n->hist[n->headidx]; + } +} + +static struct whc_node *whc_insert_seq (struct whc *whc, seqno_t max_drop_seq, seqno_t seq, struct nn_plist *plist, serdata_t serdata) +{ + struct whc_node *newn = NULL; + + if ((newn = nn_freelist_pop (&whc->freelist)) == NULL) + newn = os_malloc (sizeof (*newn)); + newn->seq = seq; + newn->plist = plist; + newn->unacked = (seq > max_drop_seq); + newn->idxnode = NULL; /* initial state, may be changed */ + newn->idxnode_pos = 0; + newn->last_rexmit_ts.v = 0; + newn->rexmit_count = 0; + newn->serdata = ddsi_serdata_ref (serdata); + newn->next_seq = NULL; + newn->prev_seq = whc->maxseq_node; + if (newn->prev_seq) + newn->prev_seq->next_seq = newn; + whc->maxseq_node = newn; + + newn->size = whcn_size (whc, newn); + whc->total_bytes += newn->size; + newn->total_bytes = whc->total_bytes; + if (newn->unacked) + whc->unacked_bytes += newn->size; + + insert_whcn_in_hash (whc, newn); + + if (whc->open_intv->first == NULL) + { + /* open_intv is empty => reset open_intv */ + whc->open_intv->min = seq; + whc->open_intv->maxp1 = seq + 1; + whc->open_intv->first = whc->open_intv->last = newn; + } + else if (whc->open_intv->maxp1 == seq) + { + /* no gap => append to open_intv */ + whc->open_intv->last = newn; + whc->open_intv->maxp1++; + } + else + { + /* gap => need new open_intv */ + struct whc_intvnode *intv1; + ut_avlIPath_t path; + intv1 = os_malloc (sizeof (*intv1)); + intv1->min = seq; + intv1->maxp1 = seq + 1; + intv1->first = intv1->last = newn; + if (ut_avlLookupIPath (&whc_seq_treedef, &whc->seq, &seq, &path) != NULL) + assert (0); + ut_avlInsertIPath (&whc_seq_treedef, &whc->seq, intv1, &path); + whc->open_intv = intv1; + } + + whc->seq_size++; + return newn; +} + +int whc_insert (struct whc *whc, seqno_t max_drop_seq, seqno_t seq, struct nn_plist *plist, serdata_t serdata, struct tkmap_instance *tk) +{ + struct whc_node *newn = NULL; + struct whc_idxnode *idxn; + union { + struct whc_idxnode idxn; + char pad[sizeof(struct whc_idxnode) + sizeof(struct whc_node *)]; + } template; + check_whc (whc); + + TRACE_WHC(("whc_insert(%p max_drop_seq %"PRId64" seq %"PRId64" plist %p serdata %p:%x)\n", (void *)whc, max_drop_seq, seq, (void*)plist, (void*)serdata, *(unsigned *)serdata->v.keyhash.m_hash)); + TRACE_WHC((" whc: [%"PRId64",%"PRId64"] max_drop_seq %"PRId64" h %u tl %u\n", + whc_empty(whc) ? (seqno_t)-1 : whc_min_seq(whc), + whc_empty(whc) ? (seqno_t)-1 : whc_max_seq(whc), + whc->max_drop_seq, whc->hdepth, whc->tldepth)); + + assert (max_drop_seq < MAX_SEQ_NUMBER); + assert (max_drop_seq >= whc->max_drop_seq); + + /* Seq must be greater than what is currently stored. Usually it'll + be the next sequence number, but if there are no readers + temporarily, a gap may be among the possibilities */ + assert (whc_empty (whc) || seq > whc_max_seq (whc)); + + /* Always insert in seq admin */ + newn = whc_insert_seq (whc, max_drop_seq, seq, plist, serdata); + + TRACE_WHC((" whcn %p:", (void*)newn)); + + /* Special case of empty data (such as commit messages) can't go into index, and if we're not maintaining an index, we're done, too */ + if (ddsi_serdata_is_empty(serdata) || whc->idxdepth == 0) + { + TRACE_WHC((" empty or no hist\n")); + return 0; + } + + template.idxn.iid = tk->m_iid; + if ((idxn = ut_hhLookup (whc->idx_hash, &template)) != NULL) + { + /* Unregisters cause deleting of index entry, non-unregister of adding/overwriting in history */ + TRACE_WHC((" idxn %p", (void *)idxn)); + if (serdata->v.msginfo.statusinfo & NN_STATUSINFO_UNREGISTER) + { + TRACE_WHC((" unreg:delete\n")); + delete_one_instance_from_idx (whc, max_drop_seq, idxn); + if (newn->seq <= max_drop_seq) + { + struct whc_node *prev_seq = newn->prev_seq; + TRACE_WHC((" unreg:seq <= max_drop_seq: delete newn\n")); + whc_delete_one (whc, newn); + whc->maxseq_node = prev_seq; + } + } + else + { + struct whc_node *oldn; + if (++idxn->headidx == whc->idxdepth) + idxn->headidx = 0; + if ((oldn = idxn->hist[idxn->headidx]) != NULL) + { + TRACE_WHC((" overwrite whcn %p", (void *)oldn)); + oldn->idxnode = NULL; + } + idxn->hist[idxn->headidx] = newn; + newn->idxnode = idxn; + newn->idxnode_pos = idxn->headidx; + + if (oldn && (whc->hdepth > 0 || oldn->seq <= max_drop_seq)) + { + TRACE_WHC((" prune whcn %p", (void *)oldn)); + assert(oldn != whc->maxseq_node); + whc_delete_one (whc, oldn); + } + + /* Special case for dropping everything beyond T-L history when the new sample is being + auto-acknowledged (for lack of reliable readers), and the keep-last T-L history is + shallower than the keep-last regular history (normal path handles this via pruning in + whc_remove_acked_messages, but that never happens when there are no readers). */ + if (seq <= max_drop_seq && whc->tldepth > 0 && whc->idxdepth > whc->tldepth) + { + unsigned pos = idxn->headidx + whc->idxdepth - whc->tldepth; + if (pos >= whc->idxdepth) + pos -= whc->idxdepth; + if ((oldn = idxn->hist[pos]) != NULL) + { + TRACE_WHC((" prune tl whcn %p", (void *)oldn)); + assert(oldn != whc->maxseq_node); + whc_delete_one (whc, oldn); + } + } + TRACE_WHC(("\n")); + } + } + else + { + TRACE_WHC((" newkey")); + /* Ignore unregisters, but insert everything else */ + if (!(serdata->v.msginfo.statusinfo & NN_STATUSINFO_UNREGISTER)) + { + unsigned i; + idxn = os_malloc (sizeof (*idxn) + whc->idxdepth * sizeof (idxn->hist[0])); + TRACE_WHC((" idxn %p", (void *)idxn)); + dds_tkmap_instance_ref(tk); + idxn->iid = tk->m_iid; + idxn->tk = tk; + idxn->prune_seq = 0; + idxn->headidx = 0; + idxn->hist[0] = newn; + for (i = 1; i < whc->idxdepth; i++) + idxn->hist[i] = NULL; + newn->idxnode = idxn; + newn->idxnode_pos = 0; + if (!ut_hhAdd (whc->idx_hash, idxn)) + assert (0); + } + else + { + TRACE_WHC((" unreg:skip")); + if (newn->seq <= max_drop_seq) + { + struct whc_node *prev_seq = newn->prev_seq; + TRACE_WHC((" unreg:seq <= max_drop_seq: delete newn\n")); + whc_delete_one (whc, newn); + whc->maxseq_node = prev_seq; + } + } + TRACE_WHC(("\n")); + } + return 0; +} + +size_t whc_unacked_bytes (struct whc *whc) +{ + return whc->unacked_bytes; +} diff --git a/src/core/ddsi/src/q_xevent.c b/src/core/ddsi/src/q_xevent.c new file mode 100644 index 0000000..0cdb756 --- /dev/null +++ b/src/core/ddsi/src/q_xevent.c @@ -0,0 +1,1594 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include + +#include "os/os.h" + +#include "util/ut_avl.h" +#include "util/ut_fibheap.h" + +#include "ddsi/q_time.h" +#include "ddsi/q_log.h" +#include "ddsi/q_addrset.h" +#include "ddsi/q_xmsg.h" +#include "ddsi/q_whc.h" +#include "ddsi/q_xevent.h" +#include "ddsi/q_thread.h" +#include "ddsi/q_config.h" +#include "ddsi/q_unused.h" +#include "ddsi/q_globals.h" +#include "ddsi/q_ephash.h" +#include "ddsi/q_transmit.h" +#include "ddsi/q_error.h" +#include "ddsi/q_bswap.h" +#include "ddsi/q_entity.h" +#include "ddsi/q_misc.h" +#include "ddsi/q_radmin.h" +#include "ddsi/q_bitset.h" +#include "ddsi/q_lease.h" +#include "ddsi/q_xmsg.h" +#include "q__osplser.h" +#include "ddsi/ddsi_ser.h" + +#include "ddsi/sysdeps.h" + +/* This is absolute bottom for signed integers, where -x = x and yet x + != 0 -- and note that it had better be 2's complement machine! */ +#define TSCHED_DELETE ((int64_t) ((uint64_t) 1 << 63)) + +#if __STDC_VERSION__ >= 199901L +#define POS_INFINITY_DOUBLE INFINITY +#else +/* Hope for the best -- the only consequence of getting this wrong is + that T_NEVER may be printed as a fugly value instead of as +inf. */ +#define POS_INFINITY_DOUBLE (HUGE_VAL + HUGE_VAL) +#endif + +enum xeventkind +{ + XEVK_HEARTBEAT, + XEVK_ACKNACK, + XEVK_SPDP, + XEVK_PMD_UPDATE, + XEVK_END_STARTUP_MODE, + XEVK_DELETE_WRITER, + XEVK_CALLBACK +}; + +struct xevent +{ + ut_fibheapNode_t heapnode; + struct xeventq *evq; + nn_mtime_t tsched; + enum xeventkind kind; + union { + struct { + nn_guid_t wr_guid; + } heartbeat; + struct { + nn_guid_t pwr_guid; + nn_guid_t rd_guid; + } acknack; + struct { + nn_guid_t pp_guid; + nn_guid_prefix_t dest_proxypp_guid_prefix; /* only if "directed" */ + int directed; + } spdp; + struct { + nn_guid_t pp_guid; + } pmd_update; +#if 0 + struct { + } info; +#endif +#if 0 + struct { + } end_startup_mode; +#endif + struct { + nn_guid_t guid; + } delete_writer; + struct { + void (*cb) (struct xevent *ev, void *arg, nn_mtime_t tnow); + void *arg; + } callback; + } u; +}; + +enum xeventkind_nt +{ + XEVK_MSG, + XEVK_MSG_REXMIT, + XEVK_ENTITYID +}; + +struct untimed_listelem { + struct xevent_nt *next; +}; + +struct xevent_nt +{ + struct untimed_listelem listnode; + struct xeventq *evq; + enum xeventkind_nt kind; + union { + struct { + /* xmsg is self-contained / relies on reference counts */ + struct nn_xmsg *msg; + } msg; + struct { + /* xmsg is self-contained / relies on reference counts */ + struct nn_xmsg *msg; + size_t queued_rexmit_bytes; + ut_avlNode_t msg_avlnode; + } msg_rexmit; + struct { + /* xmsg is self-contained / relies on reference counts */ + struct nn_xmsg *msg; + } entityid; + } u; +}; + +struct xeventq { + ut_fibheap_t xevents; + ut_avlTree_t msg_xevents; + struct xevent_nt *non_timed_xmit_list_oldest; + struct xevent_nt *non_timed_xmit_list_newest; /* undefined if ..._oldest == NULL */ + size_t queued_rexmit_bytes; + size_t queued_rexmit_msgs; + size_t max_queued_rexmit_bytes; + size_t max_queued_rexmit_msgs; + int terminate; + struct thread_state1 *ts; + os_mutex lock; + os_cond cond; + ddsi_tran_conn_t tev_conn; + uint32_t auxiliary_bandwidth_limit; +}; + +static uint32_t xevent_thread (struct xeventq *xevq); +static nn_mtime_t earliest_in_xeventq (struct xeventq *evq); +static int msg_xevents_cmp (const void *a, const void *b); +static int compare_xevent_tsched (const void *va, const void *vb); + +static const ut_avlTreedef_t msg_xevents_treedef = UT_AVL_TREEDEF_INITIALIZER_INDKEY (offsetof (struct xevent_nt, u.msg_rexmit.msg_avlnode), offsetof (struct xevent_nt, u.msg_rexmit.msg), msg_xevents_cmp, 0); + +static const ut_fibheapDef_t evq_xevents_fhdef = UT_FIBHEAPDEF_INITIALIZER(offsetof (struct xevent, heapnode), compare_xevent_tsched); + +static int compare_xevent_tsched (const void *va, const void *vb) +{ + const struct xevent *a = va; + const struct xevent *b = vb; + return (a->tsched.v == b->tsched.v) ? 0 : (a->tsched.v < b->tsched.v) ? -1 : 1; +} + +static void update_rexmit_counts (struct xeventq *evq, struct xevent_nt *ev) +{ +#if 0 + TRACE (("ZZZ(%p,%"PA_PRIuSIZE")", (void *) ev, ev->u.msg_rexmit.queued_rexmit_bytes)); +#endif + assert (ev->kind == XEVK_MSG_REXMIT); + assert (ev->u.msg_rexmit.queued_rexmit_bytes <= evq->queued_rexmit_bytes); + assert (evq->queued_rexmit_msgs > 0); + evq->queued_rexmit_bytes -= ev->u.msg_rexmit.queued_rexmit_bytes; + evq->queued_rexmit_msgs--; +} + +#if 0 +static void trace_msg (const char *func, const struct nn_xmsg *m) +{ + if (config.enabled_logcats & LC_TRACE) + { + nn_guid_t wrguid; + seqno_t wrseq; + nn_fragment_number_t wrfragid; + nn_xmsg_guid_seq_fragid (m, &wrguid, &wrseq, &wrfragid); + TRACE ((" %s(%x:%x:%x:%x/%"PRId64"/%u)", func, PGUID (wrguid), wrseq, wrfragid)); + } +} +#else +static void trace_msg (UNUSED_ARG (const char *func), UNUSED_ARG (const struct nn_xmsg *m)) +{ +} +#endif + +static struct xevent_nt *lookup_msg (struct xeventq *evq, struct nn_xmsg *msg) +{ + assert (nn_xmsg_kind (msg) == NN_XMSG_KIND_DATA_REXMIT); + trace_msg ("lookup-msg", msg); + return ut_avlLookup (&msg_xevents_treedef, &evq->msg_xevents, msg); +} + +static void remember_msg (struct xeventq *evq, struct xevent_nt *ev) +{ + assert (ev->kind == XEVK_MSG_REXMIT); + trace_msg ("remember-msg", ev->u.msg_rexmit.msg); + ut_avlInsert (&msg_xevents_treedef, &evq->msg_xevents, ev); +} + +static void forget_msg (struct xeventq *evq, struct xevent_nt *ev) +{ + assert (ev->kind == XEVK_MSG_REXMIT); + trace_msg ("forget-msg", ev->u.msg_rexmit.msg); + ut_avlDelete (&msg_xevents_treedef, &evq->msg_xevents, ev); +} + +static void add_to_non_timed_xmit_list (struct xeventq *evq, struct xevent_nt *ev) +{ + ev->listnode.next = NULL; + if (evq->non_timed_xmit_list_oldest == NULL) { + /* list is currently empty so add the first item (at the front) */ + evq->non_timed_xmit_list_oldest = ev; + } else { + evq->non_timed_xmit_list_newest->listnode.next = ev; + } + evq->non_timed_xmit_list_newest = ev; + + if (ev->kind == XEVK_MSG_REXMIT) + remember_msg (evq, ev); + + os_condSignal (&evq->cond); +} + +static struct xevent_nt *getnext_from_non_timed_xmit_list (struct xeventq *evq) +{ + /* function removes and returns the first item in the list + (from the front) and frees the container */ + struct xevent_nt *ev = evq->non_timed_xmit_list_oldest; + if (ev != NULL) + { + evq->non_timed_xmit_list_oldest = ev->listnode.next; + + if (ev->kind == XEVK_MSG_REXMIT) + { + assert (lookup_msg (evq, ev->u.msg_rexmit.msg) == ev); + forget_msg (evq, ev); + } + } + return ev; +} + +static int non_timed_xmit_list_is_empty (struct xeventq *evq) +{ + /* check whether the "non-timed" xevent list is empty */ + return (evq->non_timed_xmit_list_oldest == NULL); +} + +static int compute_non_timed_xmit_list_size (struct xeventq *evq) +{ + /* returns how many "non-timed" xevents are pending by counting the + number of events in the list -- it'd be easy to compute the + length incrementally in the add_... and next_... functions, but + it isn't really being used anywhere, so why bother? */ + struct xevent_nt *current = evq->non_timed_xmit_list_oldest; + int i = 0; + while (current) + { + current = current->listnode.next; + i++; + } + return i; +} + +#ifndef NDEBUG +static int nontimed_xevent_in_queue (struct xeventq *evq, struct xevent_nt *ev) +{ + struct xevent_nt *x; + os_mutexLock (&evq->lock); + for (x = evq->non_timed_xmit_list_oldest; x; x = x->listnode.next) + { + if (x == ev) + { + os_mutexUnlock (&evq->lock); + return 1; + } + } + os_mutexUnlock (&evq->lock); + return 0; +} +#endif + +static void free_xevent (struct xeventq *evq, struct xevent *ev) +{ + (void) evq; + if (ev->tsched.v != TSCHED_DELETE) + { + switch (ev->kind) + { + case XEVK_HEARTBEAT: + case XEVK_ACKNACK: + case XEVK_SPDP: + case XEVK_PMD_UPDATE: + case XEVK_END_STARTUP_MODE: + case XEVK_DELETE_WRITER: + case XEVK_CALLBACK: + break; + } + } + os_free (ev); +} + +static void free_xevent_nt (struct xeventq *evq, struct xevent_nt *ev) +{ + assert (!nontimed_xevent_in_queue (evq, ev)); + switch (ev->kind) + { + case XEVK_MSG: + nn_xmsg_free (ev->u.msg.msg); + break; + case XEVK_MSG_REXMIT: + assert (ut_avlLookup (&msg_xevents_treedef, &evq->msg_xevents, ev->u.msg_rexmit.msg) == NULL); + update_rexmit_counts (evq, ev); + nn_xmsg_free (ev->u.msg_rexmit.msg); + break; + case XEVK_ENTITYID: + nn_xmsg_free (ev->u.entityid.msg); + break; + } + os_free (ev); +} + +void delete_xevent (struct xevent *ev) +{ + struct xeventq *evq = ev->evq; + os_mutexLock (&evq->lock); + /* Can delete it only once, no matter how we implement it internally */ + assert (ev->tsched.v != TSCHED_DELETE); + assert (TSCHED_DELETE < ev->tsched.v); + if (ev->tsched.v != T_NEVER) + { + ev->tsched.v = TSCHED_DELETE; + ut_fibheapDecreaseKey (&evq_xevents_fhdef, &evq->xevents, ev); + } + else + { + ev->tsched.v = TSCHED_DELETE; + ut_fibheapInsert (&evq_xevents_fhdef, &evq->xevents, ev); + } + /* TSCHED_DELETE is absolute minimum time, so chances are we need to + wake up the thread. The superfluous signal is harmless. */ + os_condSignal (&evq->cond); + os_mutexUnlock (&evq->lock); +} + +int resched_xevent_if_earlier (struct xevent *ev, nn_mtime_t tsched) +{ + struct xeventq *evq = ev->evq; + int is_resched; + os_mutexLock (&evq->lock); + assert (tsched.v != TSCHED_DELETE); + /* If you want to delete it, you to say so by calling the right + function. Don't want to reschedule an event marked for deletion, + but with TSCHED_DELETE = MIN_INT64, tsched >= ev->tsched is + guaranteed to be false. */ + assert (tsched.v > TSCHED_DELETE); + if (tsched.v >= ev->tsched.v) + is_resched = 0; + else + { + nn_mtime_t tbefore = earliest_in_xeventq (evq); + assert (tsched.v != T_NEVER); + if (ev->tsched.v != T_NEVER) + { + ev->tsched = tsched; + ut_fibheapDecreaseKey (&evq_xevents_fhdef, &evq->xevents, ev); + } + else + { + ev->tsched = tsched; + ut_fibheapInsert (&evq_xevents_fhdef, &evq->xevents, ev); + } + is_resched = 1; + if (tsched.v < tbefore.v) + os_condSignal (&evq->cond); + } + os_mutexUnlock (&evq->lock); + return is_resched; +} + +static struct xevent * qxev_common (struct xeventq *evq, nn_mtime_t tsched, enum xeventkind kind) +{ + /* qxev_common is the route by which all timed xevents are + created. */ + struct xevent *ev = os_malloc (sizeof (*ev)); + + assert (tsched.v != TSCHED_DELETE); + ASSERT_MUTEX_HELD (&evq->lock); + + /* round up the scheduled time if required */ + if (tsched.v != T_NEVER && config.schedule_time_rounding != 0) + { + nn_mtime_t tsched_rounded = mtime_round_up (tsched, config.schedule_time_rounding); + TRACE (("rounded event scheduled for %"PRId64" to %"PRId64"\n", tsched.v, tsched_rounded.v)); + tsched = tsched_rounded; + } + + ev->evq = evq; + ev->tsched = tsched; + ev->kind = kind; + return ev; +} + +static struct xevent_nt *qxev_common_nt (struct xeventq *evq, enum xeventkind_nt kind) +{ + /* qxev_common_nt is the route by which all non-timed xevents are created. */ + struct xevent_nt *ev = os_malloc (sizeof (*ev)); + ev->evq = evq; + ev->kind = kind; + return ev; +} + +static nn_mtime_t earliest_in_xeventq (struct xeventq *evq) +{ + struct xevent *min; + ASSERT_MUTEX_HELD (&evq->lock); + if ((min = ut_fibheapMin (&evq_xevents_fhdef, &evq->xevents)) != NULL) + return min->tsched; + else + { + nn_mtime_t r = { T_NEVER }; + return r; + } +} + +static void qxev_insert (struct xevent *ev) +{ + /* qxev_insert is how all timed xevents are registered into the + event administration. */ + struct xeventq *evq = ev->evq; + ASSERT_MUTEX_HELD (&evq->lock); + if (ev->tsched.v != T_NEVER) + { + nn_mtime_t tbefore = earliest_in_xeventq (evq); + ut_fibheapInsert (&evq_xevents_fhdef, &evq->xevents, ev); + if (ev->tsched.v < tbefore.v) + os_condSignal (&evq->cond); + } +} + +static void qxev_insert_nt (struct xevent_nt *ev) +{ + /* qxev_insert is how all non-timed xevents are queued. */ + struct xeventq *evq = ev->evq; + ASSERT_MUTEX_HELD (&evq->lock); + add_to_non_timed_xmit_list (evq, ev); + TRACE (("non-timed queue now has %d items\n", compute_non_timed_xmit_list_size (evq))); +} + +static int msg_xevents_cmp (const void *a, const void *b) +{ + return nn_xmsg_compare_fragid (a, b); +} + +struct xeventq * xeventq_new +( + ddsi_tran_conn_t conn, + size_t max_queued_rexmit_bytes, + size_t max_queued_rexmit_msgs, + uint32_t auxiliary_bandwidth_limit +) +{ + struct xeventq *evq = os_malloc (sizeof (*evq)); + /* limit to 2GB to prevent overflow (4GB - 64kB should be ok, too) */ + if (max_queued_rexmit_bytes > 2147483648u) + max_queued_rexmit_bytes = 2147483648u; + ut_fibheapInit (&evq_xevents_fhdef, &evq->xevents); + ut_avlInit (&msg_xevents_treedef, &evq->msg_xevents); + evq->non_timed_xmit_list_oldest = NULL; + evq->non_timed_xmit_list_newest = NULL; + evq->terminate = 0; + evq->ts = NULL; + evq->max_queued_rexmit_bytes = max_queued_rexmit_bytes; + evq->max_queued_rexmit_msgs = max_queued_rexmit_msgs; + evq->auxiliary_bandwidth_limit = auxiliary_bandwidth_limit; + evq->queued_rexmit_bytes = 0; + evq->queued_rexmit_msgs = 0; + evq->tev_conn = conn; + os_mutexInit (&evq->lock); + os_condInit (&evq->cond, &evq->lock); + return evq; +} + +int xeventq_start (struct xeventq *evq, const char *name) +{ + char * evqname = "tev"; + assert (evq->ts == NULL); + + if (name) + { + size_t slen = strlen (name) + 5; + evqname = os_malloc (slen); + (void) snprintf (evqname, slen, "tev.%s", name); + } + + evq->terminate = 0; + evq->ts = create_thread (evqname, (uint32_t (*) (void *)) xevent_thread, evq); + + if (name) + { + os_free (evqname); + } + return (evq->ts == NULL) ? ERR_UNSPECIFIED : 0; +} + +void xeventq_stop (struct xeventq *evq) +{ + assert (evq->ts != NULL); + os_mutexLock (&evq->lock); + evq->terminate = 1; + os_condSignal (&evq->cond); + os_mutexUnlock (&evq->lock); + join_thread (evq->ts); + evq->ts = NULL; +} + +void xeventq_free (struct xeventq *evq) +{ + struct xevent *ev; + assert (evq->ts == NULL); + while ((ev = ut_fibheapExtractMin (&evq_xevents_fhdef, &evq->xevents)) != NULL) + { + if (ev->tsched.v == TSCHED_DELETE || ev->kind != XEVK_CALLBACK) + free_xevent (evq, ev); + else + { + ev->tsched.v = T_NEVER; + ev->u.callback.cb (ev, ev->u.callback.arg, ev->tsched); + if (ev->tsched.v != TSCHED_DELETE) + { + union { void *v; void (*f) (struct xevent *ev, void *arg, nn_mtime_t tnow); } fp; + fp.f = ev->u.callback.cb; + NN_WARNING("xeventq_free: callback %p did not schedule deletion as required, deleting event anyway\n", fp.v); + delete_xevent (ev); + } + } + } + while (!non_timed_xmit_list_is_empty(evq)) + free_xevent_nt (evq, getnext_from_non_timed_xmit_list (evq)); + assert (ut_avlIsEmpty (&evq->msg_xevents)); + os_condDestroy (&evq->cond); + os_mutexDestroy (&evq->lock); + os_free (evq); +} + +/* EVENT QUEUE EVENT HANDLERS ******************************************************/ + +static void handle_xevk_msg (struct nn_xpack *xp, struct xevent_nt *ev) +{ + assert (!nontimed_xevent_in_queue (ev->evq, ev)); + nn_xpack_addmsg (xp, ev->u.msg.msg, 0); +} + +static void handle_xevk_msg_rexmit (struct nn_xpack *xp, struct xevent_nt *ev) +{ + struct xeventq *evq = ev->evq; + + assert (!nontimed_xevent_in_queue (ev->evq, ev)); + + nn_xpack_addmsg (xp, ev->u.msg_rexmit.msg, 0); + + /* FIXME: less than happy about having to relock the queue for a + little while here */ + os_mutexLock (&evq->lock); + update_rexmit_counts (evq, ev); + os_mutexUnlock (&evq->lock); +} + +static void handle_xevk_entityid (struct nn_xpack *xp, struct xevent_nt *ev) +{ + assert (!nontimed_xevent_in_queue (ev->evq, ev)); + nn_xpack_addmsg (xp, ev->u.entityid.msg, 0); +} + +static void handle_xevk_heartbeat (struct nn_xpack *xp, struct xevent *ev, nn_mtime_t tnow /* monotonic */) +{ + struct nn_xmsg *msg; + struct writer *wr; + nn_mtime_t t_next; + int hbansreq = 0; + + if ((wr = ephash_lookup_writer_guid (&ev->u.heartbeat.wr_guid)) == NULL) + { + TRACE (("heartbeat(wr %x:%x:%x:%x) writer gone\n", + PGUID (ev->u.heartbeat.wr_guid))); + return; + } + + assert (wr->reliable); + os_mutexLock (&wr->e.lock); + if (!writer_must_have_hb_scheduled (wr)) + { + hbansreq = 1; /* just for trace */ + msg = NULL; /* Need not send it now, and no need to schedule it for the future */ + t_next.v = T_NEVER; + } + else if (!writer_hbcontrol_must_send (wr, tnow)) + { + hbansreq = 1; /* just for trace */ + msg = NULL; + t_next.v = tnow.v + writer_hbcontrol_intv (wr, tnow); + } + else + { + hbansreq = writer_hbcontrol_ack_required (wr, tnow); + msg = writer_hbcontrol_create_heartbeat (wr, tnow, hbansreq, 0); + t_next.v = tnow.v + writer_hbcontrol_intv (wr, tnow); + } + + TRACE (("heartbeat(wr %x:%x:%x:%x%s) %s, resched in %g s (min-ack %"PRId64"%s, avail-seq %"PRId64", xmit %"PRId64")\n", + PGUID (wr->e.guid), + hbansreq ? "" : " final", + msg ? "sent" : "suppressed", + (t_next.v == T_NEVER) ? POS_INFINITY_DOUBLE : (double)(t_next.v - tnow.v) / 1e9, + ut_avlIsEmpty (&wr->readers) ? (seqno_t) -1 : ((struct wr_prd_match *) ut_avlRootNonEmpty (&wr_readers_treedef, &wr->readers))->min_seq, + ut_avlIsEmpty (&wr->readers) || ((struct wr_prd_match *) ut_avlRootNonEmpty (&wr_readers_treedef, &wr->readers))->all_have_replied_to_hb ? "" : "!", + whc_empty (wr->whc) ? (seqno_t) -1 : whc_max_seq (wr->whc), READ_SEQ_XMIT(wr))); + resched_xevent_if_earlier (ev, t_next); + wr->hbcontrol.tsched = t_next; + os_mutexUnlock (&wr->e.lock); + + /* Can't transmit synchronously with writer lock held: trying to add + the heartbeat to the xp may cause xp to be sent out, which may + require updating wr->seq_xmit for other messages already in xp. + Besides, nn_xpack_addmsg may sleep for bandwidth-limited channels + and we certainly don't want to hold the lock during that time. */ + if (msg) + { + nn_xpack_addmsg (xp, msg, 0); + } +} + +static seqno_t next_deliv_seq (const struct proxy_writer *pwr, const seqno_t next_seq) +{ + /* We want to determine next_deliv_seq, the next sequence number to + be delivered to all in-sync readers, so that we can acknowledge + what we have actually delivered. This is different from next_seq + tracks, which tracks the sequence number up to which all samples + have been received. The difference is the delivery queue. + + There is always but a single delivery queue, and hence delivery + thread, associated with a single proxy writer; but the ACKs are + always generated by another thread. Therefore, updates to + next_deliv_seq need only be atomic with respect to these reads. + On all supported platforms we can atomically load and store 32 + bits without issue, and so we store just the low word of the + sequence number. + + We know 1 <= next_deliv_seq AND next_seq - N <= next_deliv_seq <= + next_seq for N << 2**32. With n = next_seq, nd = next_deliv_seq, + H the upper half and L the lower half: + + - H(nd) <= H(n) <= H(nd)+1 { n >= nd AND N << 2*32} + - H(n) = H(nd) => L(n) >= L(nd) { n >= nd } + - H(n) = H(nd)+1 => L(n) < L(nd) { N << 2*32 } + + Therefore: + + L(n) < L(nd) <=> H(n) = H(nd+1) + + a.k.a.: + + nd = nd' - if nd' > nd then 2**32 else 0 + where nd' = 2**32 * H(n) + L(nd) + + By not locking next_deliv_seq, we may have nd a bit lower than it + could be, but that only means we are acknowledging slightly less + than we could; but that is perfectly acceptible. + + FIXME: next_seq - #dqueue could probably be used instead, + provided #dqueue is decremented after delivery, rather than + before delivery. */ + const uint32_t lw = os_atomic_ld32 (&pwr->next_deliv_seq_lowword); + seqno_t next_deliv_seq; + next_deliv_seq = (next_seq & ~(seqno_t) UINT32_MAX) | lw; + if (next_deliv_seq > next_seq) + next_deliv_seq -= ((seqno_t) 1) << 32; + assert (0 < next_deliv_seq && next_deliv_seq <= next_seq); + return next_deliv_seq; +} + +static void add_AckNack (struct nn_xmsg *msg, struct proxy_writer *pwr, struct pwr_rd_match *rwn, seqno_t *nack_seq) +{ + /* If pwr->have_seen_heartbeat == 0, no heartbeat has been received + by this proxy writer yet, so we'll be sending a pre-emptive + AckNack. NACKing data now will most likely cause another NACK + upon reception of the first heartbeat, and so cause the data to + be resent twice. */ + const unsigned max_numbits = 256; /* as spec'd */ + int notail = 0; /* all known missing ones are nack'd */ + struct nn_reorder *reorder; + AckNack_t *an; + struct nn_xmsg_marker sm_marker; + unsigned i, numbits; + seqno_t base; + unsigned ui; + + union { + struct nn_fragment_number_set set; + char pad[NN_FRAGMENT_NUMBER_SET_SIZE (256)]; + } nackfrag; + int nackfrag_numbits; + seqno_t nackfrag_seq = 0; + seqno_t bitmap_base; + + ASSERT_MUTEX_HELD (pwr->e.lock); + + /* if in sync, look at proxy writer status, else look at + proxy-writer--reader match status */ + if (rwn->in_sync != PRMSS_OUT_OF_SYNC) + { + reorder = pwr->reorder; + if (!config.late_ack_mode) + bitmap_base = nn_reorder_next_seq (reorder); + else + { + bitmap_base = next_deliv_seq (pwr, nn_reorder_next_seq (reorder)); + if (nn_dqueue_is_full (pwr->dqueue)) + notail = 1; + } + } + else + { + reorder = rwn->u.not_in_sync.reorder; + bitmap_base = nn_reorder_next_seq (reorder); + } + + an = nn_xmsg_append (msg, &sm_marker, ACKNACK_SIZE_MAX); + nn_xmsg_submsg_init (msg, sm_marker, SMID_ACKNACK); + an->readerId = nn_hton_entityid (rwn->rd_guid.entityid); + an->writerId = nn_hton_entityid (pwr->e.guid.entityid); + + /* Make bitmap; note that we've made sure to have room for the + maximum bitmap size. */ + numbits = nn_reorder_nackmap (reorder, bitmap_base, pwr->last_seq, &an->readerSNState, max_numbits, notail); + base = fromSN (an->readerSNState.bitmap_base); + + /* Scan through bitmap, cutting it off at the first missing sample + that the defragmenter knows about. Then note the sequence number + & add a NACKFRAG for that sample */ + nackfrag_numbits = -1; + for (i = 0; i < numbits && nackfrag_numbits < 0; i++) + { + uint32_t fragnum; + nackfrag_seq = base + i; + if (!nn_bitset_isset (numbits, an->readerSNState.bits, i)) + continue; + if (nackfrag_seq == pwr->last_seq) + fragnum = pwr->last_fragnum; + else + fragnum = UINT32_MAX; + nackfrag_numbits = nn_defrag_nackmap (pwr->defrag, nackfrag_seq, fragnum, &nackfrag.set, max_numbits); + } + if (nackfrag_numbits >= 0) { + /* Cut the NACK short, NACKFRAG will be added after the NACK's is + properly formatted */ + assert (i > 0); + an->readerSNState.numbits = numbits = i - 1; + } + + /* Let caller know whether it is a nack, and, in steady state, set + final to prevent a response if it isn't. The initial + (pre-emptive) acknack is different: it'd be nice to get a + heartbeat in response. + + Who cares about an answer to an acknowledgment!? -- actually, + that'd a very useful feature in combination with directed + heartbeats, or somesuch, to get reliability guarantees. */ + *nack_seq = (numbits > 0) ? base + numbits : 0; + if (!pwr->have_seen_heartbeat) { + /* We must have seen a heartbeat for us to consider setting FINAL */ + } else if (*nack_seq && base + numbits <= pwr->last_seq) { + /* If it's a NACK and it doesn't cover samples all the way up to + the highest known sequence number, there's some reason to expect + we may to do another round. For which we need a Heartbeat. + + Note: last_seq exists, base is first in bitmap, numbits is + length of bitmap, hence less-than-or-equal. */ + } else { + /* An ACK or we think we'll get everything now. */ + an->smhdr.flags |= ACKNACK_FLAG_FINAL; + } + + /* If we refuse to send invalid AckNacks, grow a length-0 bitmap and + zero-fill it. Cleared bits are meaningless (DDSI 2.1, table 8.33, + although RTI seems to think otherwise). */ + if (numbits == 0 && config.acknack_numbits_emptyset > 0) + { + an->readerSNState.numbits = (unsigned) config.acknack_numbits_emptyset; + nn_bitset_zero (an->readerSNState.numbits, an->readerSNState.bits); + } + + { + /* Count field is at a variable offset ... silly DDSI spec. */ + nn_count_t *countp = + (nn_count_t *) ((char *) an + offsetof (AckNack_t, readerSNState) + + NN_SEQUENCE_NUMBER_SET_SIZE (an->readerSNState.numbits)); + *countp = ++rwn->count; + + /* Reset submessage size, now that we know the real size, and update + the offset to the next submessage. */ + nn_xmsg_shrink (msg, sm_marker, ACKNACK_SIZE (an->readerSNState.numbits)); + nn_xmsg_submsg_setnext (msg, sm_marker); + + TRACE (("acknack %x:%x:%x:%x -> %x:%x:%x:%x: #%d:%"PRId64"/%u:", + PGUID (rwn->rd_guid), PGUID (pwr->e.guid), rwn->count, + base, an->readerSNState.numbits)); + for (ui = 0; ui != an->readerSNState.numbits; ui++) + TRACE (("%c", nn_bitset_isset (numbits, an->readerSNState.bits, ui) ? '1' : '0')); + } + + if (nackfrag_numbits > 0) + { + NackFrag_t *nf; + + /* We use 0-based fragment numbers, but externally have to provide + 1-based fragment numbers */ + assert ((unsigned) nackfrag_numbits == nackfrag.set.numbits); + + nf = nn_xmsg_append (msg, &sm_marker, NACKFRAG_SIZE ((unsigned) nackfrag_numbits)); + + nn_xmsg_submsg_init (msg, sm_marker, SMID_NACK_FRAG); + nf->readerId = nn_hton_entityid (rwn->rd_guid.entityid); + nf->writerId = nn_hton_entityid (pwr->e.guid.entityid); + nf->writerSN = toSN (nackfrag_seq); + nf->fragmentNumberState.bitmap_base = nackfrag.set.bitmap_base + 1; + nf->fragmentNumberState.numbits = nackfrag.set.numbits; + memcpy (nf->fragmentNumberState.bits, nackfrag.set.bits, NN_FRAGMENT_NUMBER_SET_BITS_SIZE (nackfrag_numbits)); + + { + nn_count_t *countp = + (nn_count_t *) ((char *) nf + offsetof (NackFrag_t, fragmentNumberState) + NN_FRAGMENT_NUMBER_SET_SIZE (nf->fragmentNumberState.numbits)); + *countp = ++pwr->nackfragcount; + nn_xmsg_submsg_setnext (msg, sm_marker); + + TRACE ((" + nackfrag #%d:%"PRId64"/%u/%u:", *countp, fromSN (nf->writerSN), nf->fragmentNumberState.bitmap_base, nf->fragmentNumberState.numbits)); + for (ui = 0; ui != nf->fragmentNumberState.numbits; ui++) + TRACE (("%c", nn_bitset_isset (nf->fragmentNumberState.numbits, nf->fragmentNumberState.bits, ui) ? '1' : '0')); + } + } + + TRACE (("\n")); +} + +static void handle_xevk_acknack (UNUSED_ARG (struct nn_xpack *xp), struct xevent *ev, nn_mtime_t tnow) +{ + /* FIXME: ought to keep track of which NACKs are being generated in + response to a Heartbeat. There is no point in having multiple + readers NACK the data. + + FIXME: ought to determine the set of missing samples (as it does + now), and then check which for of those fragments are available already. + A little snag is that the defragmenter can throw out partial samples in + favour of others, so MUST ensure that the defragmenter won't start + threshing and fail to make progress! */ + struct proxy_writer *pwr; + struct nn_xmsg *msg; + struct pwr_rd_match *rwn; + nn_locator_t loc; + + if ((pwr = ephash_lookup_proxy_writer_guid (&ev->u.acknack.pwr_guid)) == NULL) + { + return; + } + + os_mutexLock (&pwr->e.lock); + if ((rwn = ut_avlLookup (&pwr_readers_treedef, &pwr->readers, &ev->u.acknack.rd_guid)) == NULL) + { + os_mutexUnlock (&pwr->e.lock); + return; + } + + if (addrset_any_uc (pwr->c.as, &loc) || addrset_any_mc (pwr->c.as, &loc)) + { + seqno_t nack_seq; + if ((msg = nn_xmsg_new (gv.xmsgpool, &ev->u.acknack.rd_guid.prefix, ACKNACK_SIZE_MAX, NN_XMSG_KIND_CONTROL)) == NULL) + goto outofmem; + nn_xmsg_setdst1 (msg, &ev->u.acknack.pwr_guid.prefix, &loc); + if (config.meas_hb_to_ack_latency && rwn->hb_timestamp.v) + { + /* If HB->ACK latency measurement is enabled, and we have a + timestamp available, add it and clear the time stamp. There + is no real guarantee that the two match, but I haven't got a + solution for that yet ... If adding the time stamp fails, + too bad, but no reason to get worried. */ + nn_xmsg_add_timestamp (msg, rwn->hb_timestamp); + rwn->hb_timestamp.v = 0; + } + add_AckNack (msg, pwr, rwn, &nack_seq); + if (nack_seq) + { + rwn->t_last_nack = tnow; + rwn->seq_last_nack = nack_seq; + /* If NACKing, make sure we don't give up too soon: even though + we're not allowed to send an ACKNACK unless in response to a + HEARTBEAT, I've seen too many cases of not sending an NACK + because the writing side got confused ... Better to recover + eventually. */ + resched_xevent_if_earlier (ev, add_duration_to_mtime (tnow, config.auto_resched_nack_delay)); + } + TRACE (("send acknack(rd %x:%x:%x:%x -> pwr %x:%x:%x:%x)\n", + PGUID (ev->u.acknack.rd_guid), PGUID (ev->u.acknack.pwr_guid))); + } + else + { + TRACE (("skip acknack(rd %x:%x:%x:%x -> pwr %x:%x:%x:%x): no address\n", + PGUID (ev->u.acknack.rd_guid), PGUID (ev->u.acknack.pwr_guid))); + msg = NULL; + } + + if (!pwr->have_seen_heartbeat && tnow.v - rwn->tcreate.v <= 300 * T_SECOND) + { + /* Force pre-emptive AckNacks out until we receive a heartbeat, + but let the frequency drop over time and stop after a couple + of minutes. */ + int intv, age = (int) ((tnow.v - rwn->tcreate.v) / T_SECOND + 1); + if (age <= 10) + intv = 1; + else if (age <= 60) + intv = 2; + else if (age <= 120) + intv = 5; + else + intv = 10; + resched_xevent_if_earlier (ev, add_duration_to_mtime (tnow, intv * T_SECOND)); + } + os_mutexUnlock (&pwr->e.lock); + + /* nn_xpack_addmsg may sleep (for bandwidth-limited channels), so + must be outside the lock */ + if (msg) + nn_xpack_addmsg (xp, msg, 0); + return; + + outofmem: + /* What to do if out of memory? Crash or burn? */ + os_mutexUnlock (&pwr->e.lock); + resched_xevent_if_earlier (ev, add_duration_to_mtime (tnow, 100 * T_MILLISECOND)); +} + + +static void handle_xevk_spdp (UNUSED_ARG (struct nn_xpack *xp), struct xevent *ev, nn_mtime_t tnow) +{ + /* Like the writer pointer in the heartbeat event, the participant pointer in the spdp event is assumed valid. */ + struct participant *pp; + struct proxy_reader *prd; + struct writer *spdp_wr; + struct whc_node *whcn; + serstate_t st; + serdata_t sd; + nn_guid_t kh; + + if ((pp = ephash_lookup_participant_guid (&ev->u.spdp.pp_guid)) == NULL) + { + TRACE (("handle_xevk_spdp %x:%x:%x:%x - unknown guid\n", + PGUID (ev->u.spdp.pp_guid))); + return; + } + + if ((spdp_wr = get_builtin_writer (pp, NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_WRITER)) == NULL) + { + TRACE (("handle_xevk_spdp %x:%x:%x:%x - spdp writer of participant not found\n", + PGUID (ev->u.spdp.pp_guid))); + goto skip; + } + + if (!ev->u.spdp.directed) + { + /* memset is for tracing output */ + memset (&ev->u.spdp.dest_proxypp_guid_prefix, 0, sizeof (ev->u.spdp.dest_proxypp_guid_prefix)); + prd = NULL; + } + else + { + nn_guid_t guid; + guid.prefix = ev->u.spdp.dest_proxypp_guid_prefix; + guid.entityid.u = NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_READER; + if ((prd = ephash_lookup_proxy_reader_guid (&guid)) == NULL) + { + TRACE (("xmit spdp: no proxy reader %x:%x:%x:%x\n", PGUID (guid))); + goto skip; + } + } + + /* Look up data in (transient-local) WHC by key value */ + if ((st = ddsi_serstate_new (gv.serpool, NULL)) == NULL) + { + TRACE (("xmit spdp: skip %x:%x:%x:%x: out of memory\n", PGUID (ev->u.spdp.pp_guid))); + goto skip; + } + kh = nn_hton_guid (ev->u.spdp.pp_guid); + serstate_set_key (st, 1, &kh); + sd = ddsi_serstate_fix (st); + + os_mutexLock (&spdp_wr->e.lock); + if ((whcn = whc_findkey (spdp_wr->whc, sd)) != NULL) + { + /* Claiming it is new rather than a retransmit so that the rexmit + limiting won't kick in. It is best-effort and therefore the + updating of the last transmitted sequence number won't take + place anyway. Nor is it necessary to fiddle with heartbeat + control stuff. */ + enqueue_sample_wrlock_held (spdp_wr, whcn->seq, whcn->plist, whcn->serdata, prd, 1); + } + os_mutexUnlock (&spdp_wr->e.lock); + + ddsi_serdata_unref (sd); + +#ifndef NDEBUG + if (whcn == NULL) + { + /* If undirected, it is pp->spdp_xevent, and that one must never + run into an empty WHC unless it is already marked for deletion. + + If directed, it may happen in response to an SPDP packet during + creation of the participant. This is because pp is inserted in + the hash table quite early on, which, in turn, is because it + needs to be visible for creating its builtin endpoints. But in + this case, the initial broadcast of the SPDP packet of pp will + happen shortly. */ + if (!ev->u.spdp.directed) + { + os_mutexLock (&pp->e.lock); + os_mutexLock (&ev->evq->lock); + assert (ev->tsched.v == TSCHED_DELETE); + os_mutexUnlock (&ev->evq->lock); + os_mutexUnlock (&pp->e.lock); + } + else + { + TRACE (("xmit spdp: suppressing early spdp response from %x:%x:%x:%x to %x:%x:%x:%x\n", + PGUID (pp->e.guid), PGUIDPREFIX (ev->u.spdp.dest_proxypp_guid_prefix), NN_ENTITYID_PARTICIPANT)); + } + } +#endif + + skip: + if (ev->u.spdp.directed) + { + /* Directed events are used to send SPDP packets to newly + discovered peers, and used just once. */ + delete_xevent (ev); + } + else + { + /* schedule next when 80% of the interval has elapsed, or 2s + before the lease ends, whichever comes first (similar to PMD), + but never wait longer than spdp_interval */ + const int64_t mindelta = 10 * T_MILLISECOND; + const int64_t ldur = pp->lease_duration; + nn_mtime_t tnext; + int64_t intv; + + if (ldur < 5 * mindelta / 4) + intv = mindelta; + else if (ldur < 10 * T_SECOND) + intv = 4 * ldur / 5; + else + intv = ldur - 2 * T_SECOND; + if (intv > config.spdp_interval) + intv = config.spdp_interval; + tnext = add_duration_to_mtime (tnow, intv); + TRACE (("xmit spdp %x:%x:%x:%x to %x:%x:%x:%x (resched %gs)\n", + PGUID (pp->e.guid), + PGUIDPREFIX (ev->u.spdp.dest_proxypp_guid_prefix), NN_ENTITYID_SPDP_BUILTIN_PARTICIPANT_READER, + (double)(tnext.v - tnow.v) / 1e9)); + resched_xevent_if_earlier (ev, tnext); + } +} + +static void write_pmd_message (struct nn_xpack *xp, struct participant *pp, unsigned pmd_kind) +{ +#define PMD_DATA_LENGTH 1 + struct writer *wr; + union { + ParticipantMessageData_t pmd; + char pad[offsetof (ParticipantMessageData_t, value) + PMD_DATA_LENGTH]; + } u; + serdata_t serdata; + serstate_t serstate; + struct tkmap_instance *tk; + + if ((wr = get_builtin_writer (pp, NN_ENTITYID_P2P_BUILTIN_PARTICIPANT_MESSAGE_WRITER)) == NULL) + { + TRACE (("write_pmd_message(%x:%x:%x:%x) - builtin pmd writer not found\n", PGUID (pp->e.guid))); + return; + } + + u.pmd.participantGuidPrefix = nn_hton_guid_prefix (pp->e.guid.prefix); + u.pmd.kind = toBE4u (pmd_kind); + u.pmd.length = PMD_DATA_LENGTH; + memset (u.pmd.value, 0, u.pmd.length); + + serstate = ddsi_serstate_new (gv.serpool, NULL); + ddsi_serstate_append_blob (serstate, 4, sizeof (u.pad), &u.pmd); + serstate_set_key (serstate, 0, &u.pmd); + ddsi_serstate_set_msginfo (serstate, 0, now (), NULL); + serdata = ddsi_serstate_fix (serstate); + + /* HORRIBLE HACK ALERT -- serstate/serdata looks at whether topic is + a null pointer to choose PL_CDR_x encoding or regular CDR_x + encoding. */ + serdata->hdr.identifier = PLATFORM_IS_LITTLE_ENDIAN ? CDR_LE : CDR_BE; + + tk = (ddsi_plugin.rhc_lookup_fn) (serdata); + write_sample_nogc (xp, wr, serdata, tk); + (ddsi_plugin.rhc_unref_fn) (tk); +#undef PMD_DATA_LENGTH +} + +static void handle_xevk_pmd_update (struct nn_xpack *xp, struct xevent *ev, nn_mtime_t tnow) +{ + struct participant *pp; + int64_t intv; + nn_mtime_t tnext; + + if ((pp = ephash_lookup_participant_guid (&ev->u.pmd_update.pp_guid)) == NULL) + { + return; + } + + write_pmd_message (xp, pp, PARTICIPANT_MESSAGE_DATA_KIND_AUTOMATIC_LIVELINESS_UPDATE); + + /* QoS changes can't change lease durations. So the only thing that + could cause trouble here is that the addition or removal of a + writer cause the interval to change for this participant. If we + lock pp for reading out the lease duration we are guaranteed a + consistent value (can't assume 64-bit atomic reads on all support + platforms!) */ + os_mutexLock (&pp->e.lock); + intv = pp->lease_duration; + + /* FIXME: need to use smallest liveliness duration of all automatic-liveliness writers */ + if (intv == T_NEVER) + { + tnext.v = T_NEVER; + TRACE (("resched pmd(%x:%x:%x:%x): never\n", PGUID (pp->e.guid))); + } + else + { + /* schedule next when 80% of the interval has elapsed, or 2s + before the lease ends, whichever comes first */ + if (intv >= 10 * T_SECOND) + tnext.v = tnow.v + intv - 2 * T_SECOND; + else + tnext.v = tnow.v + 4 * intv / 5; + TRACE (("resched pmd(%x:%x:%x:%x): %gs\n", PGUID (pp->e.guid), (double)(tnext.v - tnow.v) / 1e9)); + } + + resched_xevent_if_earlier (ev, tnext); + os_mutexUnlock (&pp->e.lock); +} + +static void handle_xevk_end_startup_mode (UNUSED_ARG (struct nn_xpack *xp), struct xevent *ev, UNUSED_ARG (nn_mtime_t tnow)) +{ + struct ephash_enum_writer est; + struct writer *wr; + assert (gv.startup_mode); + nn_log (LC_DISCOVERY, "end startup mode\n"); + gv.startup_mode = 0; + /* FIXME: MEMBAR needed for startup mode (or use a lock) */ + ephash_enum_writer_init (&est); + while ((wr = ephash_enum_writer_next (&est)) != NULL) + writer_exit_startup_mode (wr); + ephash_enum_writer_fini (&est); + delete_xevent (ev); +} + +static void handle_xevk_delete_writer (UNUSED_ARG (struct nn_xpack *xp), struct xevent *ev, UNUSED_ARG (nn_mtime_t tnow)) +{ + /* don't worry if the writer is already gone by the time we get here. */ + TRACE (("handle_xevk_delete_writer: %x:%x:%x:%x\n", PGUID (ev->u.delete_writer.guid))); + delete_writer_nolinger (&ev->u.delete_writer.guid); + delete_xevent (ev); +} + +static void handle_individual_xevent (struct xevent *xev, struct nn_xpack *xp, nn_mtime_t tnow) +{ + switch (xev->kind) + { + case XEVK_HEARTBEAT: + handle_xevk_heartbeat (xp, xev, tnow); + break; + case XEVK_ACKNACK: + handle_xevk_acknack (xp, xev, tnow); + break; + case XEVK_SPDP: + handle_xevk_spdp (xp, xev, tnow); + break; + case XEVK_PMD_UPDATE: + handle_xevk_pmd_update (xp, xev, tnow); + break; + case XEVK_END_STARTUP_MODE: + handle_xevk_end_startup_mode (xp, xev, tnow); + break; + case XEVK_DELETE_WRITER: + handle_xevk_delete_writer (xp, xev, tnow); + break; + case XEVK_CALLBACK: + xev->u.callback.cb (xev, xev->u.callback.arg, tnow); + break; + } +} + +static void handle_individual_xevent_nt (struct xevent_nt *xev, struct nn_xpack *xp) +{ + switch (xev->kind) + { + case XEVK_MSG: + handle_xevk_msg (xp, xev); + break; + case XEVK_MSG_REXMIT: + handle_xevk_msg_rexmit (xp, xev); + break; + case XEVK_ENTITYID: + handle_xevk_entityid (xp, xev); + break; + } + os_free (xev); +} + +static void handle_timed_xevent (struct thread_state1 *self, struct xevent *xev, struct nn_xpack *xp, nn_mtime_t tnow /* monotonic */) +{ + /* This function handles the individual xevent irrespective of + whether it is a "timed" or "non-timed" xevent */ + struct xeventq *xevq = xev->evq; + + /* We relinquish the lock while processing the event, but require it + held for administrative work. */ + ASSERT_MUTEX_HELD (&xevq->lock); + + assert (xev->evq == xevq); + assert (xev->tsched.v != TSCHED_DELETE); + + os_mutexUnlock (&xevq->lock); + thread_state_awake (self); + handle_individual_xevent (xev, xp, tnow /* monotonic */); + os_mutexLock (&xevq->lock); + + ASSERT_MUTEX_HELD (&xevq->lock); +} + +static void handle_nontimed_xevent (struct thread_state1 *self, struct xevent_nt *xev, struct nn_xpack *xp) +{ + /* This function handles the individual xevent irrespective of + whether it is a "timed" or "non-timed" xevent */ + struct xeventq *xevq = xev->evq; + + /* We relinquish the lock while processing the event, but require it + held for administrative work. */ + ASSERT_MUTEX_HELD (&xevq->lock); + + assert (xev->evq == xevq); + + os_mutexUnlock (&xevq->lock); + thread_state_awake (self); + handle_individual_xevent_nt (xev, xp); + /* non-timed xevents are freed by the handlers */ + os_mutexLock (&xevq->lock); + + ASSERT_MUTEX_HELD (&xevq->lock); +} + +static void handle_xevents (struct thread_state1 *self, struct xeventq *xevq, struct nn_xpack *xp, nn_mtime_t tnow /* monotonic */) +{ + int xeventsToProcess = 1; + + ASSERT_MUTEX_HELD (&xevq->lock); + + /* The following loops give priority to the "timed" events (heartbeats, + acknacks etc) if there are any. The algorithm is that we handle all + "timed" events that are scheduled now and then handle one "non-timed" + event. If there weren't any "non-timed" events then the loop + terminates. If there was one, then after handling it, re-read the + clock and continue the loop, i.e. test again to see whether any + "timed" events are now due. */ + + while (xeventsToProcess) + { + while (earliest_in_xeventq(xevq).v <= tnow.v) + { + struct xevent *xev = ut_fibheapExtractMin (&evq_xevents_fhdef, &xevq->xevents); + if (xev->tsched.v == TSCHED_DELETE) + { + free_xevent (xevq, xev); + } + else + { + /* event rescheduling functions look at xev->tsched to + determine whether it is currently on the heap or not (i.e., + scheduled or not), so set to TSCHED_NEVER to indicate it + currently isn't. */ + xev->tsched.v = T_NEVER; + handle_timed_xevent (self, xev, xp, tnow); + } + + /* Limited-bandwidth channels means events can take a LONG time + to process. So read the clock more often. */ + tnow = now_mt (); + } + + if (!non_timed_xmit_list_is_empty (xevq)) + { + struct xevent_nt *xev = getnext_from_non_timed_xmit_list (xevq); + handle_nontimed_xevent (self, xev, xp); + tnow = now_mt (); + } + else + { + xeventsToProcess = 0; + } + } + + ASSERT_MUTEX_HELD (&xevq->lock); +} + +static uint32_t xevent_thread (struct xeventq * xevq) +{ + struct thread_state1 *self = lookup_thread_state (); + struct nn_xpack *xp; + nn_mtime_t next_thread_cputime = { 0 }; + + xp = nn_xpack_new (xevq->tev_conn, xevq->auxiliary_bandwidth_limit, config.xpack_send_async); + + os_mutexLock (&xevq->lock); + while (!xevq->terminate) + { + nn_mtime_t tnow = now_mt (); + + LOG_THREAD_CPUTIME (next_thread_cputime); + + handle_xevents (self, xevq, xp, tnow); + + /* Send to the network unlocked, as it may sleep due to bandwidth + limitation */ + os_mutexUnlock (&xevq->lock); + nn_xpack_send (xp, false); + os_mutexLock (&xevq->lock); + + thread_state_asleep (self); + + if (!non_timed_xmit_list_is_empty (xevq) || xevq->terminate) + { + /* continue immediately */ + } + else + { + nn_mtime_t twakeup = earliest_in_xeventq (xevq); + if (twakeup.v == T_NEVER) + { + /* no scheduled events nor any non-timed events */ + os_condWait (&xevq->cond, &xevq->lock); + } + else + { + /* Although we assumed instantaneous handling of events, we + don't want to sleep much longer than we have to. With + os_condTimedWait requiring a relative time, we don't have + much choice but to read the clock now */ + tnow = now_mt (); + if (twakeup.v > tnow.v) + { + os_time to; + twakeup.v -= tnow.v; /* os_condTimedWait: relative timeout */ + to.tv_sec = (int) (twakeup.v / 1000000000); + to.tv_nsec = (int32_t) (twakeup.v % 1000000000); + os_condTimedWait (&xevq->cond, &xevq->lock, &to); + } + } + } + } + os_mutexUnlock (&xevq->lock); + nn_xpack_send (xp, false); + nn_xpack_free (xp); + return 0; +} + +void qxev_msg (struct xeventq *evq, struct nn_xmsg *msg) +{ + struct xevent_nt *ev; + assert (evq); + assert (nn_xmsg_kind (msg) != NN_XMSG_KIND_DATA_REXMIT); + os_mutexLock (&evq->lock); + ev = qxev_common_nt (evq, XEVK_MSG); + ev->u.msg.msg = msg; + qxev_insert_nt (ev); + os_mutexUnlock (&evq->lock); +} + +void qxev_prd_entityid (struct proxy_reader * prd, nn_guid_prefix_t * id) +{ + struct nn_xmsg *msg; + struct xevent_nt *ev; + + /* For connected transports, may need to establish and identify connection */ + + if (! gv.xevents->tev_conn->m_connless) + { + msg = nn_xmsg_new (gv.xmsgpool, id, sizeof (EntityId_t), NN_XMSG_KIND_CONTROL); + if (nn_xmsg_setdstPRD (msg, prd) == 0) + { + TRACE ((" qxev_prd_entityid (%x:%x:%x)\n", PGUIDPREFIX (*id))); + nn_xmsg_add_entityid (msg); + os_mutexLock (&gv.xevents->lock); + ev = qxev_common_nt (gv.xevents, XEVK_ENTITYID); + ev->u.entityid.msg = msg; + qxev_insert_nt (ev); + os_mutexUnlock (&gv.xevents->lock); + } + else + { + nn_xmsg_free (msg); + } + } +} + +void qxev_pwr_entityid (struct proxy_writer * pwr, nn_guid_prefix_t * id) +{ + struct nn_xmsg *msg; + struct xevent_nt *ev; + + /* For connected transports, may need to establish and identify connection */ + + if (! pwr->evq->tev_conn->m_connless) + { + msg = nn_xmsg_new (gv.xmsgpool, id, sizeof (EntityId_t), NN_XMSG_KIND_CONTROL); + if (nn_xmsg_setdstPWR (msg, pwr) == 0) + { + TRACE ((" qxev_pwr_entityid (%x:%x:%x)\n", PGUIDPREFIX (*id))); + nn_xmsg_add_entityid (msg); + os_mutexLock (&pwr->evq->lock); + ev = qxev_common_nt (pwr->evq, XEVK_ENTITYID); + ev->u.entityid.msg = msg; + qxev_insert_nt (ev); + os_mutexUnlock (&pwr->evq->lock); + } + else + { + nn_xmsg_free (msg); + } + } +} + +int qxev_msg_rexmit_wrlock_held (struct xeventq *evq, struct nn_xmsg *msg, int force) +{ + size_t msg_size = nn_xmsg_size (msg); + struct xevent_nt *ev; + + assert (evq); + assert (nn_xmsg_kind (msg) == NN_XMSG_KIND_DATA_REXMIT); + os_mutexLock (&evq->lock); + if ((ev = lookup_msg (evq, msg)) != NULL && nn_xmsg_merge_rexmit_destinations_wrlock_held (ev->u.msg_rexmit.msg, msg)) + { + /* MSG got merged with a pending retransmit, so it has effectively been queued */ + os_mutexUnlock (&evq->lock); + nn_xmsg_free (msg); + return 1; + } + else if ((evq->queued_rexmit_bytes > evq->max_queued_rexmit_bytes || + evq->queued_rexmit_msgs == evq->max_queued_rexmit_msgs) && + !force) + { + /* drop it if insufficient resources available */ + os_mutexUnlock (&evq->lock); + nn_xmsg_free (msg); +#if 0 + TRACE ((" qxev_msg_rexmit%s drop (sz %"PA_PRIuSIZE" qb %"PA_PRIuSIZE" qm %"PA_PRIuSIZE")", force ? "!" : "", + msg_size, evq->queued_rexmit_bytes, evq->queued_rexmit_msgs)); +#endif + return 0; + } + else + { + ev = qxev_common_nt (evq, XEVK_MSG_REXMIT); + ev->u.msg_rexmit.msg = msg; + ev->u.msg_rexmit.queued_rexmit_bytes = msg_size; + evq->queued_rexmit_bytes += msg_size; + evq->queued_rexmit_msgs++; + qxev_insert_nt (ev); +#if 0 + TRACE (("AAA(%p,%"PA_PRIuSIZE")", (void *) ev, msg_size)); +#endif + os_mutexUnlock (&evq->lock); + return 2; + } +} + +struct xevent *qxev_heartbeat (struct xeventq *evq, nn_mtime_t tsched, const nn_guid_t *wr_guid) +{ + /* Event _must_ be deleted before enough of the writer is freed to + cause trouble. Currently used exclusively for + wr->heartbeat_xevent. */ + struct xevent *ev; + assert(evq); + os_mutexLock (&evq->lock); + ev = qxev_common (evq, tsched, XEVK_HEARTBEAT); + ev->u.heartbeat.wr_guid = *wr_guid; + qxev_insert (ev); + os_mutexUnlock (&evq->lock); + return ev; +} + +struct xevent *qxev_acknack (struct xeventq *evq, nn_mtime_t tsched, const nn_guid_t *pwr_guid, const nn_guid_t *rd_guid) +{ + struct xevent *ev; + assert(evq); + os_mutexLock (&evq->lock); + ev = qxev_common (evq, tsched, XEVK_ACKNACK); + ev->u.acknack.pwr_guid = *pwr_guid; + ev->u.acknack.rd_guid = *rd_guid; + qxev_insert (ev); + os_mutexUnlock (&evq->lock); + return ev; +} + +struct xevent *qxev_spdp (nn_mtime_t tsched, const nn_guid_t *pp_guid, const nn_guid_t *dest_proxypp_guid) +{ + struct xevent *ev; + os_mutexLock (&gv.xevents->lock); + ev = qxev_common (gv.xevents, tsched, XEVK_SPDP); + ev->u.spdp.pp_guid = *pp_guid; + if (dest_proxypp_guid == NULL) + ev->u.spdp.directed = 0; + else + { + ev->u.spdp.dest_proxypp_guid_prefix = dest_proxypp_guid->prefix; + ev->u.spdp.directed = 1; + } + qxev_insert (ev); + os_mutexUnlock (&gv.xevents->lock); + return ev; +} + +struct xevent *qxev_pmd_update (nn_mtime_t tsched, const nn_guid_t *pp_guid) +{ + struct xevent *ev; + os_mutexLock (&gv.xevents->lock); + ev = qxev_common (gv.xevents, tsched, XEVK_PMD_UPDATE); + ev->u.pmd_update.pp_guid = *pp_guid; + qxev_insert (ev); + os_mutexUnlock (&gv.xevents->lock); + return ev; +} + +struct xevent *qxev_end_startup_mode (nn_mtime_t tsched) +{ + struct xevent *ev; + os_mutexLock (&gv.xevents->lock); + ev = qxev_common (gv.xevents, tsched, XEVK_END_STARTUP_MODE); + qxev_insert (ev); + os_mutexUnlock (&gv.xevents->lock); + return ev; +} + +struct xevent *qxev_delete_writer (nn_mtime_t tsched, const nn_guid_t *guid) +{ + struct xevent *ev; + os_mutexLock (&gv.xevents->lock); + ev = qxev_common (gv.xevents, tsched, XEVK_DELETE_WRITER); + ev->u.delete_writer.guid = *guid; + qxev_insert (ev); + os_mutexUnlock (&gv.xevents->lock); + return ev; +} + +struct xevent *qxev_callback (nn_mtime_t tsched, void (*cb) (struct xevent *ev, void *arg, nn_mtime_t tnow), void *arg) +{ + struct xevent *ev; + os_mutexLock (&gv.xevents->lock); + ev = qxev_common (gv.xevents, tsched, XEVK_CALLBACK); + ev->u.callback.cb = cb; + ev->u.callback.arg = arg; + qxev_insert (ev); + os_mutexUnlock (&gv.xevents->lock); + return ev; +} diff --git a/src/core/ddsi/src/q_xmsg.c b/src/core/ddsi/src/q_xmsg.c new file mode 100644 index 0000000..15a583f --- /dev/null +++ b/src/core/ddsi/src/q_xmsg.c @@ -0,0 +1,1882 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include /* for IOV_MAX */ +#endif + +#include "os/os.h" + +#include "util/ut_avl.h" +#include "util/ut_thread_pool.h" + +#include "ddsi/ddsi_ser.h" +#include "ddsi/q_protocol.h" +#include "ddsi/q_xqos.h" +#include "ddsi/q_bswap.h" +#include "ddsi/q_rtps.h" +#include "ddsi/q_addrset.h" +#include "ddsi/q_error.h" +#include "ddsi/q_misc.h" +#include "ddsi/q_log.h" +#include "ddsi/q_unused.h" +#include "ddsi/q_xmsg.h" +#include "ddsi/q_align.h" +#include "ddsi/q_config.h" +#include "ddsi/q_entity.h" +#include "ddsi/q_globals.h" +#include "ddsi/q_ephash.h" +#include "ddsi/q_freelist.h" +#include "q__osplser.h" + +#include "ddsi/sysdeps.h" + +#define NN_XMSG_MAX_ALIGN 8 +#define NN_XMSG_CHUNK_SIZE 128 + +struct nn_xmsgpool { + struct nn_freelist freelist; +}; + +struct nn_xmsg_data { + InfoSRC_t src; + InfoDST_t dst; + char payload[1]; /* of size maxsz */ +}; + +struct nn_xmsg_chain_elem { + struct nn_xmsg_chain_elem *older; +}; + +enum nn_xmsg_dstmode { + NN_XMSG_DST_UNSET, + NN_XMSG_DST_ONE, + NN_XMSG_DST_ALL +}; + +struct nn_xmsg { + struct nn_xmsgpool *pool; + size_t maxsz; + size_t sz; + int have_params; + struct serdata *refd_payload; + struct iovec refd_payload_iov; + int64_t maxdelay; +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + uint32_t encoderid; +#endif + + /* Backref for late updating of available sequence numbers, and + merging of retransmits. */ + enum nn_xmsg_kind kind; + union { + char control; + struct { + nn_guid_t wrguid; + seqno_t wrseq; + nn_fragment_number_t wrfragid; + /* readerId encodes offset to destination readerId or 0 -- used + only for rexmits, but more convenient to combine both into + one struct in the union */ + unsigned readerId_off; + } data; + } kindspecific; + + enum nn_xmsg_dstmode dstmode; + union { + struct { + nn_locator_t loc; /* send just to this locator */ + } one; + struct { + struct addrset *as; /* send to all addresses in set */ + struct addrset *as_group; /* send to one address in set */ + } all; + } dstaddr; + + struct nn_xmsg_chain_elem link; + struct nn_xmsg_data *data; +}; + +/* Worst-case: change of SRC [+1] but no DST, submessage [+1], ref'd + payload [+1]. So 128 iovecs => at least ~40 submessages, so for + very small ones still >1kB. */ +#define NN_XMSG_MAX_SUBMESSAGE_IOVECS 3 + +#ifdef IOV_MAX +#if IOV_MAX > 0 && IOV_MAX < 256 +#define NN_XMSG_MAX_MESSAGE_IOVECS IOV_MAX +#endif +#endif /* defined IOV_MAX */ +#ifndef NN_XMSG_MAX_MESSAGE_IOVECS +#define NN_XMSG_MAX_MESSAGE_IOVECS 256 +#endif + +/* Used to keep them in order, but it now transpires that delayed + updating of writer seq nos benefits from having them in the + reverse order. They are not being used for anything else, so + we no longer maintain a pointer to both ends. */ +struct nn_xmsg_chain { + struct nn_xmsg_chain_elem *latest; +}; + +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING +#define NN_BW_UNLIMITED (0) + +struct nn_bw_limiter { + uint32_t bandwidth; /*Config in bytes/s (0 = UNLIMITED)*/ + int64_t balance; + nn_mtime_t last_update; +}; +#endif + +/////////////////////////// +typedef struct os_sem { + os_mutex mtx; + uint32_t value; + os_cond cv; +} os_sem_t; + +static os_result os_sem_init (os_sem_t * sem, uint32_t value) +{ + sem->value = value; + os_mutexInit (&sem->mtx); + os_condInit (&sem->cv, &sem->mtx); + return os_resultSuccess; +} + +static os_result os_sem_destroy (os_sem_t *sem) +{ + os_condDestroy (&sem->cv); + os_mutexDestroy (&sem->mtx); + return os_resultSuccess; +} + +static os_result os_sem_post (os_sem_t *sem) +{ + os_mutexLock (&sem->mtx); + if (sem->value++ == 0) + os_condSignal (&sem->cv); + os_mutexUnlock (&sem->mtx); + return os_resultSuccess; +} + +static os_result os_sem_wait (os_sem_t *sem) +{ + os_mutexLock (&sem->mtx); + while (sem->value == 0) + os_condWait (&sem->cv, &sem->mtx); + os_mutexUnlock (&sem->mtx); + return os_resultSuccess; +} +/////////////////////////// + +struct nn_xpack +{ + struct nn_xpack *sendq_next; + bool async_mode; + Header_t hdr; + MsgLen_t msg_len; + nn_guid_prefix_t *last_src; + InfoDST_t *last_dst; + int64_t maxdelay; + unsigned packetid; + os_atomic_uint32_t calls; + uint32_t call_flags; + ddsi_tran_conn_t conn; + os_sem_t sem; + size_t niov; + struct iovec iov[NN_XMSG_MAX_MESSAGE_IOVECS]; + enum nn_xmsg_dstmode dstmode; + + union + { + nn_locator_t loc; /* send just to this locator */ + struct + { + struct addrset *as; /* send to all addresses in set */ + struct addrset *as_group; /* send to one address in set */ + } all; + } dstaddr; + + struct nn_xmsg_chain included_msgs; + +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING + struct nn_bw_limiter limiter; +#endif + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + uint32_t encoderId; +#endif /* DDSI_INCLUDE_NETWORK_PARTITIONS */ + +#ifdef DDSI_INCLUDE_ENCRYPTION + /* each partion is associated with a SecurityPolicy, this codecset will serve */ + /* all of them, different cipher for each partition */ + q_securityEncoderSet codec; + PT_InfoContainer_t SecurityHeader; +#endif /* DDSI_INCLUDE_ENCRYPTION */ +}; + +static unsigned align4u (unsigned x) +{ + return (x + 3u) & (unsigned)-4; +} + +/* XMSGPOOL ------------------------------------------------------------ + + Great expectations, but so far still wanting. */ + +static void nn_xmsg_realfree (struct nn_xmsg *m); + +struct nn_xmsgpool *nn_xmsgpool_new (void) +{ + struct nn_xmsgpool *pool; + pool = os_malloc (sizeof (*pool)); + nn_freelist_init (&pool->freelist, UINT32_MAX, offsetof (struct nn_xmsg, link.older)); + return pool; +} + +static void nn_xmsg_realfree_wrap (void *elem) +{ + nn_xmsg_realfree (elem); +} + +void nn_xmsgpool_free (struct nn_xmsgpool *pool) +{ + nn_freelist_fini (&pool->freelist, nn_xmsg_realfree_wrap); + TRACE (("xmsgpool_free(%p)\n", pool)); + os_free (pool); +} + +/* XMSG ---------------------------------------------------------------- + + All messages that are sent start out as xmsgs, which is a sequence + of submessages potentially ending with a blob of serialized data. + Such serialized data is given as a reference to part of a serdata. + + An xmsg can be queued for transmission, after which it must be + forgotten by its creator. The queue handler packs them into xpacks + (see below), transmits them, and releases them. + + Currently, the message pool is fake, so 2 mallocs and frees are + needed for each message, and additionally, it involves address set + manipulations. The latter is especially inefficiently dealt with + in the xpack. */ + +static void nn_xmsg_reinit (struct nn_xmsg *m, enum nn_xmsg_kind kind) +{ + m->sz = 0; + m->have_params = 0; + m->refd_payload = NULL; + m->dstmode = NN_XMSG_DST_UNSET; + m->kind = kind; + m->maxdelay = 0; +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + m->encoderid = 0; +#endif + memset (&m->kindspecific, 0, sizeof (m->kindspecific)); +} + +static struct nn_xmsg *nn_xmsg_allocnew (struct nn_xmsgpool *pool, size_t expected_size, enum nn_xmsg_kind kind) +{ + const nn_vendorid_t myvendorid = MY_VENDOR_ID; + struct nn_xmsg *m; + struct nn_xmsg_data *d; + + if (expected_size == 0) + expected_size = NN_XMSG_CHUNK_SIZE; + + if ((m = os_malloc (sizeof (*m))) == NULL) + return NULL; + + m->pool = pool; + m->maxsz = (expected_size + NN_XMSG_CHUNK_SIZE - 1) & (unsigned)-NN_XMSG_CHUNK_SIZE; + + if ((d = m->data = os_malloc (offsetof (struct nn_xmsg_data, payload) + m->maxsz)) == NULL) + { + os_free (m); + return NULL; + } + d->src.smhdr.submessageId = SMID_INFO_SRC; + d->src.smhdr.flags = (PLATFORM_IS_LITTLE_ENDIAN ? SMFLAG_ENDIANNESS : 0); + d->src.smhdr.octetsToNextHeader = sizeof (d->src) - (offsetof (InfoSRC_t, smhdr.octetsToNextHeader) + 2); + d->src.unused = 0; + d->src.version.major = RTPS_MAJOR; + d->src.version.minor = RTPS_MINOR; + d->src.vendorid = myvendorid; + d->dst.smhdr.submessageId = SMID_INFO_DST; + d->dst.smhdr.flags = (PLATFORM_IS_LITTLE_ENDIAN ? SMFLAG_ENDIANNESS : 0); + d->dst.smhdr.octetsToNextHeader = sizeof (d->dst.guid_prefix); + nn_xmsg_reinit (m, kind); + return m; +} + +struct nn_xmsg *nn_xmsg_new (struct nn_xmsgpool *pool, const nn_guid_prefix_t *src_guid_prefix, size_t expected_size, enum nn_xmsg_kind kind) +{ + struct nn_xmsg *m; + if ((m = nn_freelist_pop (&pool->freelist)) != NULL) + nn_xmsg_reinit (m, kind); + else if ((m = nn_xmsg_allocnew (pool, expected_size, kind)) == NULL) + return NULL; + m->data->src.guid_prefix = nn_hton_guid_prefix (*src_guid_prefix); + return m; +} + +static void nn_xmsg_realfree (struct nn_xmsg *m) +{ + os_free (m->data); + os_free (m); +} + +void nn_xmsg_free (struct nn_xmsg *m) +{ + struct nn_xmsgpool *pool = m->pool; + if (m->refd_payload) + { + ddsi_serdata_unref (m->refd_payload); + } + if (m->dstmode == NN_XMSG_DST_ALL) + { + unref_addrset (m->dstaddr.all.as); + unref_addrset (m->dstaddr.all.as_group); + } + if (!nn_freelist_push (&pool->freelist, m)) + nn_xmsg_realfree (m); +} + +/************************************************/ + +#ifndef NDEBUG +static int submsg_is_compatible (const struct nn_xmsg *msg, SubmessageKind_t smkind) +{ + switch (msg->kind) + { + case NN_XMSG_KIND_CONTROL: + switch (smkind) + { + case SMID_PAD: + /* never use this one -- so let's crash when we do :) */ + return 0; + case SMID_INFO_SRC: case SMID_INFO_REPLY_IP4: + case SMID_INFO_DST: case SMID_INFO_REPLY: + /* we never generate these directly */ + return 0; + case SMID_INFO_TS: + case SMID_ACKNACK: case SMID_HEARTBEAT: + case SMID_GAP: case SMID_NACK_FRAG: + case SMID_HEARTBEAT_FRAG: + case SMID_PT_INFO_CONTAINER: + case SMID_PT_MSG_LEN: + case SMID_PT_ENTITY_ID: + /* normal control stuff is ok */ + return 1; + case SMID_DATA: case SMID_DATA_FRAG: + /* but data is strictly verboten */ + return 0; + } + assert (0); + break; + case NN_XMSG_KIND_DATA: + case NN_XMSG_KIND_DATA_REXMIT: + switch (smkind) + { + case SMID_PAD: + /* never use this one -- so let's crash when we do :) */ + return 0; + case SMID_INFO_SRC: case SMID_INFO_REPLY_IP4: + case SMID_INFO_DST: case SMID_INFO_REPLY: + /* we never generate these directly */ + return 0; + case SMID_INFO_TS: case SMID_DATA: case SMID_DATA_FRAG: + /* Timestamp only preceding data; data may be present just + once for rexmits. The readerId offset can be used to + ensure rexmits have only one data submessages -- the test + won't work for initial transmits, but those currently + don't allow a readerId */ + return msg->kindspecific.data.readerId_off == 0; + case SMID_ACKNACK: + case SMID_HEARTBEAT: + case SMID_GAP: + case SMID_NACK_FRAG: + case SMID_HEARTBEAT_FRAG: + case SMID_PT_INFO_CONTAINER: + case SMID_PT_MSG_LEN: + case SMID_PT_ENTITY_ID: + /* anything else is strictly verboten */ + return 0; + } + assert (0); + break; + } + assert (0); + return 1; +} +#endif + +int nn_xmsg_compare_fragid (const struct nn_xmsg *a, const struct nn_xmsg *b) +{ + int c; + assert (a->kind == NN_XMSG_KIND_DATA_REXMIT); + assert (b->kind == NN_XMSG_KIND_DATA_REXMIT); + /* I think most likely discriminator is seq, then writer guid, then + fragid, but we'll stick to the "expected" order for now: writer, + seq, frag */ + if ((c = memcmp (&a->kindspecific.data.wrguid, &b->kindspecific.data.wrguid, sizeof (a->kindspecific.data.wrguid))) != 0) + return c; + else if (a->kindspecific.data.wrseq != b->kindspecific.data.wrseq) + return (a->kindspecific.data.wrseq < b->kindspecific.data.wrseq) ? -1 : 1; + else if (a->kindspecific.data.wrfragid != b->kindspecific.data.wrfragid) + return (a->kindspecific.data.wrfragid < b->kindspecific.data.wrfragid) ? -1 : 1; + else + return 0; +} + +size_t nn_xmsg_size (const struct nn_xmsg *m) +{ + return m->sz; +} + +enum nn_xmsg_kind nn_xmsg_kind (const struct nn_xmsg *m) +{ + return m->kind; +} + +void nn_xmsg_guid_seq_fragid (const struct nn_xmsg *m, nn_guid_t *wrguid, seqno_t *wrseq, nn_fragment_number_t *wrfragid) +{ + assert (m->kind != NN_XMSG_KIND_CONTROL); + *wrguid = m->kindspecific.data.wrguid; + *wrseq = m->kindspecific.data.wrseq; + *wrfragid = m->kindspecific.data.wrfragid; +} + +void *nn_xmsg_payload (size_t *sz, struct nn_xmsg *m) +{ + *sz = m->sz; + return m->data->payload; +} + +void nn_xmsg_submsg_init (struct nn_xmsg *msg, struct nn_xmsg_marker marker, SubmessageKind_t smkind) +{ + SubmessageHeader_t *hdr = (SubmessageHeader_t *) (msg->data->payload + marker.offset); + assert (submsg_is_compatible (msg, smkind)); + hdr->submessageId = smkind; + hdr->flags = PLATFORM_IS_LITTLE_ENDIAN ? SMFLAG_ENDIANNESS : 0; + hdr->octetsToNextHeader = 0; +} + +void nn_xmsg_submsg_setnext (struct nn_xmsg *msg, struct nn_xmsg_marker marker) +{ + SubmessageHeader_t *hdr = (SubmessageHeader_t *) (msg->data->payload + marker.offset); + unsigned plsize = msg->refd_payload ? (unsigned) msg->refd_payload_iov.iov_len : 0; + assert ((msg->sz % 4) == 0); + assert ((plsize % 4) == 0); + assert ((unsigned) (msg->data->payload + msg->sz + plsize - (char *) hdr) >= RTPS_SUBMESSAGE_HEADER_SIZE); + hdr->octetsToNextHeader = (unsigned short) + ((unsigned)(msg->data->payload + msg->sz + plsize - (char *) hdr) - RTPS_SUBMESSAGE_HEADER_SIZE); +} + +void *nn_xmsg_submsg_from_marker (struct nn_xmsg *msg, struct nn_xmsg_marker marker) +{ + return msg->data->payload + marker.offset; +} + +void * nn_xmsg_append (struct nn_xmsg *m, struct nn_xmsg_marker *marker, size_t sz) +{ + static const size_t a = 4; + + /* May realloc, in which case m may change. But that will not + happen if you do not exceed expected_size. Max size is always a + multiple of A: that means we don't have to worry about memory + available just for alignment. */ + char *p; + assert (1 <= a && a <= NN_XMSG_MAX_ALIGN); + assert ((m->maxsz % a) == 0); + if ((m->sz % a) != 0) + { + size_t npad = a - (m->sz % a); + memset (m->data->payload + m->sz, 0, npad); + m->sz += npad; + } + if (m->sz + sz > m->maxsz) + { + size_t nmax = (m->maxsz + sz + NN_XMSG_CHUNK_SIZE - 1) & (size_t)-NN_XMSG_CHUNK_SIZE; + struct nn_xmsg_data *ndata = os_realloc (m->data, offsetof (struct nn_xmsg_data, payload) + nmax); + m->maxsz = nmax; + m->data = ndata; + } + p = m->data->payload + m->sz; + if (marker) + marker->offset = m->sz; + m->sz += sz; + return p; +} + +void nn_xmsg_shrink (struct nn_xmsg *m, struct nn_xmsg_marker marker, size_t sz) +{ + assert (m != NULL); + assert (marker.offset <= m->sz); + assert (marker.offset + sz <= m->sz); + m->sz = marker.offset + sz; +} + +void nn_xmsg_add_timestamp (struct nn_xmsg *m, nn_wctime_t t) +{ + InfoTimestamp_t * ts; + struct nn_xmsg_marker sm; + + ts = (InfoTimestamp_t*) nn_xmsg_append (m, &sm, sizeof (InfoTimestamp_t)); + nn_xmsg_submsg_init (m, sm, SMID_INFO_TS); + ts->time = nn_wctime_to_ddsi_time (t); + nn_xmsg_submsg_setnext (m, sm); +} + +void nn_xmsg_add_entityid (struct nn_xmsg * m) +{ + EntityId_t * eid; + struct nn_xmsg_marker sm; + + eid = (EntityId_t*) nn_xmsg_append (m, &sm, sizeof (EntityId_t)); + nn_xmsg_submsg_init (m, sm, SMID_PT_ENTITY_ID); + eid->entityid.u = NN_ENTITYID_PARTICIPANT; + nn_xmsg_submsg_setnext (m, sm); +} + +void nn_xmsg_serdata (struct nn_xmsg *m, serdata_t serdata, unsigned off, unsigned len) +{ + if (!ddsi_serdata_is_empty (serdata)) + { + unsigned len4 = align4u (len); + assert (m->refd_payload == NULL); + m->refd_payload = ddsi_serdata_ref (serdata); + m->refd_payload_iov.iov_base = (char *) &m->refd_payload->hdr + off; + m->refd_payload_iov.iov_len = len4; + } +} + +void nn_xmsg_setdst1 (struct nn_xmsg *m, const nn_guid_prefix_t *gp, const nn_locator_t *loc) +{ + assert (m->dstmode == NN_XMSG_DST_UNSET); + m->dstmode = NN_XMSG_DST_ONE; + m->dstaddr.one.loc = *loc; + m->data->dst.guid_prefix = nn_hton_guid_prefix (*gp); +} + +int nn_xmsg_setdstPRD (struct nn_xmsg *m, const struct proxy_reader *prd) +{ + nn_locator_t loc; + if (addrset_any_uc (prd->c.as, &loc) || addrset_any_mc (prd->c.as, &loc)) + { + nn_xmsg_setdst1 (m, &prd->e.guid.prefix, &loc); + return 0; + } + else + { + NN_WARNING("nn_xmsg_setdstPRD: no address for %x:%x:%x:%x", PGUID (prd->e.guid)); + return ERR_NO_ADDRESS; + } +} + +int nn_xmsg_setdstPWR (struct nn_xmsg *m, const struct proxy_writer *pwr) +{ + nn_locator_t loc; + if (addrset_any_uc (pwr->c.as, &loc) || addrset_any_mc (pwr->c.as, &loc)) + { + nn_xmsg_setdst1 (m, &pwr->e.guid.prefix, &loc); + return 0; + } + NN_WARNING ("nn_xmsg_setdstPRD: no address for %x:%x:%x:%x", PGUID (pwr->e.guid)); + return ERR_NO_ADDRESS; +} + +void nn_xmsg_setdstN (struct nn_xmsg *m, struct addrset *as, struct addrset *as_group) +{ + assert (m->dstmode == NN_XMSG_DST_UNSET || m->dstmode == NN_XMSG_DST_ONE); + m->dstmode = NN_XMSG_DST_ALL; + m->dstaddr.all.as = ref_addrset (as); + m->dstaddr.all.as_group = ref_addrset (as_group); +} + +void nn_xmsg_set_data_readerId (struct nn_xmsg *m, nn_entityid_t *readerId) +{ + assert (m->kind == NN_XMSG_KIND_DATA_REXMIT); + assert (m->kindspecific.data.readerId_off == 0); + assert ((char *) readerId > m->data->payload); + assert ((char *) readerId < m->data->payload + m->sz); + m->kindspecific.data.readerId_off = (unsigned) ((char *) readerId - m->data->payload); +} + +static void clear_readerId (struct nn_xmsg *m) +{ + assert (m->kind == NN_XMSG_KIND_DATA_REXMIT); + assert (m->kindspecific.data.readerId_off != 0); + *((nn_entityid_t *) (m->data->payload + m->kindspecific.data.readerId_off)) = + nn_hton_entityid (to_entityid (NN_ENTITYID_UNKNOWN)); +} + +static nn_entityid_t load_readerId (const struct nn_xmsg *m) +{ + assert (m->kind == NN_XMSG_KIND_DATA_REXMIT); + assert (m->kindspecific.data.readerId_off != 0); + return nn_ntoh_entityid (*((nn_entityid_t *) (m->data->payload + m->kindspecific.data.readerId_off))); +} + +static int readerId_compatible (const struct nn_xmsg *m, const struct nn_xmsg *madd) +{ + nn_entityid_t e = load_readerId (m); + nn_entityid_t eadd = load_readerId (madd); + return e.u == NN_ENTITYID_UNKNOWN || e.u == eadd.u; +} + +int nn_xmsg_merge_rexmit_destinations_wrlock_held (struct nn_xmsg *m, const struct nn_xmsg *madd) +{ + assert (m->kindspecific.data.wrseq >= 1); + assert (m->kindspecific.data.wrguid.prefix.u[0] != 0); + assert (is_writer_entityid (m->kindspecific.data.wrguid.entityid)); + assert (memcmp (&m->kindspecific.data.wrguid, &madd->kindspecific.data.wrguid, sizeof (m->kindspecific.data.wrguid)) == 0); + assert (m->kindspecific.data.wrseq == madd->kindspecific.data.wrseq); + assert (m->kindspecific.data.wrfragid == madd->kindspecific.data.wrfragid); + assert (m->kind == NN_XMSG_KIND_DATA_REXMIT); + assert (madd->kind == NN_XMSG_KIND_DATA_REXMIT); + assert (m->kindspecific.data.readerId_off != 0); + assert (madd->kindspecific.data.readerId_off != 0); + + TRACE ((" (%x:%x:%x:%x#%"PRId64"/%u:", + PGUID (m->kindspecific.data.wrguid), m->kindspecific.data.wrseq, m->kindspecific.data.wrfragid + 1)); + + switch (m->dstmode) + { + case NN_XMSG_DST_UNSET: + assert (0); + return 0; + + case NN_XMSG_DST_ALL: + TRACE (("*->*)")); + return 1; + + case NN_XMSG_DST_ONE: + switch (madd->dstmode) + { + case NN_XMSG_DST_UNSET: + assert (0); + return 0; + + case NN_XMSG_DST_ALL: + TRACE (("1+*->*)")); + clear_readerId (m); + m->dstmode = NN_XMSG_DST_ALL; + m->dstaddr.all.as = ref_addrset (madd->dstaddr.all.as); + m->dstaddr.all.as_group = ref_addrset (madd->dstaddr.all.as_group); + return 1; + + case NN_XMSG_DST_ONE: + if (memcmp (&m->data->dst.guid_prefix, &madd->data->dst.guid_prefix, sizeof (m->data->dst.guid_prefix)) != 0) + { + struct writer *wr; + /* This is why wr->e.lock must be held: we can't safely + reference the writer's address set if it isn't -- so + FIXME: add a way to atomically replace the contents of + an addrset in rebuild_writer_addrset: then we don't + need the lock anymore, and the '_wrlock_held' suffix + can go and everyone's life will become easier! */ + if ((wr = ephash_lookup_writer_guid (&m->kindspecific.data.wrguid)) == NULL) + { + TRACE (("writer-dead)")); + return 0; + } + else + { + TRACE (("1+1->*)")); + clear_readerId (m); + m->dstmode = NN_XMSG_DST_ALL; + m->dstaddr.all.as = ref_addrset (wr->as); + m->dstaddr.all.as_group = ref_addrset (wr->as_group); + return 1; + } + } + else if (readerId_compatible (m, madd)) + { + TRACE (("1+1->1)")); + return 1; + } + else + { + TRACE (("1+1->2)")); + clear_readerId (m); + return 1; + } + } + break; + } + assert (0); + return 0; +} + +int nn_xmsg_setmaxdelay (struct nn_xmsg *msg, int64_t maxdelay) +{ + assert (msg->maxdelay == 0); + msg->maxdelay = maxdelay; + return 0; +} + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS +int nn_xmsg_setencoderid (struct nn_xmsg *msg, uint32_t encoderid) +{ + assert (msg->encoderid == 0); + msg->encoderid = encoderid; + return 0; +} +#endif + +void nn_xmsg_setwriterseq (struct nn_xmsg *msg, const nn_guid_t *wrguid, seqno_t wrseq) +{ + msg->kindspecific.data.wrguid = *wrguid; + msg->kindspecific.data.wrseq = wrseq; +} + +void nn_xmsg_setwriterseq_fragid (struct nn_xmsg *msg, const nn_guid_t *wrguid, seqno_t wrseq, nn_fragment_number_t wrfragid) +{ + nn_xmsg_setwriterseq (msg, wrguid, wrseq); + msg->kindspecific.data.wrfragid = wrfragid; +} + +unsigned nn_xmsg_add_string_padded(_Inout_opt_ unsigned char *buf, _In_ char *str) +{ + unsigned len = (unsigned) strlen (str) + 1; + if (buf) { + /* Add cdr string */ + struct cdrstring *p = (struct cdrstring *) buf; + p->length = len; + memcpy (p->contents, str, len); + /* clear padding */ + if (len < align4u (len)) { + memset (p->contents + len, 0, align4u (len) - len); + } + } + len = 4 + /* cdr string len arg + */ + align4u(len); /* strlen + possible padding */ + return len; +} + +unsigned nn_xmsg_add_octseq_padded(_Inout_opt_ unsigned char *buf, _In_ nn_octetseq_t *seq) +{ + unsigned len = seq->length; + if (buf) { + /* Add cdr octet seq */ + *((unsigned *)buf) = len; + buf += sizeof (int); + memcpy (buf, seq->value, len); + /* clear padding */ + if (len < align4u (len)) { + memset (buf + len, 0, align4u (len) - len); + } + } + len = 4 + /* cdr sequence len arg + */ + align4u(len); /* seqlen + possible padding */ + return len; +} + + +unsigned nn_xmsg_add_dataholder_padded (_Inout_opt_ unsigned char *buf, const struct nn_dataholder *dh) +{ + unsigned i, len; + unsigned dummy = 0; + unsigned *cnt = &dummy; + + len = nn_xmsg_add_string_padded(buf, dh->class_id); + + if (buf) { + cnt = ((unsigned *)&(buf[len])); + *cnt = 0; + } + len += sizeof(int); + for (i = 0; i < dh->properties.n; i++) { + nn_property_t *p = &(dh->properties.props[i]); + if (p->propagate) { + len += nn_xmsg_add_string_padded(buf ? &(buf[len]) : NULL, p->name); + len += nn_xmsg_add_string_padded(buf ? &(buf[len]) : NULL, p->value); + (*cnt)++; + } + /* p->propagate is not propagated over the wire. */ + } + + if (buf) { + cnt = ((unsigned *)&(buf[len])); + *cnt = 0; + } + len += sizeof(int); + for (i = 0; i < dh->binary_properties.n; i++) { + nn_binaryproperty_t *p = &(dh->binary_properties.props[i]); + if (p->propagate) { + len += nn_xmsg_add_string_padded(buf ? &(buf[len]) : NULL, p->name ); + len += nn_xmsg_add_octseq_padded(buf ? &(buf[len]) : NULL, &(p->value)); + (*cnt)++; + } + /* p->propagate is not propagated over the wire. */ + } + + return len; +} + + +void * nn_xmsg_addpar (struct nn_xmsg *m, unsigned pid, size_t len) +{ + const size_t len4 = (len + 3) & (size_t)-4; /* must alloc a multiple of 4 */ + nn_parameter_t *phdr; + char *p; + m->have_params = 1; + phdr = nn_xmsg_append (m, NULL, sizeof (nn_parameter_t) + len4); + phdr->parameterid = (nn_parameterid_t) pid; + phdr->length = (unsigned short) len4; + p = (char *) (phdr + 1); + if (len4 > len) + { + /* zero out padding bytes added to satisfy parameter alignment -- + alternative: zero out, but this way valgrind/purify can tell us + where we forgot to initialize something */ + memset (p + len, 0, len4 - len); + } + return p; +} + +void nn_xmsg_addpar_string (struct nn_xmsg *m, unsigned pid, const char *str) +{ + struct cdrstring *p; + unsigned len = (unsigned) strlen (str) + 1; + p = nn_xmsg_addpar (m, pid, 4 + len); + p->length = len; + memcpy (p->contents, str, len); +} + +void nn_xmsg_addpar_octetseq (struct nn_xmsg *m, unsigned pid, const nn_octetseq_t *oseq) +{ + char *p = nn_xmsg_addpar (m, pid, 4 + oseq->length); + *((unsigned *) p) = oseq->length; + memcpy (p + sizeof (int), oseq->value, oseq->length); +} + +void nn_xmsg_addpar_stringseq (struct nn_xmsg *m, unsigned pid, const nn_stringseq_t *sseq) +{ + char *tmp; + unsigned i, len = 0; + + for (i = 0; i < sseq->n; i++) + { + len += nn_xmsg_add_string_padded(NULL, sseq->strs[i]); + } + + tmp = nn_xmsg_addpar (m, pid, 4 + len); + + *((unsigned *) tmp) = sseq->n; + tmp += sizeof (int); + for (i = 0; i < sseq->n; i++) + { + tmp += nn_xmsg_add_string_padded(tmp, sseq->strs[i]); + } +} + +void nn_xmsg_addpar_keyhash (struct nn_xmsg *m, const struct serdata *serdata) +{ + if (!ddsi_serdata_is_empty (serdata)) + { + char *p = nn_xmsg_addpar (m, PID_KEYHASH, 16); + memcpy (p, serdata->v.keyhash.m_hash, 16); + } +} + +void nn_xmsg_addpar_guid (struct nn_xmsg *m, unsigned pid, const nn_guid_t *guid) +{ + unsigned *pu; + int i; + pu = nn_xmsg_addpar (m, pid, 16); + for (i = 0; i < 3; i++) + { + pu[i] = toBE4u (guid->prefix.u[i]); + } + pu[i] = toBE4u (guid->entityid.u); +} + +void nn_xmsg_addpar_reliability (struct nn_xmsg *m, unsigned pid, const struct nn_reliability_qospolicy *rq) +{ + struct nn_external_reliability_qospolicy *p; + p = nn_xmsg_addpar (m, pid, sizeof (*p)); + if (NN_PEDANTIC_P) + { + switch (rq->kind) + { + case NN_BEST_EFFORT_RELIABILITY_QOS: + p->kind = NN_PEDANTIC_BEST_EFFORT_RELIABILITY_QOS; + break; + case NN_RELIABLE_RELIABILITY_QOS: + p->kind = NN_PEDANTIC_RELIABLE_RELIABILITY_QOS; + break; + default: + assert (0); + } + } + else + { + switch (rq->kind) + { + case NN_BEST_EFFORT_RELIABILITY_QOS: + p->kind = NN_INTEROP_BEST_EFFORT_RELIABILITY_QOS; + break; + case NN_RELIABLE_RELIABILITY_QOS: + p->kind = NN_INTEROP_RELIABLE_RELIABILITY_QOS; + break; + default: + assert (0); + } + } + p->max_blocking_time = rq->max_blocking_time; +} + +void nn_xmsg_addpar_4u (struct nn_xmsg *m, unsigned pid, unsigned x) +{ + unsigned *p = nn_xmsg_addpar (m, pid, 4); + *p = x; +} + +void nn_xmsg_addpar_BE4u (struct nn_xmsg *m, unsigned pid, unsigned x) +{ + unsigned *p = nn_xmsg_addpar (m, pid, 4); + *p = toBE4u (x); +} + +void nn_xmsg_addpar_statusinfo (struct nn_xmsg *m, unsigned statusinfo) +{ + if ((statusinfo & ~NN_STATUSINFO_STANDARDIZED) == 0) + nn_xmsg_addpar_BE4u (m, PID_STATUSINFO, statusinfo); + else + { + unsigned *p = nn_xmsg_addpar (m, PID_STATUSINFO, 8); + unsigned statusinfox = 0; + assert ((statusinfo & ~NN_STATUSINFO_STANDARDIZED) == NN_STATUSINFO_OSPL_AUTO); + if (statusinfo & NN_STATUSINFO_OSPL_AUTO) + statusinfox |= NN_STATUSINFOX_OSPL_AUTO; + p[0] = toBE4u (statusinfo & NN_STATUSINFO_STANDARDIZED); + p[1] = toBE4u (statusinfox); + } +} + + +void nn_xmsg_addpar_share (struct nn_xmsg *m, unsigned pid, const struct nn_share_qospolicy *q) +{ + /* Written thus to allow q->name to be a null pointer if enable = false */ + const unsigned fixed_len = 4 + 4; + const unsigned len = (q->enable ? (unsigned) strlen (q->name) : 0) + 1; + unsigned char *p; + struct cdrstring *ps; + p = nn_xmsg_addpar (m, pid, fixed_len + len); + p[0] = q->enable; + p[1] = 0; + p[2] = 0; + p[3] = 0; + ps = (struct cdrstring *) (p + 4); + ps->length = len; + if (q->enable) + memcpy (ps->contents, q->name, len); + else + ps->contents[0] = 0; +} + +void nn_xmsg_addpar_subscription_keys (struct nn_xmsg *m, unsigned pid, const struct nn_subscription_keys_qospolicy *q) +{ + unsigned char *tmp; + unsigned i, len = 8; /* use_key_list, length of key_list */ + + for (i = 0; i < q->key_list.n; i++) + { + unsigned len1 = (unsigned) strlen (q->key_list.strs[i]) + 1; + len += 4 + align4u (len1); + } + + tmp = nn_xmsg_addpar (m, pid, len); + + tmp[0] = q->use_key_list; + for (i = 1; i < sizeof (int); i++) + { + tmp[i] = 0; + } + tmp += sizeof (int); + *((unsigned *) tmp) = q->key_list.n; + tmp += sizeof (unsigned); + for (i = 0; i < q->key_list.n; i++) + { + struct cdrstring *p = (struct cdrstring *) tmp; + unsigned len1 = (unsigned) strlen (q->key_list.strs[i]) + 1; + p->length = len1; + memcpy (p->contents, q->key_list.strs[i], len1); + if (len1 < align4u (len1)) + memset (p->contents + len1, 0, align4u (len1) - len1); + tmp += 4 + align4u (len1); + } +} + +void nn_xmsg_addpar_sentinel (struct nn_xmsg * m) +{ + nn_xmsg_addpar (m, PID_SENTINEL, 0); +} + +int nn_xmsg_addpar_sentinel_ifparam (struct nn_xmsg * m) +{ + if (m->have_params) + { + nn_xmsg_addpar_sentinel (m); + return 1; + } + return 0; +} + +void nn_xmsg_addpar_parvinfo (struct nn_xmsg *m, unsigned pid, const struct nn_prismtech_participant_version_info *pvi) +{ + int i; + unsigned slen; + unsigned *pu; + struct cdrstring *ps; + + /* pvi->internals cannot be NULL here */ + slen = (unsigned) strlen(pvi->internals) + 1; /* +1 for '\0' terminator */ + pu = nn_xmsg_addpar (m, pid, NN_PRISMTECH_PARTICIPANT_VERSION_INFO_FIXED_CDRSIZE + slen); + pu[0] = pvi->version; + pu[1] = pvi->flags; + for (i = 0; i < 3; i++) + { + pu[i+2] = (pvi->unused[i]); + } + ps = (struct cdrstring *)&pu[5]; + ps->length = slen; + memcpy(ps->contents, pvi->internals, slen); +} + +void nn_xmsg_addpar_eotinfo (struct nn_xmsg *m, unsigned pid, const struct nn_prismtech_eotinfo *txnid) +{ + uint32_t *pu, i; + pu = nn_xmsg_addpar (m, pid, 2 * sizeof (uint32_t) + txnid->n * sizeof (txnid->tids[0])); + pu[0] = txnid->transactionId; + pu[1] = txnid->n; + for (i = 0; i < txnid->n; i++) + { + pu[2*i + 2] = toBE4u (txnid->tids[i].writer_entityid.u); + pu[2*i + 3] = txnid->tids[i].transactionId; + } +} + +void nn_xmsg_addpar_dataholder (_In_ struct nn_xmsg *m, _In_ unsigned pid, _In_ const struct nn_dataholder *dh) +{ + unsigned char *tmp; + unsigned len; + + /* Get total payload length. */ + len = nn_xmsg_add_dataholder_padded(NULL, dh); + + /* Prepare parameter header and get payload pointer. */ + tmp = nn_xmsg_addpar (m, pid, 4 + len); + + /* Insert dataholder. */ + nn_xmsg_add_dataholder_padded(tmp, dh); +} + +/* XMSG_CHAIN ---------------------------------------------------------- + + Xpacks refer to xmsgs and need to release these after having been + sent. For that purpose, we have a chain of xmsgs in an xpack. + + Chain elements are embedded in the xmsg, so instead of loading a + pointer we compute the address of the xmsg from the address of the + chain element, &c. */ + +static void nn_xmsg_chain_release (struct nn_xmsg_chain *chain) +{ + nn_guid_t wrguid; + memset (&wrguid, 0, sizeof (wrguid)); + + while (chain->latest) + { + struct nn_xmsg_chain_elem *ce = chain->latest; + struct nn_xmsg *m = (struct nn_xmsg *) ((char *) ce - offsetof (struct nn_xmsg, link)); + chain->latest = ce->older; + + /* If this xmsg was written by a writer different from wrguid, + update wr->xmit_seq. There isn't necessarily a writer, and + for fragmented data, only the last one must be updated, which + we do by not setting the writer+seq for those xmsgs. + + These are all local writers, and are guaranteed to have the + same, non-zero, systemId <=> wrguid.u[0]. + + They are in reverse order, so we only attempt an update if this + xmsg was produced by a writer different from the last one we + processed. */ + if (m->kind == NN_XMSG_KIND_DATA && m->kindspecific.data.wrguid.prefix.u[0]) + { + if (wrguid.prefix.u[1] != m->kindspecific.data.wrguid.prefix.u[1] || + wrguid.prefix.u[2] != m->kindspecific.data.wrguid.prefix.u[2] || + wrguid.entityid.u != m->kindspecific.data.wrguid.entityid.u) + { + struct writer *wr; + assert (m->kindspecific.data.wrseq != 0); + wrguid = m->kindspecific.data.wrguid; + if ((wr = ephash_lookup_writer_guid (&m->kindspecific.data.wrguid)) != NULL) + UPDATE_SEQ_XMIT_UNLOCKED(wr, m->kindspecific.data.wrseq); + } + } + + nn_xmsg_free (m); + } +} + +static void nn_xmsg_chain_add (struct nn_xmsg_chain *chain, struct nn_xmsg *m) +{ + m->link.older = chain->latest; + chain->latest = &m->link; +} + +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING +/* BW_LIMITER ---------------------------------------------------------- + + Helper for XPACKS, that contain the configuration and state to handle Bandwidth limitation.*/ + +/* To be called after Xpack sends out a packet. + * Keeps a balance of real data vs. allowed data according to the bandwidth limit. + * If data is send too fast, a sleep is inserted to get the used bandwidth at the configured rate. + */ + +#define NN_BW_LIMIT_MAX_BUFFER (-30 * T_MILLISECOND) +#define NN_BW_LIMIT_MIN_SLEEP (2 * T_MILLISECOND) +static void nn_bw_limit_sleep_if_needed(struct nn_bw_limiter* this, ssize_t size) +{ + if ( this->bandwidth > 0 ) { + nn_mtime_t tnow = now_mt(); + int64_t actual_interval; + int64_t target_interval; + + /* calculate intervals */ + actual_interval = tnow.v - this->last_update.v; + this->last_update = tnow; + + target_interval = T_SECOND*size/this->bandwidth; + + this->balance += (target_interval - actual_interval); + + + TRACE ((" balance < NN_BW_LIMIT_MAX_BUFFER ) + { + /* We're below the bandwidth limit, do not further accumulate */ + this->balance = NN_BW_LIMIT_MAX_BUFFER; + TRACE ((":%"PRId64":max",this->balance/1000)); + } + else if ( this->balance > NN_BW_LIMIT_MIN_SLEEP ) + { + /* We're over the bandwidth limit far enough, to warrent a sleep. */ + os_time delay; + TRACE ((":%"PRId64":sleep",this->balance/1000)); + delay.tv_sec = (int32_t) (this->balance / T_SECOND); + delay.tv_nsec = (int32_t) (this->balance % T_SECOND); + thread_state_blocked (lookup_thread_state ()); + os_nanoSleep (delay); + thread_state_unblocked (lookup_thread_state ()); + } + else + { + TRACE ((":%"PRId64"",this->balance/1000)); + } + TRACE ((">")); + } +} + + +static void nn_bw_limit_init (struct nn_bw_limiter *limiter, uint32_t bandwidth_limit) +{ + limiter->bandwidth = bandwidth_limit; + limiter->balance = 0; + if (bandwidth_limit) + limiter->last_update = now_mt (); + else + limiter->last_update.v = 0; +} +#endif /* DDSI_INCLUDE_BANDWIDTH_LIMITING */ + +/* XPACK --------------------------------------------------------------- + + Queued messages are packed into xpacks (all by-ref, using iovecs). + The xpack is sent to the union of all address sets provided in the + message added to the xpack. */ + +static void nn_xpack_reinit (struct nn_xpack *xp) +{ + xp->dstmode = NN_XMSG_DST_UNSET; + xp->niov = 0; + xp->call_flags = 0; + xp->msg_len.length = 0; + xp->included_msgs.latest = NULL; + xp->maxdelay = T_NEVER; +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + xp->encoderId = 0; +#endif + xp->packetid++; +} + +struct nn_xpack * nn_xpack_new (ddsi_tran_conn_t conn, uint32_t bw_limit, bool async_mode) +{ + const nn_vendorid_t myvendorid = MY_VENDOR_ID; + struct nn_xpack *xp; + + xp = os_malloc (sizeof (*xp)); + memset (xp, 0, sizeof (*xp)); + xp->async_mode = async_mode; + + /* Fixed header fields, initialized just once */ + xp->hdr.protocol.id[0] = 'R'; + xp->hdr.protocol.id[1] = 'T'; + xp->hdr.protocol.id[2] = 'P'; + xp->hdr.protocol.id[3] = 'S'; + xp->hdr.version.major = RTPS_MAJOR; + xp->hdr.version.minor = RTPS_MINOR; + xp->hdr.vendorid = myvendorid; + + /* MSG_LEN first sub message for stream based connections */ + + xp->msg_len.smhdr.submessageId = SMID_PT_MSG_LEN; + xp->msg_len.smhdr.flags = (PLATFORM_IS_LITTLE_ENDIAN ? SMFLAG_ENDIANNESS : 0); + xp->msg_len.smhdr.octetsToNextHeader = 4; + + xp->conn = conn; + nn_xpack_reinit (xp); + + if (gv.thread_pool) + os_sem_init (&xp->sem, 0); + +#ifdef DDSI_INCLUDE_ENCRYPTION + if (q_security_plugin.new_encoder) + { + xp->codec = (q_security_plugin.new_encoder) (); + xp->SecurityHeader.smhdr.submessageId = SMID_PT_INFO_CONTAINER; + xp->SecurityHeader.smhdr.flags = PLATFORM_IS_LITTLE_ENDIAN ? SMFLAG_ENDIANNESS : 0;; + xp->SecurityHeader.smhdr.octetsToNextHeader = 4; + xp->SecurityHeader.id = PTINFO_ID_ENCRYPT; + } +#endif +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING + nn_bw_limit_init (&xp->limiter, bw_limit); +#else + (void) bw_limit; +#endif + return xp; +} + +void nn_xpack_free (struct nn_xpack *xp) +{ + assert (xp->niov == 0); + assert (xp->included_msgs.latest == NULL); +#ifdef DDSI_INCLUDE_ENCRYPTION + if (q_security_plugin.free_encoder) + { + (q_security_plugin.free_encoder) (xp->codec); + } +#endif + if (gv.thread_pool) + os_sem_destroy (&xp->sem); + os_free (xp); +} + + +static socklen_t sockaddr_size (const os_sockaddr_storage *a) +{ + switch (a->ss_family) + { + case AF_INET: return sizeof (os_sockaddr_in); +#if OS_SOCKET_HAS_IPV6 + case AF_INET6: return sizeof (os_sockaddr_in6); +#endif + default: assert (0); return 0; + } +} + +/* Turns out Darwin uses "int" for msg_iovlen, but glibc uses "size_t". The simplest + way out is to do the assignment with the conversion warnings disabled */ +OS_WARNING_GNUC_OFF(conversion) +static void set_msghdr_iov (struct msghdr *mhdr, struct iovec *iov, size_t iovlen) +{ + mhdr->msg_iov = iov; + mhdr->msg_iovlen = iovlen; +} +OS_WARNING_GNUC_ON(conversion) + +static ssize_t nn_xpack_send1 (const nn_locator_t *loc, void * varg) +{ + struct iovec iov[NN_XMSG_MAX_MESSAGE_IOVECS]; + struct nn_xpack * xp = varg; + struct msghdr mhdr; + ssize_t nbytes = 0; + os_sockaddr_storage addr; + + nn_loc_to_address(&addr, loc); + if (config.enabled_logcats & LC_TRACE) + { + char buf[INET6_ADDRSTRLEN_EXTENDED]; + TRACE ((" %s", sockaddr_to_string_with_port (buf, &addr))); + } + + if (config.xmit_lossiness > 0) + { + /* We drop APPROXIMATELY a fraction of xmit_lossiness * 10**(-3) + of all packets to be sent */ + if ((random () % 1000) < config.xmit_lossiness) + { + TRACE (("(dropped)")); + xp->call_flags = 0; + return 0; + } + } + + /* Set target data/address in message */ + + memcpy (iov, xp->iov, sizeof (iov)); + memset (&mhdr, 0, sizeof (mhdr)); + set_msghdr_iov (&mhdr, iov, xp->niov); + mhdr.msg_name = &addr; + mhdr.msg_namelen = sockaddr_size (&addr); + +#ifdef DDSI_INCLUDE_ENCRYPTION + if (q_security_plugin.send_encoded && xp->encoderId != 0 && (q_security_plugin.encoder_type) (xp->codec, xp->encoderId) != Q_CIPHER_NONE) + { + nbytes = (q_security_plugin.send_encoded) (xp->conn, &mhdr, &xp->codec, xp->encoderId, xp->call_flags); + } + else +#endif + { + if (!gv.mute) + nbytes = ddsi_conn_write (xp->conn, &mhdr, xp->msg_len.length, xp->call_flags); + else + { + TRACE (("(dropped)")); + nbytes = (ssize_t) xp->msg_len.length; + } + } + + /* Clear call flags, as used on a per call basis */ + + xp->call_flags = 0; + +#ifdef DDSI_INCLUDE_BANDWIDTH_LIMITING + if (nbytes > 0) + { + nn_bw_limit_sleep_if_needed (&xp->limiter, nbytes); + } +#endif + + return nbytes; +} + +static void nn_xpack_send1v (const nn_locator_t *loc, void * varg) +{ + (void) nn_xpack_send1 (loc, varg); +} + +typedef struct nn_xpack_send1_thread_arg { + const nn_locator_t *loc; + struct nn_xpack *xp; +} *nn_xpack_send1_thread_arg_t; + +static void nn_xpack_send1_thread (void * varg) +{ + nn_xpack_send1_thread_arg_t arg = varg; + (void) nn_xpack_send1 (arg->loc, arg->xp); + if (os_atomic_dec32_ov (&arg->xp->calls) == 1) + { + os_sem_post (&arg->xp->sem); + } + os_free (varg); +} + +static void nn_xpack_send1_threaded (const nn_locator_t *loc, void * varg) +{ + nn_xpack_send1_thread_arg_t arg = os_malloc (sizeof (*arg)); + arg->xp = (struct nn_xpack *) varg; + arg->loc = loc; + os_atomic_inc32 (&arg->xp->calls); + ut_thread_pool_submit (gv.thread_pool, nn_xpack_send1_thread, arg); +} + +static void nn_xpack_send_real (struct nn_xpack * xp) +{ + size_t calls; + + assert (xp->niov <= NN_XMSG_MAX_MESSAGE_IOVECS); + + if (xp->niov == 0) + { + return; + } + + assert (xp->dstmode != NN_XMSG_DST_UNSET); + + if (config.enabled_logcats & LC_TRACE) + { + int i; + TRACE (("nn_xpack_send %u:", xp->msg_len.length)); + for (i = 0; i < (int) xp->niov; i++) + { + TRACE ((" %p:%lu", (void *) xp->iov[i].iov_base, (unsigned long) xp->iov[i].iov_len)); + } + } + + TRACE ((" [")); + if (xp->dstmode == NN_XMSG_DST_ONE) + { + calls = 1; + (void) nn_xpack_send1 (&xp->dstaddr.loc, xp); + } + else + { + /* Send to all addresses in as - as ultimately references the writer's + address set, which is currently replaced rather than changed whenever + it is updated, but that might not be something we want to guarantee */ + calls = 0; + if (xp->dstaddr.all.as) + { + if (gv.thread_pool == NULL) + { + calls = addrset_forall_count (xp->dstaddr.all.as, nn_xpack_send1v, xp); + } + else + { + os_atomic_st32 (&xp->calls, 1); + calls = addrset_forall_count (xp->dstaddr.all.as, nn_xpack_send1_threaded, xp); + /* Wait for the thread pool to complete the write; if we're the one + decrementing "calls" to 0, all of the work has been completed and + none of the threads will be posting; else some thread will be + posting it and we had better wait for it */ + if (os_atomic_dec32_ov (&xp->calls) != 1) + os_sem_wait (&xp->sem); + } + unref_addrset (xp->dstaddr.all.as); + } + + /* Send to at most one address in as_group */ + + if (xp->dstaddr.all.as_group) + { + if (addrset_forone (xp->dstaddr.all.as_group, nn_xpack_send1, xp) == 0) + { + calls++; + } + unref_addrset (xp->dstaddr.all.as_group); + } + } + TRACE ((" ]\n")); + if (calls) + { + nn_log (LC_TRAFFIC, "traffic-xmit (%lu) %u\n", (unsigned long) calls, xp->msg_len.length); + } + nn_xmsg_chain_release (&xp->included_msgs); + nn_xpack_reinit (xp); +} + +#define SENDQ_MAX 200 +#define SENDQ_HW 10 +#define SENDQ_LW 0 + +static uint32_t nn_xpack_sendq_thread (UNUSED_ARG (void *arg)) +{ + os_mutexLock (&gv.sendq_lock); + while (!(gv.sendq_stop && gv.sendq_head == NULL)) + { + struct nn_xpack *xp; + if ((xp = gv.sendq_head) == NULL) + { + os_time to = { 0, 1000000 }; + os_condTimedWait (&gv.sendq_cond, &gv.sendq_lock, &to); + } + else + { + gv.sendq_head = xp->sendq_next; + if (--gv.sendq_length == SENDQ_LW) + os_condBroadcast (&gv.sendq_cond); + os_mutexUnlock (&gv.sendq_lock); + nn_xpack_send_real (xp); + nn_xpack_free (xp); + os_mutexLock (&gv.sendq_lock); + } + } + os_mutexUnlock (&gv.sendq_lock); + return 0; +} + +void nn_xpack_sendq_init (void) +{ + gv.sendq_stop = 0; + gv.sendq_head = NULL; + gv.sendq_tail = NULL; + gv.sendq_length = 0; + os_mutexInit (&gv.sendq_lock); + os_condInit (&gv.sendq_cond, &gv.sendq_lock); +} + +void nn_xpack_sendq_start (void) +{ + gv.sendq_ts = create_thread("sendq", nn_xpack_sendq_thread, NULL); +} + +void nn_xpack_sendq_stop (void) +{ + os_mutexLock (&gv.sendq_lock); + gv.sendq_stop = 1; + os_condBroadcast (&gv.sendq_cond); + os_mutexUnlock (&gv.sendq_lock); +} + +void nn_xpack_sendq_fini (void) +{ + assert (gv.sendq_head == NULL); + join_thread(gv.sendq_ts); + os_condDestroy(&gv.sendq_cond); + os_mutexDestroy(&gv.sendq_lock); +} + +void nn_xpack_send (struct nn_xpack *xp, bool immediately) +{ + if (!xp->async_mode) + { + nn_xpack_send_real (xp); + } + else + { + struct nn_xpack *xp1 = os_malloc (sizeof (*xp)); + memcpy (xp1, xp, sizeof (*xp1)); + nn_xpack_reinit (xp); + xp1->sendq_next = NULL; + os_mutexLock (&gv.sendq_lock); + if (immediately || gv.sendq_length == SENDQ_HW) + os_condBroadcast (&gv.sendq_cond); + if (gv.sendq_length >= SENDQ_MAX) + { + while (gv.sendq_length > SENDQ_LW) + os_condWait (&gv.sendq_cond, &gv.sendq_lock); + } + if (gv.sendq_head) + gv.sendq_tail->sendq_next = xp1; + else + { + gv.sendq_head = xp1; + } + gv.sendq_tail = xp1; + gv.sendq_length++; + os_mutexUnlock (&gv.sendq_lock); + } +} + +static void copy_addressing_info (struct nn_xpack *xp, const struct nn_xmsg *m) +{ + xp->dstmode = m->dstmode; + switch (m->dstmode) + { + case NN_XMSG_DST_UNSET: + assert (0); + break; + case NN_XMSG_DST_ONE: + xp->dstaddr.loc = m->dstaddr.one.loc; + break; + case NN_XMSG_DST_ALL: + xp->dstaddr.all.as = ref_addrset (m->dstaddr.all.as); + xp->dstaddr.all.as_group = ref_addrset (m->dstaddr.all.as_group); + break; + } +} + +static int addressing_info_eq_onesidederr (const struct nn_xpack *xp, const struct nn_xmsg *m) +{ + if (xp->dstmode != m->dstmode) + return 0; + switch (xp->dstmode) + { + case NN_XMSG_DST_UNSET: + assert (0); + case NN_XMSG_DST_ONE: + return (memcmp (&xp->dstaddr.loc, &m->dstaddr.one.loc, sizeof (xp->dstaddr.loc)) == 0); + case NN_XMSG_DST_ALL: + return (addrset_eq_onesidederr (xp->dstaddr.all.as, m->dstaddr.all.as) && + addrset_eq_onesidederr (xp->dstaddr.all.as_group, m->dstaddr.all.as_group)); + } + assert (0); + return 0; +} + +static int nn_xpack_mayaddmsg (const struct nn_xpack *xp, const struct nn_xmsg *m, const uint32_t flags) +{ + unsigned max_msg_size = config.max_msg_size; + unsigned payload_size; + + if (xp->niov == 0) + return 1; + assert (xp->included_msgs.latest != NULL); + if (xp->niov + NN_XMSG_MAX_SUBMESSAGE_IOVECS > NN_XMSG_MAX_MESSAGE_IOVECS) + return 0; + + payload_size = m->refd_payload ? (unsigned) m->refd_payload_iov.iov_len : 0; + +#ifdef DDSI_INCLUDE_ENCRYPTION + if (xp->encoderId) + { + unsigned security_header; + security_header = (q_security_plugin.header_size) (xp->codec, xp->encoderId); + assert (security_header < max_msg_size); + max_msg_size -= security_header; + } +#endif + + /* Check if max message size exceeded */ + + if (xp->msg_len.length + m->sz + payload_size > max_msg_size) + { + return 0; + } + + /* Check if different call semantics */ + + if (xp->call_flags != flags) + { + return 0; + } + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + /* Don't mix up xmsg for different encoders */ + if (xp->encoderId != m->encoderid) + return 0; +#endif + + return addressing_info_eq_onesidederr (xp, m); +} + +static int guid_prefix_eq (const nn_guid_prefix_t *a, const nn_guid_prefix_t *b) +{ + return a->u[0] == b->u[0] && a->u[1] == b->u[1] && a->u[2] == b->u[2]; +} + +int nn_xpack_addmsg (struct nn_xpack *xp, struct nn_xmsg *m, const uint32_t flags) +{ + /* Returns > 0 if pack got sent out before adding m */ + + static InfoDST_t static_zero_dst = { + { SMID_INFO_DST, (PLATFORM_IS_LITTLE_ENDIAN ? SMFLAG_ENDIANNESS : 0), sizeof (nn_guid_prefix_t) }, + { { 0,0,0,0, 0,0,0,0, 0,0,0,0 } } + }; + InfoDST_t *dst; + size_t niov; + size_t sz; + int result = 0; + size_t xpo_niov = 0; + uint32_t xpo_sz = 0; + + assert (m->kind != NN_XMSG_KIND_DATA_REXMIT || m->kindspecific.data.readerId_off != 0); + + assert (m->sz > 0); + assert (m->dstmode != NN_XMSG_DST_UNSET); + + /* Submessage offset must be a multiple of 4 to meet alignment + requirement (DDSI 2.1, 9.4.1). If we keep everything 4-byte + aligned all the time, we don't need to check for padding here. */ + assert ((xp->msg_len.length % 4) == 0); + assert ((m->sz % 4) == 0); + assert (m->refd_payload == NULL || (m->refd_payload_iov.iov_len % 4) == 0); + + if (!nn_xpack_mayaddmsg (xp, m, flags)) + { + assert (xp->niov > 0); + nn_xpack_send (xp, false); + assert (nn_xpack_mayaddmsg (xp, m, flags)); + result = 1; + } + + niov = xp->niov; + sz = xp->msg_len.length; + + /* We try to merge iovecs, but we can never merge across messages + because of all the headers. So we can speculatively start adding + the submessage to the pack, and if we can't transmit and restart. + But do make sure we can't run out of iovecs. */ + assert (niov + NN_XMSG_MAX_SUBMESSAGE_IOVECS <= NN_XMSG_MAX_MESSAGE_IOVECS); + + TRACE (("xpack_addmsg %p %p %u(", (void *) xp, (void *) m, flags)); + switch (m->kind) + { + case NN_XMSG_KIND_CONTROL: + TRACE (("control")); + break; + case NN_XMSG_KIND_DATA: + case NN_XMSG_KIND_DATA_REXMIT: + TRACE (("%s(%x:%x:%x:%x:#%"PRId64"/%u)", + (m->kind == NN_XMSG_KIND_DATA) ? "data" : "rexmit", + PGUID (m->kindspecific.data.wrguid), + m->kindspecific.data.wrseq, + m->kindspecific.data.wrfragid + 1)); + break; + } + TRACE (("): niov %d sz %"PRIuSIZE, (int) niov, sz)); + + /* If a fresh xp has been provided, add an RTPS header */ + + if (niov == 0) + { + copy_addressing_info (xp, m); + xp->hdr.guid_prefix = m->data->src.guid_prefix; + xp->iov[niov].iov_base = (void*) &xp->hdr; + xp->iov[niov].iov_len = sizeof (xp->hdr); + sz = xp->iov[niov].iov_len; + niov++; + + /* Add MSG_LEN sub message for stream based transports */ + + if (xp->conn->m_stream) + { + xp->iov[niov].iov_base = (void*) &xp->msg_len; + xp->iov[niov].iov_len = sizeof (xp->msg_len); + sz += sizeof (xp->msg_len); + niov++; + } + +#ifdef DDSI_INCLUDE_NETWORK_PARTITIONS + xp->encoderId = m->encoderid; +#endif +#ifdef DDSI_INCLUDE_ENCRYPTION + if (xp->encoderId > 0 && (q_security_plugin.encoder_type) (xp->codec, xp->encoderId) != Q_CIPHER_NONE) + { + /* Insert a reference to the security header + the correct size will be set upon encryption in q_xpack_sendmsg_encoded */ + xp->iov[niov].iov_base = (void*) &xp->SecurityHeader; + xp->iov[niov].iov_len = sizeof (xp->SecurityHeader); + sz += xp->iov[niov].iov_len; + niov++; + } +#endif + xp->last_src = &xp->hdr.guid_prefix; + xp->last_dst = NULL; + } + else + { + xpo_niov = xp->niov; + xpo_sz = xp->msg_len.length; + if (!guid_prefix_eq (xp->last_src, &m->data->src.guid_prefix)) + { + /* If m's source participant differs from that of the source + currently set in the packed message, add an InfoSRC note. */ + xp->iov[niov].iov_base = (void*) &m->data->src; + xp->iov[niov].iov_len = sizeof (m->data->src); + sz += sizeof (m->data->src); + xp->last_src = &m->data->src.guid_prefix; + niov++; + } + } + + /* We try to merge iovecs by checking iov[niov-1]. We used to check + addressing_info_eq_onesidederr here (again), but can't because it + relies on an imprecise check that may (timing-dependent) return + false incorrectly */ + assert (niov >= 1); + + /* Adding this message may shorten the time this xpack may linger */ + if (m->maxdelay < xp->maxdelay) + xp->maxdelay = m->maxdelay; + + /* If m's dst differs from that of the dst currently set in the + packed message, add an InfoDST note. Note that neither has to + have a dst set. */ + if (xp->last_dst == NULL) + dst = (m->dstmode == NN_XMSG_DST_ONE) ? &m->data->dst : NULL; + else if (m->dstmode != NN_XMSG_DST_ONE) + dst = &static_zero_dst; + else + dst = guid_prefix_eq (&xp->last_dst->guid_prefix, &m->data->dst.guid_prefix) ? NULL : &m->data->dst; + + if (dst) + { + /* Try to merge iovecs, a few large ones should be more efficient + than many small ones */ + if ((char *) xp->iov[niov-1].iov_base + xp->iov[niov-1].iov_len == (char *) dst) + { + xp->iov[niov-1].iov_len += sizeof (*dst); + } + else + { + xp->iov[niov].iov_base = (void*) dst; + xp->iov[niov].iov_len = sizeof (*dst); + niov++; + } + sz += sizeof (*dst); + xp->last_dst = dst; + } + + /* Append submessage; can possibly be merged with preceding iovec */ + if ((char *) xp->iov[niov-1].iov_base + xp->iov[niov-1].iov_len == (char *) m->data->payload) + xp->iov[niov-1].iov_len += m->sz; + else + { + xp->iov[niov].iov_base = m->data->payload; + xp->iov[niov].iov_len = m->sz; + niov++; + } + sz += m->sz; + + /* Append ref'd payload if given; whoever constructed the message + should've taken care of proper alignment for the payload. The + ref'd payload is always at some weird address, so no chance of + merging iovecs here. */ + if (m->refd_payload) + { + xp->iov[niov] = m->refd_payload_iov; + sz += m->refd_payload_iov.iov_len; + niov++; + } + + /* Shouldn't've overrun iov, and shouldn't've tried to add a + submessage that is too large for a message ... but the latter + isn't worth checking. */ + assert (niov <= NN_XMSG_MAX_MESSAGE_IOVECS); + + /* Set total message length in MSG_LEN sub message */ + assert((uint32_t)sz == sz); + xp->msg_len.length = (uint32_t) sz; + xp->niov = niov; + + if (xpo_niov > 0 && sz > config.max_msg_size) + { + TRACE ((" => now niov %d sz %"PRIuSIZE" > max_msg_size %u, nn_xpack_send niov %d sz %u now\n", (int) niov, sz, config.max_msg_size, (int) xpo_niov, xpo_sz)); + xp->msg_len.length = xpo_sz; + xp->niov = xpo_niov; + nn_xpack_send (xp, false); + result = nn_xpack_addmsg (xp, m, flags); /* Retry on emptied xp */ + } + else + { + xp->call_flags = flags; + nn_xmsg_chain_add (&xp->included_msgs, m); + TRACE ((" => now niov %d sz %"PRIuSIZE"\n", (int) niov, sz)); + } + + return result; +} + +int64_t nn_xpack_maxdelay (const struct nn_xpack *xp) +{ + return xp->maxdelay; +} + +unsigned nn_xpack_packetid (const struct nn_xpack *xp) +{ + return xp->packetid; +} diff --git a/src/core/ddsi/src/sysdeps.c b/src/core/ddsi/src/sysdeps.c new file mode 100644 index 0000000..636db60 --- /dev/null +++ b/src/core/ddsi/src/sysdeps.c @@ -0,0 +1,355 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +#include +#include + +#include "os/os.h" + +#include "ddsi/q_error.h" +#include "ddsi/q_log.h" +#include "ddsi/q_config.h" +#include "ddsi/sysdeps.h" + +#ifdef NEED_ARM_MEMBAR_SUPPORT +static void q_membar_autodecide (void); +void (*q_maybe_membar) (void) = q_membar_autodecide; + +static void q_membar_nop (void) { } +static void q_membar_dmb (void) { MemoryBarrierARM; } + +static void q_membar_autodecide (void) +{ + SYSTEM_INFO sysinfo; + GetSystemInfo (&sysinfo); + assert (sysinfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_ARM); + if (sysinfo.wProcessorLevel >= 7) + { + q_maybe_membar = q_membar_dmb; + } + else + { + q_maybe_membar = q_membar_nop; + } + q_maybe_membar (); +} +#endif + +/* MISSING IN OS ABSTRACTION LAYER ------------------------------------- */ + +#if ! SYSDEPS_HAVE_RECVMSG +ssize_t recvmsg (os_handle fd, struct msghdr *message, int flags) +{ + ssize_t ret; + assert (message->msg_iovlen == 1); +#if SYSDEPS_MSGHDR_ACCRIGHTS + assert (message->msg_accrightslen == 0); +#else + assert (message->msg_controllen == 0); +#endif +#if SYSDEPS_MSGHDR_FLAGS + message->msg_flags = 0; +#endif + ret = recvfrom (fd, message->msg_iov[0].iov_base, (int)message->msg_iov[0].iov_len, flags, + message->msg_name, &message->msg_namelen); /* To fix the warning of conversion from 'size_t' to 'int', which may cause possible loss of data, type casting is done*/ +#if defined (_WIN32) + /* Windows returns an error for too-large messages, Unix expects + original size and the MSG_TRUNC flag. MSDN says it is truncated, + which presumably means it returned as much of the message as it + could - so we return that the message was 1 byte larger than the + available space, and set MSG_TRUNC if we can. */ + if (ret == -1 && os_getErrno () == WSAEMSGSIZE) { + ret = message->msg_iov[0].iov_len + 1; +#if SYSDEPS_MSGHDR_FLAGS + message->msg_flags |= MSG_TRUNC; +#endif + } +#endif + return ret; +} +#endif + +#if ! SYSDEPS_HAVE_SENDMSG +#if !(defined _WIN32 && !defined WINCE) +ssize_t sendmsg (os_handle fd, const struct msghdr *message, int flags) +{ + char stbuf[3072], *buf; + ssize_t sz, ret; + size_t sent = 0; + unsigned i; + +#if SYSDEPS_MSGHDR_ACCRIGHTS + assert (message->msg_accrightslen == 0); +#else + assert (message->msg_controllen == 0); +#endif + + if (message->msg_iovlen == 1) + { + /* if only one fragment, skip all copying */ + buf = message->msg_iov[0].iov_base; + sz = message->msg_iov[0].iov_len; + } + else + { + /* first determine the size of the message, then select the + on-stack buffer or allocate one on the heap ... */ + sz = 0; + for (i = 0; i < message->msg_iovlen; i++) + { + sz += message->msg_iov[i].iov_len; + } + if (sz <= sizeof (stbuf)) + { + buf = stbuf; + } + else + { + buf = os_malloc (sz); + } + /* ... then copy data into buffer */ + sz = 0; + for (i = 0; i < message->msg_iovlen; i++) + { + memcpy (buf + sz, message->msg_iov[i].iov_base, message->msg_iov[i].iov_len); + sz += message->msg_iov[i].iov_len; + } + } + + while (TRUE) + { + ret = sendto (fd, buf + sent, sz - sent, flags, message->msg_name, message->msg_namelen); + if (ret < 0) + { + break; + } + sent += (size_t) ret; + if (sent == sz) + { + ret = sent; + break; + } + } + + if (buf != stbuf) + { + os_free (buf); + } + return ret; +} +#else /* _WIN32 && !WINCE */ +ssize_t sendmsg (os_handle fd, const struct msghdr *message, int flags) +{ + WSABUF stbufs[128], *bufs; + DWORD sent; + unsigned i; + ssize_t ret; + +#if SYSDEPS_MSGHDR_ACCRIGHTS + assert (message->msg_accrightslen == 0); +#else + assert (message->msg_controllen == 0); +#endif + + if (message->msg_iovlen <= (int)(sizeof(stbufs) / sizeof(*stbufs))) + bufs = stbufs; + else + bufs = os_malloc (message->msg_iovlen * sizeof (*bufs)); + for (i = 0; i < message->msg_iovlen; i++) + { + bufs[i].buf = (void *) message->msg_iov[i].iov_base; + bufs[i].len = (unsigned) message->msg_iov[i].iov_len; + } + if (WSASendTo (fd, bufs, (DWORD)message->msg_iovlen, &sent, (DWORD)flags, (SOCKADDR *) message->msg_name, message->msg_namelen, NULL, NULL) == 0) /* Type casting to silence the warning of conversion from 'size_t' to 'DWORD', which may cause possible loss of data */ + ret = (ssize_t) sent; + else + ret = -1; + if (bufs != stbufs) + os_free (bufs); + return ret; +} +#endif +#endif + +#ifndef SYSDEPS_HAVE_RANDOM +long random (void) +{ + /* rand() is a really terribly bad PRNG */ + union { long x; unsigned char c[4]; } t; + int i; + for (i = 0; i < 4; i++) + t.c[i] = (unsigned char) ((rand () >> 4) & 0xff); +#if RAND_MAX == INT32_MAX || RAND_MAX == 0x7fff + t.x &= RAND_MAX; +#elif RAND_MAX <= 0x7ffffffe + t.x %= (RAND_MAX+1); +#else +#error "RAND_MAX out of range" +#endif + return t.x; +} +#endif + +#if SYSDEPS_HAVE_CLOCK_THREAD_CPUTIME +int64_t get_thread_cputime (void) +{ + struct timespec ts; + clock_gettime (CLOCK_THREAD_CPUTIME_ID, &ts); + return ts.tv_sec * (int64_t) 1000000000 + ts.tv_nsec; +} +#else +int64_t get_thread_cputime (void) +{ + return 0; +} +#endif + +#if ! OS_HAVE_THREADEQUAL +int os_threadEqual (os_threadId a, os_threadId b) +{ + /* on pthreads boxes, pthread_equal (a, b); as a workaround: */ + return os_threadIdToInteger (a) == os_threadIdToInteger (b); +} +#endif + +#if defined __sun && __GNUC__ && defined __sparc__ +int __S_exchange_and_add (volatile int *mem, int val) +{ + /* Hopefully cache lines are <= 64 bytes, we then use 8 bytes, 64 + bytes. Should be a lot better than just one lock byte if it gets + used often, but the overhead is a bit larger, so who knows what's + best without trying it out?. We want 3 bits + 6 lsbs zero, so + need to shift addr_hash by 23 bits. */ + static unsigned char locks[8 * 64]; + unsigned addr_hash = 0xe2c7 * (unsigned short) ((os_address) mem >> 2); + unsigned lock_idx = (addr_hash >> 23) & ~0x1c0; + unsigned char * const lock = &locks[lock_idx]; + int result, tmp; + + __asm__ __volatile__("1: ldstub [%1], %0\n\t" + " cmp %0, 0\n\t" + " bne 1b\n\t" + " nop" + : "=&r" (tmp) + : "r" (lock) + : "memory"); + result = *mem; + *mem += val; + __asm__ __volatile__("stb %%g0, [%0]" + : /* no outputs */ + : "r" (lock) + : "memory"); + return result; +} +#endif + +#if !(defined __APPLE__ || defined __linux) || (__GNUC__ > 0 && (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__) < 40100) +void log_stacktrace (const char *name, os_threadId tid) +{ + OS_UNUSED_ARG (name); + OS_UNUSED_ARG (tid); +} +#else +#include +#include + +static os_atomic_uint32_t log_stacktrace_flag = OS_ATOMIC_UINT32_INIT(0); +static struct { + int depth; + void *stk[64]; +} log_stacktrace_stk; + + +static void log_stacktrace_sigh (int sig __attribute__ ((unused))) +{ + int e = os_getErrno(); + log_stacktrace_stk.depth = backtrace (log_stacktrace_stk.stk, (int) (sizeof (log_stacktrace_stk.stk) / sizeof (*log_stacktrace_stk.stk))); + os_atomic_inc32 (&log_stacktrace_flag); + os_setErrno(e); +} + +void log_stacktrace (const char *name, os_threadId tid) +{ + if (config.enabled_logcats == 0) + ; /* no op if nothing logged */ + else if (!config.noprogress_log_stacktraces) + nn_log (~0u, "-- stack trace of %s requested, but traces disabled --\n", name); + else + { + const os_time d = { 0, 1000000 }; + struct sigaction act, oact; + char **strs; + int i; + nn_log (~0u, "-- stack trace of %s requested --\n", name); + act.sa_handler = log_stacktrace_sigh; + act.sa_flags = 0; + sigfillset (&act.sa_mask); + while (!os_atomic_cas32 (&log_stacktrace_flag, 0, 1)) + os_nanoSleep (d); + sigaction (SIGXCPU, &act, &oact); + pthread_kill (tid.v, SIGXCPU); + while (!os_atomic_cas32 (&log_stacktrace_flag, 2, 3)) + os_nanoSleep (d); + sigaction (SIGXCPU, &oact, NULL); + nn_log (~0u, "-- stack trace follows --\n"); + strs = backtrace_symbols (log_stacktrace_stk.stk, log_stacktrace_stk.depth); + for (i = 0; i < log_stacktrace_stk.depth; i++) + nn_log (~0u, "%s\n", strs[i]); + free (strs); + nn_log (~0u, "-- end of stack trace --\n"); + os_atomic_st32 (&log_stacktrace_flag, 0); + } +} +#endif + +#if HAVE_ATOMIC_LIFO +static int os_atomic_casvoidp2 (volatile os_atomic_uintptr2_t *x, uintptr_t a0, uintptr_t b0, uintptr_t a1, uintptr_t b1) +{ + os_atomic_uintptr2_t o, n; + o.s.a = a0; o.s.b = b0; + n.s.a = a1; n.s.b = b1; + return __sync_bool_compare_and_swap(&x->x, o.x, n.x); +} +void os_atomic_lifo_init (os_atomic_lifo_t *head) +{ + head->aba_head.s.a = head->aba_head.s.b = 0; +} +void os_atomic_lifo_push (os_atomic_lifo_t *head, void *elem, size_t linkoff) +{ + uintptr_t a0, b0; + do { + a0 = *((volatile uintptr_t *) &head->aba_head.s.a); + b0 = *((volatile uintptr_t *) &head->aba_head.s.b); + *((volatile uintptr_t *) ((char *) elem + linkoff)) = b0; + } while (!os_atomic_casvoidp2 (&head->aba_head, a0, b0, a0+1, (uintptr_t)elem)); +} +void *os_atomic_lifo_pop (os_atomic_lifo_t *head, size_t linkoff) { + uintptr_t a0, b0, b1; + do { + a0 = *((volatile uintptr_t *) &head->aba_head.s.a); + b0 = *((volatile uintptr_t *) &head->aba_head.s.b); + if (b0 == 0) { + return NULL; + } + b1 = (*((volatile uintptr_t *) ((char *) b0 + linkoff))); + } while (!os_atomic_casvoidp2 (&head->aba_head, a0, b0, a0+1, b1)); + return (void *) b0; +} +void os_atomic_lifo_pushmany (os_atomic_lifo_t *head, void *first, void *last, size_t linkoff) +{ + uintptr_t a0, b0; + do { + a0 = *((volatile uintptr_t *) &head->aba_head.s.a); + b0 = *((volatile uintptr_t *) &head->aba_head.s.b); + *((volatile uintptr_t *) ((char *) last + linkoff)) = b0; + } while (!os_atomic_casvoidp2 (&head->aba_head, a0, b0, a0+1, (uintptr_t)first)); +} +#endif diff --git a/src/core/security/CMakeLists.txt b/src/core/security/CMakeLists.txt new file mode 100644 index 0000000..1b3eb53 --- /dev/null +++ b/src/core/security/CMakeLists.txt @@ -0,0 +1,32 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# + +PREPEND(headers_public_security "$$" + ddsc_security.h +) + + +set(IDLC_ARGS "-dll" "FOO") +idlc_generate(SecurityBuiltinTypes security/src/dds_security_builtintopics.idl security/src/dds_security_interface_types.idl) +set(IDLC_ARGS) +target_link_libraries(ddsc PRIVATE SecurityBuiltinTypes) + +install( + DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/include/security" + DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}" + COMPONENT dev) + + +# TODO: improve test inclusion. +if((BUILD_TESTING) AND ((NOT DEFINED MSVC_VERSION) OR (MSVC_VERSION GREATER "1800"))) + add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/tests") +endif() \ No newline at end of file diff --git a/src/core/security/include/security/ddsc_security.h b/src/core/security/include/security/ddsc_security.h new file mode 100644 index 0000000..ee432cb --- /dev/null +++ b/src/core/security/include/security/ddsc_security.h @@ -0,0 +1,872 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +// +// Created by kurtulus on 29-11-17. +// +//#include "os/os_public.h" +//#include "os/os_decl_attributes_sal.h" + +#include "ddsc/dds.h" +#include "dds_security_builtintopics.h" +#include "dds_security_interface_types.h" + +#ifndef DDSC_SECURITY_H +#define DDSC_SECURITY_H + +//Note – It is recommended that native types be mapped to equivalent type +// names in each programming language, subject to the normal mapping rules for type names in that language + +/** + * Authentication Component + */ + +typedef struct dds_security_authentication dds_security_authentication; + +/** + * AuthenticationListener interface + */ + +typedef bool +(*dds_security_authentication_listener_on_revoke_identity) + (void *listener_data, + _In_ const dds_security_authentication *plugin, + _In_ const DDS_Security_IdentityHandle handle, + _Inout_ DDS_Security_SecurityException *ex + ); + +typedef bool +(*dds_security_authentication_listener_on_status_changed) + (void *listener_data, + _In_ const dds_security_authentication *plugin, + _In_ const DDS_Security_IdentityHandle handle, + _In_ const DDS_Security_AuthStatusKind status_kind, + _Inout_ DDS_Security_SecurityException *ex + ); + + +typedef struct dds_security_authentication_listener +{ + void *listener_data; + + dds_security_authentication_listener_on_revoke_identity on_revoke_identity; + + dds_security_authentication_listener_on_status_changed on_status_changed; +} dds_security_authentication_listener; + +#define dds_security_authentication_listener__alloc() \ +((dds_security_authentication_listener*) dds_alloc (sizeof (dds_security_authentication_listener))); + + +typedef DDS_Security_ValidationResult_t +(*dds_security_authentication_validate_local_identity) + (void *listener_data, + _Inout_ DDS_Security_IdentityHandle *local_identity_handle, + _Inout_ DDS_Security_GUID_t *adjusted_participant_guid, + _In_ const dds_domainid_t domain_id, + _In_ const DDS_Security_DomainParticipantQos *participant_qos, + _In_ const DDS_Security_GUID_t *candidate_participant_guid, + _Inout_ DDS_Security_SecurityException *ex + ); + + +typedef bool +(*dds_security_authentication_get_identity_token) + (void *listener_data, + _Inout_ DDS_Security_IdentityToken *identity_token, + _In_ const DDS_Security_IdentityHandle handle, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef bool +(*dds_security_authentication_get_identity_status_token) + (void *listener_data, + _Inout_ DDS_Security_IdentityStatusToken *identity_status_token, + _In_ const DDS_Security_IdentityHandle handle, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_authentication_set_permissions_credential_and_token) + (void *listener_data, + _In_ const DDS_Security_IdentityHandle handle, + _In_ const DDS_Security_PermissionsCredentialToken *permissions_credential, + _In_ const DDS_Security_PermissionsToken *permissions_token, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef DDS_Security_ValidationResult_t +(*dds_security_authentication_validate_remote_identity) + (void *listener_data, + _Inout_ DDS_Security_IdentityHandle *remote_identity_handle, + _Inout_ DDS_Security_AuthRequestMessageToken *local_auth_request_token, + _In_ const DDS_Security_AuthRequestMessageToken *remote_auth_request_token, + _In_ const DDS_Security_IdentityHandle local_identity_handle, + _In_ const DDS_Security_IdentityToken *remote_identity_token, + _In_ const DDS_Security_GUID_t *remote_participant_guid, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef DDS_Security_ValidationResult_t +(*dds_security_authentication_begin_handshake_request) + (void *listener_data, + _Inout_ DDS_Security_HandshakeHandle *handshake_handle, + _Inout_ DDS_Security_HandshakeMessageToken *handshake_message, + _In_ const DDS_Security_HandshakeMessageToken *handshake_message_in, + _In_ const DDS_Security_IdentityHandle initiator_identity_handle, + _In_ const DDS_Security_IdentityHandle replier_identity_handle, + _In_ const DDS_OctetSeq *serialized_local_participant_data, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef DDS_Security_ValidationResult_t +(*dds_security_authentication_begin_handshake_reply) + (void *listener_data, + _Inout_ DDS_Security_HandshakeHandle *handshake_handle, + _Inout_ DDS_Security_HandshakeMessageToken *handshake_message_out, + _In_ const DDS_Security_HandshakeMessageToken *handshake_message_in, + _In_ const DDS_Security_IdentityHandle initiator_identity_handle, + _In_ const DDS_Security_IdentityHandle replier_identity_handle, + _In_ const DDS_OctetSeq *serialized_local_participant_data, + _Inout_ DDS_Security_SecurityException *ex); + +typedef DDS_Security_ValidationResult_t +(*dds_security_authentication_process_handshake) + (void *listener_data, + _Inout_ DDS_Security_HandshakeMessageToken *handshake_message_out, + _In_ const DDS_Security_HandshakeMessageToken *handshake_message_in, + _In_ const DDS_Security_HandshakeHandle handshake_handle, + _Inout_ DDS_Security_SecurityException *ex); + +typedef DDS_Security_SharedSecretHandle +(*dds_security_authentication_get_shared_secret) + (void *listener_data, + _In_ const DDS_Security_HandshakeHandle handshake_handle, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_authentication_get_authenticated_peer_credential_token) + (void *listener_data, + _Inout_ DDS_Security_AuthenticatedPeerCredentialToken *peer_credential_token, + _In_ const DDS_Security_HandshakeHandle handshake_handle, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_authentication_set_listener) + (void *listener_data, + _In_ const dds_security_authentication_listener *listener, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_authentication_return_identity_token) + (void *listener_data, + _In_ const DDS_Security_IdentityToken *token, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef bool +(*dds_security_authentication_return_identity_status_token) + (void *listener_data, + _In_ const DDS_Security_IdentityStatusToken *token, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_authentication_return_authenticated_peer_credential_token) + (void *listener_data, + _In_ const DDS_Security_AuthenticatedPeerCredentialToken *peer_credential_token, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_authentication_return_handshake_handle) + (void *listener_data, + _In_ const DDS_Security_HandshakeHandle handshake_handle, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_authentication_return_identity_handle) + (void *listener_data, + _In_ const DDS_Security_IdentityHandle identity_handle, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_authentication_return_sharedsecret_handle) + (void *listener_data, + _In_ const DDS_Security_SharedSecretHandle sharedsecret_handle, + _Inout_ DDS_Security_SecurityException *ex); + + +struct dds_security_authentication +{ + + dds_security_authentication_validate_local_identity validate_local_identity; + + dds_security_authentication_get_identity_token get_identity_token; + + dds_security_authentication_get_identity_status_token get_identity_status_token; + + dds_security_authentication_set_permissions_credential_and_token set_permissions_credential_and_token; + + dds_security_authentication_validate_remote_identity validate_remote_identity; + + dds_security_authentication_begin_handshake_request begin_handshake_request; + + dds_security_authentication_begin_handshake_reply begin_handshake_reply; + + dds_security_authentication_process_handshake process_handshake; + + dds_security_authentication_get_shared_secret get_shared_secret; + + dds_security_authentication_get_authenticated_peer_credential_token get_authenticated_peer_credential_token; + + dds_security_authentication_set_listener set_listener; + + dds_security_authentication_return_identity_token return_identity_token; + + dds_security_authentication_return_identity_status_token return_identity_status_token; + + dds_security_authentication_return_authenticated_peer_credential_token return_authenticated_peer_credential_token; + + dds_security_authentication_return_handshake_handle return_handshake_handle; + + dds_security_authentication_return_identity_handle return_identity_handle; + + dds_security_authentication_return_sharedsecret_handle return_sharedsecret_handle; +}; + +#define dds_security_authentication__alloc() \ +((dds_security_authentication*) dds_alloc (sizeof (dds_security_authentication))); + + +/** + * AccessControl Component + */ + +typedef struct dds_security_access_control dds_security_access_control; + +/** + * AccessControlListener Interface + * */ + + + +typedef bool +(*dds_security_access_control_listener_on_revoke_permissions) + (void *listener_data, + _In_ const dds_security_access_control *plugin, + _In_ const DDS_Security_PermissionsHandle handle); + +typedef struct dds_security_access_control_listener +{ + dds_security_access_control_listener_on_revoke_permissions on_revoke_permissions; +} dds_security_access_control_listener; + + +/** + * AccessControl Interface + */ + +typedef DDS_Security_PermissionsHandle +(*dds_security_access_control_validate_local_permissions) + (void *listener_data, + _In_ const dds_security_authentication *auth_plugin, + _In_ const DDS_Security_IdentityHandle identity, + _In_ const dds_domainid_t domain_id, + _In_ const DDS_Security_DomainParticipantQos *participant_qos, + _Inout_ DDS_Security_SecurityException *ex); + +typedef DDS_Security_PermissionsHandle +(*dds_security_access_control_validate_remote_permissions) + (void *listener_data, + _In_ const dds_security_authentication *auth_plugin, + _In_ const DDS_Security_IdentityHandle local_identity_handle, + _In_ const DDS_Security_IdentityHandle remote_identity_handle, + _In_ const DDS_Security_PermissionsToken *remote_permissions_token, + _In_ const DDS_Security_AuthenticatedPeerCredentialToken *remote_credential_token, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_check_create_participant) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle permissions_handle, + _In_ const dds_domainid_t domain_id, + _In_ const dds_qos_t **participant_qos, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_check_create_datawriter) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle permissions_handle, + _In_ const dds_domainid_t domain_id, + _In_ const char *topic_name, + _In_ const dds_qos_t *writer_qos, + _In_ const DDS_PartitionQosPolicy *partition, + _In_ const DDS_Security_DataTags *data_tag, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_check_create_datareader) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle permissions_handle, + _In_ const dds_domainid_t domain_id, + _In_ const char *topic_name, + _In_ const dds_qos_t *reader_qos, + _In_ const DDS_PartitionQosPolicy *partition, + _In_ const DDS_Security_DataTags *data_tag, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef bool +(*dds_security_access_control_check_create_topic) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle permissions_handle, + _In_ const dds_domainid_t domain_id, + _In_ const char *topic_name, + _In_ const DDS_TopicQos *qos, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_check_local_datawriter_register_instance) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle permissions_handle, + _In_ const dds_entity_t *writer, + _In_ const DDS_Security_DynamicData *key, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_check_local_datawriter_dispose_instance) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle permissions_handle, + _In_ const dds_entity_t *writer, + _In_ const DDS_Security_DynamicData key, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_check_remote_participant) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle permissions_handle, + _In_ const dds_domainid_t domain_id, + _In_ const DDS_Security_ParticipantBuiltinTopicDataSecure *participant_data, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_check_remote_datawriter) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle permissions_handle, + _In_ const dds_domainid_t domain_id, + _In_ const DDS_Security_PublicationBuiltinTopicDataSecure *publication_data, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_check_remote_datareader) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle permissions_handle, + _In_ const dds_domainid_t domain_id, + _In_ const DDS_Security_SubscriptionBuiltinTopicDataSecure *subscription_data, + _Inout_ bool *relay_only, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_check_remote_topic) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle permissions_handle, + _In_ const dds_domainid_t domain_id, + _In_ const DDS_TopicBuiltinTopicData *topic_data, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef bool +(*dds_security_access_control_check_local_datawriter_match) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle writer_permissions_handle, + _In_ const DDS_Security_PermissionsHandle reader_permissions_handle, + _In_ const DDS_Security_PublicationBuiltinTopicDataSecure *publication_data, + _In_ const DDS_Security_SubscriptionBuiltinTopicDataSecure *subscription_data, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef bool +(*dds_security_access_control_check_local_datareader_match) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle reader_permissions_handle, + _In_ const DDS_Security_PermissionsHandle writer_permissions_handle, + _In_ const DDS_Security_SubscriptionBuiltinTopicDataSecure *subscription_data, + _In_ const DDS_Security_PublicationBuiltinTopicDataSecure *publication_data, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_check_remote_datawriter_register_instance) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle permissions_handle, + _In_ const dds_entity_t *reader, + _In_ const dds_instance_handle_t publication_handle, + _In_ const DDS_Security_DynamicData key, + _In_ const dds_instance_handle_t instance_handle, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_check_remote_datawriter_dispose_instance) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle permissions_handle, + _In_ const dds_entity_t *reader, + _In_ const dds_instance_handle_t publication_handle, + _In_ const DDS_Security_DynamicData key, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_get_permissions_token) + (void *listener_data, + _Inout_ DDS_Security_PermissionsToken *permissions_token, + _In_ const DDS_Security_PermissionsHandle handle, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_get_permissions_credential_token) + (void *listener_data, + _Inout_ DDS_Security_PermissionsCredentialToken *permissions_credential_token, + _In_ const DDS_Security_PermissionsHandle handle, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_set_listener) + (void *listener_data, + _In_ const dds_security_access_control_listener *listener, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_return_permissions_token) + (void *listener_data, + _In_ const DDS_Security_PermissionsToken *token, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_return_permissions_credential_token) + (void *listener_data, + _In_ const DDS_Security_PermissionsCredentialToken *permissions_credential_token, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_access_control_get_participant_sec_attributes) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle permissions_handle, + _Inout_ DDS_Security_ParticipantSecurityAttributes *attributes, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef bool +(*dds_security_access_control_get_topic_sec_attributes) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle permissions_handle, + _In_ const char *topic_name, + _Inout_ DDS_Security_TopicSecurityAttributes *attributes, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef bool +(*dds_security_access_control_get_datawriter_sec_attributes) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle permissions_handle, + _In_ const DDS_PartitionQosPolicy *partition, + _In_ const DDS_Security_DataTagQosPolicy *data_tag, + _Inout_ DDS_Security_EndpointSecurityAttributes *attributes, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef bool +(*dds_security_access_control_get_datareader_sec_attributes) + (void *listener_data, + _In_ const DDS_Security_PermissionsHandle permissions_handle, + _In_ const DDS_PartitionQosPolicy *partition, + _In_ const DDS_Security_DataTagQosPolicy *data_tag, + _Inout_ DDS_Security_EndpointSecurityAttributes *attributes, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef bool +(*dds_security_access_control_return_participant_sec_attributes) + (void *listener_data, + _In_ const DDS_Security_ParticipantSecurityAttributes *attributes, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef bool +(*dds_security_access_control_return_datawriter_sec_attributes) + (void *listener_data, + _In_ const DDS_Security_EndpointSecurityAttributes *attributes, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef bool +(*dds_security_access_control_return_datareader_sec_attributes) + (void *listener_data, + _In_ const DDS_Security_EndpointSecurityAttributes *attributes, + _Inout_ DDS_Security_SecurityException *ex); + + +struct dds_security_access_control +{ + dds_security_access_control_validate_local_permissions validate_local_permissions; + + dds_security_access_control_validate_remote_permissions validate_remote_permissions; + + dds_security_access_control_check_create_participant check_create_participant; + + dds_security_access_control_check_create_datawriter check_create_datawriter; + + dds_security_access_control_check_create_datareader check_create_datareader; + + dds_security_access_control_check_create_topic check_create_topic; + + dds_security_access_control_check_local_datawriter_register_instance check_local_datawriter_register_instance; + + dds_security_access_control_check_local_datawriter_dispose_instance check_local_datawriter_dispose_instance; + + dds_security_access_control_check_remote_participant check_remote_participant; + + dds_security_access_control_check_remote_datawriter check_remote_datawriter; + + dds_security_access_control_check_remote_datareader check_remote_datareader; + + dds_security_access_control_check_remote_topic check_remote_topic; + + dds_security_access_control_check_local_datawriter_match check_local_datawriter_match; + + dds_security_access_control_check_local_datareader_match check_local_datareader_match; + + dds_security_access_control_check_remote_datawriter_register_instance check_remote_datawriter_register_instance; + + dds_security_access_control_check_remote_datawriter_dispose_instance check_remote_datawriter_dispose_instance; + + dds_security_access_control_get_permissions_token get_permissions_token; + + dds_security_access_control_get_permissions_credential_token get_permissions_credential_token; + + dds_security_access_control_set_listener set_listener; + + dds_security_access_control_return_permissions_token return_permissions_token; + + dds_security_access_control_return_permissions_credential_token return_permissions_credential_token; + + dds_security_access_control_get_participant_sec_attributes get_participant_sec_attributes; + + dds_security_access_control_get_topic_sec_attributes get_topic_sec_attributes; + + dds_security_access_control_get_datawriter_sec_attributes get_datawriter_sec_attributes; + + dds_security_access_control_get_datareader_sec_attributes get_datareader_sec_attributes; + + dds_security_access_control_return_participant_sec_attributes return_participant_sec_attributes; + + dds_security_access_control_return_datawriter_sec_attributes return_datawriter_sec_attributes; + + dds_security_access_control_return_datareader_sec_attributes return_datareader_sec_attributes; + +}; + +struct dds_security_access_control *dds_security_access_control__alloc(void); + +/** + * Crypto Component + */ + +/** + * CryptoKeyFactory interface + */ + +typedef DDS_Security_ParticipantCryptoHandle +(*dds_security_crypto_key_factory_register_local_participant) + (void *listener_data, + _In_ const DDS_Security_IdentityHandle participant_identity, + _In_ const DDS_Security_PermissionsHandle participant_permissions, + _In_ const DDS_Security_PropertySeq *participant_properties, + _In_ const DDS_Security_ParticipantSecurityAttributes *participant_security_attributes, + _Inout_ DDS_Security_SecurityException *ex); + +typedef DDS_Security_ParticipantCryptoHandle +(*dds_security_crypto_key_factory_register_matched_remote_participant) + (void *listener_data, + _In_ const DDS_Security_ParticipantCryptoHandle local_participant_crypto_handle, + _In_ const DDS_Security_IdentityHandle remote_participant_identity, + _In_ const DDS_Security_PermissionsHandle remote_participant_permissions, + _In_ const DDS_Security_SharedSecretHandle shared_secret, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef DDS_Security_DatawriterCryptoHandle +(*dds_security_crypto_key_factory_register_local_datawriter) + (void *listener_data, + _In_ const DDS_Security_ParticipantCryptoHandle participant_crypto, + _In_ const DDS_Security_PropertySeq *datawriter_properties, + _In_ const DDS_Security_EndpointSecurityAttributes *datawriter_security_attributes, + _Inout_ DDS_Security_SecurityException *ex); + +typedef DDS_Security_DatareaderCryptoHandle +(*dds_security_crypto_key_factory_register_matched_remote_datareader) + (void *listener_data, + _In_ const DDS_Security_DatawriterCryptoHandle local_datawritert_crypto_handle, + _In_ const DDS_Security_ParticipantCryptoHandle remote_participant_crypto, + _In_ const DDS_Security_SharedSecretHandle shared_secret, + _In_ const bool relay_only, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef DDS_Security_DatareaderCryptoHandle +(*dds_security_crypto_key_factory_register_local_datareader) + (void *listener_data, + _In_ const DDS_Security_ParticipantCryptoHandle *participant_crypto, + _In_ const DDS_Security_PropertySeq *datareader_properties, + _In_ const DDS_Security_EndpointSecurityAttributes *datareader_security_attributes, + _Inout_ DDS_Security_SecurityException *ex); + +typedef DDS_Security_DatawriterCryptoHandle +(*dds_security_crypto_key_factory_register_matched_remote_datawriter) + (void *listener_data, + _In_ const DDS_Security_DatareaderCryptoHandle local_datareader_crypto_handle, + _In_ const DDS_Security_ParticipantCryptoHandle remote_participant_crypt, + _In_ const DDS_Security_SharedSecretHandle shared_secret, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_crypto_key_factory_unregister_participant) + (void *listener_data, + _In_ const DDS_Security_ParticipantCryptoHandle participant_crypto_handle, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_crypto_key_factory_unregister_datawriter) + (void *listener_data, + _In_ const DDS_Security_DatawriterCryptoHandle datawriter_crypto_handle, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_crypto_key_factory_unregister_datareader) + (void *listener_data, + _In_ const DDS_Security_DatareaderCryptoHandle datareader_crypto_handle, + _Inout_ DDS_Security_SecurityException *ex); + +typedef struct dds_security_crypto_key_factory +{ + + dds_security_crypto_key_factory_register_local_participant register_local_participant; + + dds_security_crypto_key_factory_register_matched_remote_participant register_matched_remote_participant; + + dds_security_crypto_key_factory_register_local_datawriter register_local_datawriter; + + dds_security_crypto_key_factory_register_matched_remote_datareader register_matched_remote_datareader; + + dds_security_crypto_key_factory_register_local_datareader register_local_datareader; + + dds_security_crypto_key_factory_register_matched_remote_datawriter register_matched_remote_datawriter; + + dds_security_crypto_key_factory_unregister_participant unregister_participant; + + dds_security_crypto_key_factory_unregister_datawriter unregister_datawriter; + + dds_security_crypto_key_factory_unregister_datareader unregister_datareader; +} dds_security_crypto_key_factory; + +#define dds_security_crypto_key_factory__alloc() \ +((dds_security_crypto_key_factory*) dds_alloc (sizeof (dds_security_crypto_key_factory))); + +/** + * CryptoKeyExchange Interface + */ +typedef bool +(*dds_security_crypto_key_exchange_create_local_participant_crypto_tokens) + (void *listener_data, + _Inout_ DDS_Security_ParticipantCryptoTokenSeq *local_participant_crypto_tokens, + _In_ const DDS_Security_ParticipantCryptoHandle local_participant_crypto, + _In_ const DDS_Security_ParticipantCryptoHandle remote_participant_crypto, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_crypto_key_exchange_set_remote_participant_crypto_tokens) + (void *listener_data, + _In_ const DDS_Security_ParticipantCryptoHandle local_participant_crypto, + _In_ const DDS_Security_ParticipantCryptoHandle remote_participant_crypto, + _In_ const DDS_Security_ParticipantCryptoTokenSeq *remote_participant_tokens, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_crypto_key_exchange_create_local_datawriter_crypto_tokens) + (void *listener_data, + _Inout_ DDS_Security_DatawriterCryptoTokenSeq *local_datawriter_crypto_tokens, + _In_ const DDS_Security_DatawriterCryptoHandle local_datawriter_crypto, + _In_ const DDS_Security_DatareaderCryptoHandle remote_datareader_crypto, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_crypto_key_exchange_set_remote_datawriter_crypto_tokens) + (void *listener_data, + _In_ const DDS_Security_DatareaderCryptoHandle local_datareader_crypto, + _In_ const DDS_Security_DatawriterCryptoHandle remote_datawriter_crypto, + _In_ const DDS_Security_DatawriterCryptoTokenSeq *remote_datawriter_tokens, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_crypto_key_exchange_create_local_datareader_crypto_tokens) + (void *listener_data, + _Inout_ DDS_Security_DatareaderCryptoTokenSeq *local_datareader_cryto_tokens, + _In_ const DDS_Security_DatareaderCryptoHandle local_datareader_crypto, + _In_ const DDS_Security_DatawriterCryptoHandle remote_datawriter_crypto, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_crypto_key_exchange_set_remote_datareader_crypto_tokens) + (void *listener_data, + _In_ const DDS_Security_DatawriterCryptoHandle local_datawriter_crypto, + _In_ const DDS_Security_DatareaderCryptoHandle remote_datareader_crypto, + _In_ const DDS_Security_DatareaderCryptoTokenSeq *remote_datareader_tokens, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_crypto_key_exchange_return_crypto_tokens) + (void *listener_data, + _In_ const DDS_Security_CryptoTokenSeq *crypto_tokens, + _Inout_ DDS_Security_SecurityException *ex); + +typedef struct dds_security_crypto_key_exchange +{ + dds_security_crypto_key_exchange_create_local_participant_crypto_tokens create_local_participant_crypto_tokens; + + dds_security_crypto_key_exchange_set_remote_participant_crypto_tokens set_remote_participant_crypto_tokens; + + dds_security_crypto_key_exchange_create_local_datawriter_crypto_tokens create_local_datawriter_crypto_tokens; + + dds_security_crypto_key_exchange_set_remote_datawriter_crypto_tokens set_remote_datawriter_crypto_tokens; + + dds_security_crypto_key_exchange_create_local_datareader_crypto_tokens create_local_datareader_crypto_tokens; + + dds_security_crypto_key_exchange_set_remote_datareader_crypto_tokens set_remote_datareader_crypto_tokens; + + dds_security_crypto_key_exchange_return_crypto_tokens return_crypto_tokens; +} dds_security_crypto_key_exchange; + +#define dds_security_crypto_key_exchange__alloc() \ +((dds_security_crypto_key_exchange*) dds_alloc (sizeof (dds_security_crypto_key_exchange))); + +/** + * CryptoTransform Interface + */ + +typedef bool +(*dds_security_crypto_transform_encode_serialized_payload) + (void *listener_data, + _Inout_ DDS_OctetSeq *encoded_buffer, + _Inout_ DDS_OctetSeq *extra_inline_qos, + _In_ const DDS_OctetSeq *plain_buffer, + _In_ const DDS_Security_DatawriterCryptoHandle sending_datawriter_crypto, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_crypto_transform_encode_datawriter_submessage) + (void *listener_data, + _Inout_ DDS_OctetSeq *encoded_rtps_submessage, + _In_ const DDS_OctetSeq *plain_rtps_submessage, + _In_ const DDS_Security_DatawriterCryptoHandle sending_datawriter_crypto, + _In_ const DDS_Security_DatareaderCryptoHandleSeq *receiving_datareader_crypto_list, + _Inout_ int32_t *receiving_datareader_crypto_list_index, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_crypto_transform_encode_datareader_submessage) + (void *listener_data, + _Inout_ DDS_OctetSeq *encoded_rtps_submessage, + _In_ const DDS_OctetSeq *plain_rtps_submessage, + _In_ const DDS_Security_DatareaderCryptoHandle sending_datareader_crypto, + _In_ const DDS_Security_DatawriterCryptoHandleSeq *receiving_datawriter_crypto_list, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef bool +(*dds_security_crypto_transform_encode_rtps_message) + (void *listener_data, + _Inout_ DDS_OctetSeq *encoded_rtps_message, + _In_ const DDS_OctetSeq *plain_rtps_message, + _In_ const DDS_Security_ParticipantCryptoHandle sending_participant_crypto, + _In_ const DDS_Security_ParticipantCryptoHandleSeq *receiving_participant_crypto_list, + _Inout_ int32_t *receiving_participant_crypto_list_index, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_crypto_transform_decode_rtps_message) + (void *listener_data, + _Inout_ DDS_OctetSeq *plain_buffer, + _In_ const DDS_OctetSeq *encoded_buffer, + _In_ const DDS_Security_ParticipantCryptoHandle receiving_participant_crypto, + _In_ const DDS_Security_ParticipantCryptoHandle sending_participant_crypto, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_crypto_transform_preprocess_secure_submsg) + (void *listener_data, + _Inout_ DDS_Security_DatawriterCryptoHandle *datawriter_crypto, + _Inout_ DDS_Security_DatareaderCryptoHandle *datareader_crypto, + _Inout_ DDS_Security_SecureSubmessageCategory_t *secure_submessage_category, + _In_ const DDS_OctetSeq *encoded_rtps_submessage, + _In_ const DDS_Security_ParticipantCryptoHandle receiving_participant_crypto, + _In_ const DDS_Security_ParticipantCryptoHandle sending_participant_crypto, + _Inout_ DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_crypto_transform_decode_datawriter_submessage) + (void *listener_data, + _Inout_ DDS_OctetSeq *plain_rtps_submessage, + _In_ const DDS_OctetSeq *encoded_rtps_submessage, + _In_ const DDS_Security_DatareaderCryptoHandle receiving_datareader_crypto, + _In_ const DDS_Security_DatawriterCryptoHandle sending_datawriter_crypto, + _In_ const DDS_Security_SecurityException *ex); + +typedef bool +(*dds_security_crypto_transform_decode_datareader_submessage) + (void *listener_data, + _Inout_ DDS_OctetSeq *plain_rtps_message, + _In_ const DDS_OctetSeq *encoded_rtps_message, + _In_ const DDS_Security_DatawriterCryptoHandle receiving_datawriter_crypto, + _In_ const DDS_Security_DatareaderCryptoHandle sending_datareader_crypto, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef bool +(*dds_security_crypto_transform_decode_serialized_payload) + (void *listener_data, + _Inout_ DDS_OctetSeq *plain_buffer, + _In_ const DDS_OctetSeq *encoded_buffer, + _In_ const DDS_OctetSeq *inline_qos, + _In_ const DDS_Security_DatareaderCryptoHandle receiving_datareader_crypto, + _In_ const DDS_Security_DatawriterCryptoHandle sending_datawriter_crypto, + _Inout_ DDS_Security_SecurityException *ex); + + +typedef struct dds_security_crypto_transform +{ + dds_security_crypto_transform_encode_serialized_payload encode_serialized_payload; + + dds_security_crypto_transform_encode_datawriter_submessage encode_datawriter_submessage; + + dds_security_crypto_transform_encode_datareader_submessage encode_datareader_submessage; + + dds_security_crypto_transform_encode_rtps_message encode_rtps_message; + + dds_security_crypto_transform_decode_rtps_message decode_rtps_message; + + dds_security_crypto_transform_preprocess_secure_submsg preprocess_secure_submsg; + + dds_security_crypto_transform_decode_datawriter_submessage decode_datawriter_submessage; + + dds_security_crypto_transform_decode_datareader_submessage decode_datareader_submessage; + + dds_security_crypto_transform_decode_serialized_payload decode_serialized_payload; +} dds_security_crypto_transform; + +#define dds_security_crypto_key_exchange__alloc() \ +((dds_security_crypto_key_exchange*) dds_alloc (sizeof (dds_security_crypto_key_exchange))); + +#endif //DDSC_SECURITY_H diff --git a/src/core/security/src/dds_security_builtintopics.idl b/src/core/security/src/dds_security_builtintopics.idl new file mode 100644 index 0000000..2b489cb --- /dev/null +++ b/src/core/security/src/dds_security_builtintopics.idl @@ -0,0 +1,301 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + +/* + * dds_rtf2_dcps.idl needed for the declarations + * of DDS Entities and DDS Entity Qos + */ + +//#include "dds-xtypes_discovery.idl" /* http://www.omg.org/spec/DDS-XTypes/20170301/dds-xtypes_discovery.idl */ +//#include "dds_dcps_builtintopics.idl" +#include "../../ddsc/src/dds_builtinTopics.idl" + +#include "../../ddsc/src/dds_dcps_builtintopics.idl" + + +#define DOMAINID_TYPE_NATIVE long +#define HANDLE_TYPE_NATIVE long long + + +module DDS { + typedef sequence OctetSeq; + + module Security { + + struct Property_t { + string name; + string value; + boolean propagate; + }; + + typedef sequence< Property_t > PropertySeq; + + + struct BinaryProperty_t { + string name; + OctetSeq value; + boolean propagate; + }; + typedef sequence< BinaryProperty_t > BinaryPropertySeq; + + struct PropertyQosPolicy { + PropertySeq value; + BinaryPropertySeq binary_value; + }; + + + + struct DataHolder { + string class_id; + /*@optional*/ PropertySeq properties; + /*@optional*/ BinaryPropertySeq binary_properties; + }; + typedef sequence DataHolderSeq; + + typedef DataHolder Token; + + + typedef Token MessageToken; + typedef Token IdentityToken; + typedef Token PermissionsToken; + + typedef Token IdentityStatusToken; + + struct DomainParticipantQos { + UserDataQosPolicy user_data; + EntityFactoryQosPolicy entity_factory; + SchedulingQosPolicy watchdog_scheduling; + SchedulingQosPolicy listener_scheduling; + PropertyQosPolicy proIperty; + }; + + struct Tag { + string name; + string value; + }; + + typedef sequence< Tag > TagSeq; + struct DataTags { + TagSeq tags; + }; + + + typedef DataTags DataTagQosPolicy; + + struct DataWriterQos { + DurabilityQosPolicy durability; + DeadlineQosPolicy deadline; + LatencyBudgetQosPolicy latency_budget; + LivelinessQosPolicy liveliness; + ReliabilityQosPolicy reliability; + DestinationOrderQosPolicy destination_order; + HistoryQosPolicy history; + ResourceLimitsQosPolicy resource_limits; + TransportPriorityQosPolicy transport_priority; + LifespanQosPolicy lifespan; + UserDataQosPolicy user_data; + OwnershipQosPolicy ownership; + OwnershipStrengthQosPolicy ownership_strength; + WriterDataLifecycleQosPolicy writer_data_lifecycle; + PropertyQosPolicy property; + DataTagQosPolicy data_tags; + }; + + struct DataReaderQos { + DurabilityQosPolicy durability; + DeadlineQosPolicy deadline; + LatencyBudgetQosPolicy latency_budget; + LivelinessQosPolicy liveliness; + ReliabilityQosPolicy reliability; + DestinationOrderQosPolicy destination_order; + HistoryQosPolicy history; + ResourceLimitsQosPolicy resource_limits; + UserDataQosPolicy user_data; + OwnershipQosPolicy ownership; + TimeBasedFilterQosPolicy time_based_filter; + ReaderDataLifecycleQosPolicy reader_data_lifecycle; + SubscriptionKeyQosPolicy subscription_keys; + ReaderLifespanQosPolicy reader_lifespan; + ShareQosPolicy share; + PropertyQosPolicy property; + DataTagQosPolicy data_tags; + }; + + + typedef unsigned long ParticipantSecurityAttributesMask; + typedef unsigned long PluginParticipantSecurityAttributesMask; + + struct ParticipantSecurityInfo { + ParticipantSecurityAttributesMask participant_security_attributes; + PluginParticipantSecurityAttributesMask plugin_participant_security_attributes; + }; + + + typedef unsigned long EndpointSecurityAttributesMask; + typedef unsigned long PluginEndpointSecurityAttributesMask; + struct EndpointSecurityInfo { + EndpointSecurityAttributesMask endpoint_security_mask; + PluginEndpointSecurityAttributesMask plugin_endpoint_security_mask; + }; + + + struct ParticipantBuiltinTopicData { + BuiltinTopicKey_t key; + UserDataQosPolicy user_data; + IdentityToken identity_token; + PermissionsToken permissions_token; + PropertyQosPolicy property; + ParticipantSecurityInfo security_info; + }; + + + struct ParticipantBuiltinTopicDataSecure { + BuiltinTopicKey_t key; + UserDataQosPolicy user_data; + IdentityToken identity_token; + PermissionsToken permissions_token; + PropertyQosPolicy property; + ParticipantSecurityInfo security_info; + IdentityStatusToken identity_status_token; + }; + + + struct PublicationBuiltinTopicData { + BuiltinTopicKey_t key; + BuiltinTopicKey_t participant_key; + string topic_name; + string type_name; + DurabilityQosPolicy durability; + DeadlineQosPolicy deadline; + LatencyBudgetQosPolicy latency_budget; + LivelinessQosPolicy liveliness; + ReliabilityQosPolicy reliability; + LifespanQosPolicy lifespan; + DestinationOrderQosPolicy destination_order; + UserDataQosPolicy user_data; + OwnershipQosPolicy ownership; + OwnershipStrengthQosPolicy ownership_strength; + PresentationQosPolicy presentation; + PartitionQosPolicy partition; + TopicDataQosPolicy topic_data; + GroupDataQosPolicy group_data; + EndpointSecurityInfo security_info; + }; + + + struct SubscriptionBuiltinTopicData{ + BuiltinTopicKey_t key; + BuiltinTopicKey_t participant_key; + string topic_name; + string type_name; + DurabilityQosPolicy durability; + DeadlineQosPolicy deadline; + LatencyBudgetQosPolicy latency_budget; + LivelinessQosPolicy liveliness; + ReliabilityQosPolicy reliability; + OwnershipQosPolicy ownership; + DestinationOrderQosPolicy destination_order; + UserDataQosPolicy user_data; + TimeBasedFilterQosPolicy time_based_filter; + PresentationQosPolicy presentation; + PartitionQosPolicy partition; + TopicDataQosPolicy topic_data; + GroupDataQosPolicy group_data; + EndpointSecurityInfo security_info; + }; + + + struct PublicationBuiltinTopicDataSecure { + BuiltinTopicKey_t key; + BuiltinTopicKey_t participant_key; + string topic_name; + string type_name; + DurabilityQosPolicy durability; + DeadlineQosPolicy deadline; + LatencyBudgetQosPolicy latency_budget; + LivelinessQosPolicy liveliness; + ReliabilityQosPolicy reliability; + LifespanQosPolicy lifespan; + DestinationOrderQosPolicy destination_order; + UserDataQosPolicy user_data; + OwnershipQosPolicy ownership; + OwnershipStrengthQosPolicy ownership_strength; + PresentationQosPolicy presentation; + PartitionQosPolicy partition; + TopicDataQosPolicy topic_data; + GroupDataQosPolicy group_data; + EndpointSecurityInfo security_info; + DataTags data_tags; + }; + + + struct SubscriptionBuiltinTopicDataSecure { // it was : DDS::SubscriptionBuiltinTopicData but it should SubscriptionBuiltinTopicData??? { + BuiltinTopicKey_t key; + BuiltinTopicKey_t participant_key; + string topic_name; + string type_name; + DurabilityQosPolicy durability; + DeadlineQosPolicy deadline; + LatencyBudgetQosPolicy latency_budget; + LivelinessQosPolicy liveliness; + ReliabilityQosPolicy reliability; + OwnershipQosPolicy ownership; + DestinationOrderQosPolicy destination_order; + UserDataQosPolicy user_data; + TimeBasedFilterQosPolicy time_based_filter; + PresentationQosPolicy presentation; + PartitionQosPolicy partition; + TopicDataQosPolicy topic_data; + GroupDataQosPolicy group_data; + EndpointSecurityInfo security_info; + DataTags data_tags; + }; + + + + struct ParticipantSecurityAttributes { + boolean allow_unauthenticated_participants; + boolean is_access_protected; + boolean is_rtps_protected; + boolean is_discovery_protected; + boolean is_liveliness_protected; + ParticipantSecurityAttributesMask plugin_participant_attributes; + PropertySeq ac_endpoint_properties; + }; + + + + struct EndpointSecurityAttributes { + boolean is_read_protected; + boolean is_write_protected; + boolean is_discovery_protected; + boolean is_liveliness_protected; + boolean is_submessage_protected; + boolean is_payload_protected; + boolean is_key_protected; + PluginEndpointSecurityAttributesMask plugin_endpoint_attributes; + PropertySeq ac_endpoint_properties; + }; + + + struct PluginEndpointSecurityAttributes { + boolean is_submessage_encrypted; + boolean is_payload_encrypted; + boolean is_submessage_origin_authenticated; + }; + + + + }; +}; + + diff --git a/src/core/security/src/dds_security_interface_types.idl b/src/core/security/src/dds_security_interface_types.idl new file mode 100644 index 0000000..c0d3c1e --- /dev/null +++ b/src/core/security/src/dds_security_interface_types.idl @@ -0,0 +1,133 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ + + +#include "dds_security_builtintopics.idl" + + +#define DOMAINID_TYPE_NATIVE long +#define HANDLE_TYPE_NATIVE long long + + +module DDS { + + /** + * DDs types that should be defined for security. These can be moved to another DDS idl other than builtin types idl + */ + + module Security { + + typedef long DynamicData; + + typedef MessageToken AuthRequestMessageToken; + typedef MessageToken HandshakeMessageToken; + + typedef Token AuthenticatedPeerCredentialToken; + typedef Token PermissionsCredentialToken; + + typedef Token CryptoToken; + typedef sequence CryptoTokenSeq; + + typedef Token ParticipantCryptoToken; + typedef Token DatawriterCryptoToken; + typedef Token DatareaderCryptoToken; + + typedef CryptoTokenSeq ParticipantCryptoTokenSeq; + typedef CryptoTokenSeq DatawriterCryptoTokenSeq; + typedef CryptoTokenSeq DatareaderCryptoTokenSeq; + + + // From DDS-RTPS [2] clauses 8.4.2.1 and 9.3.1 + typedef octet GuidPrefix_t[12]; + + struct EntityId_t { + octet entityKey[3]; + octet entityKind; + }; + + + struct GUID_t { + GuidPrefix_t prefix; + EntityId_t entityId; + }; + + + struct MessageIdentity { + GUID_t source_guid; + long long sequence_number; + }; + + + typedef long SecurityExceptionCode; + + struct SecurityException { + string message; + + long minor_code; + }; + + enum ValidationResult_t { + VALIDATION_OK, + VALIDATION_FAILED, + VALIDATION_PENDING_RETRY, + VALIDATION_PENDING_HANDSHAKE_REQUEST, + VALIDATION_PENDING_HANDSHAKE_MESSAGE, + VALIDATION_OK_FINAL_MESSAGE + }; + + typedef long IdentityHandle; + //native IdentityHandle; + typedef long HandshakeHandle; + //native HandshakeHandle; + typedef long SharedSecretHandle; + //native SharedSecretHandle; + typedef long PermissionsHandle; + //native PermissionsHandle; + typedef long ParticipantCryptoHandle; + //native ParticipantCryptoHandle; + typedef long ParticipantCryptoHandleSeq; + //native ParticipantCryptoHandleSeq; + typedef long DatawriterCryptoHandle; + //native DatawriterCryptoHandle; + typedef long DatawriterCryptoHandleSeq; + //native DatawriterCryptoHandleSeq; + typedef long DatareaderCryptoHandle; + //native DatareaderCryptoHandle; + typedef long DatareaderCryptoHandleSeq; + //native DatareaderCryptoHandleSeq; + + enum AuthStatusKind { + IDENTITY_STATUS /*@VALUE(1)*/ + }; + + + + + struct TopicSecurityAttributes { + boolean is_read_protected; + boolean is_write_protected; + boolean is_discovery_protected; + boolean is_liveliness_protected; + }; + + + enum SecureSubmessageCategory_t { + INFO_SUBMESSAGE, + DATAWRITER_SUBMESSAGE, + DATAREADER_SUBMESSAGE + }; + }; + + +}; + + diff --git a/src/core/security/tests/CMakeLists.txt b/src/core/security/tests/CMakeLists.txt new file mode 100644 index 0000000..8810334 --- /dev/null +++ b/src/core/security/tests/CMakeLists.txt @@ -0,0 +1,30 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +include(Criterion) + +add_criterion_executable(criterion_security .) +target_include_directories(criterion_security PRIVATE + "$") +target_link_libraries(criterion_security ddsc OSAPI) + +# Setup environment for config-tests +#set(Criterion_ddsc_config_simple_udp_file "${CMAKE_CURRENT_LIST_DIR}/config_simple_udp.xml") +#set(Criterion_ddsc_config_simple_udp_uri "file://${Criterion_ddsc_config_simple_udp_file}") +#set(Criterion_ddsc_config_simple_udp_max_participants "0") +#set_tests_properties( +# Criterion_security_config_simple_udp +# PROPERTIES +# REQUIRED_FILES ${Criterion_ddsc_config_simple_udp_file} +# ENVIRONMENT "${CMAKE_PROJECT_NAME_CAPS}_URI=${Criterion_ddsc_config_simple_udp_uri};MAX_PARTICIPANTS=${Criterion_ddsc_config_simple_udp_max_participants}" +# +#) +#configure_file("config_env.h.in" "config_env.h") diff --git a/src/core/security/tests/authentication.c b/src/core/security/tests/authentication.c new file mode 100644 index 0000000..6c27869 --- /dev/null +++ b/src/core/security/tests/authentication.c @@ -0,0 +1,16 @@ +/* + * Copyright(c) 2006 to 2018 ADLINK Technology Limited and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License v. 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License + * v. 1.0 which is available at + * http://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause + */ +// +// Created by kurtulus on 8-12-17. +// + +#include "../include/security/ddsc_security.h" \ No newline at end of file diff --git a/src/docs/CMakeLists.txt b/src/docs/CMakeLists.txt new file mode 100644 index 0000000..f9504c6 --- /dev/null +++ b/src/docs/CMakeLists.txt @@ -0,0 +1,225 @@ +# +# Copyright(c) 2006 to 2018 ADLINK Technology Limited and others +# +# This program and the accompanying materials are made available under the +# terms of the Eclipse Public License v. 2.0 which is available at +# http://www.eclipse.org/legal/epl-2.0, or the Eclipse Distribution License +# v. 1.0 which is available at +# http://www.eclipse.org/org/documents/edl-v10.php. +# +# SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause +# +# TODO depending on requirements we can add/remove options as needed, +# these are examples of generators we'll need as a minimum. +# Perhaps we should also consider options for building subset of all docs. +# When a certain doc is related to a target, no option is needed; you can simply check if the target exists +# (i.e. if a target 'ddsc' exists, build ddsc api docs). And possibly make the target definition dependent on an option. + +option(BUILD_DOCS, "Enable generation of docs." OFF) +# Above option should only be set when building master on a designated Jenkins job; every other +# Jenkins job/development machine can download them from there. + +set(JENKINS_BASE_URI "http://jenkins.prismtech.com:8080/") +set(JENKINS_DOCS_JOB_NAME "BuildChameleonLinux64bit") +set(PROJECT_PDF_URI "${JENKINS_BASE_URI}/job/${JENKINS_DOCS_JOB_NAME}/lastSuccessfulBuild/artifact/cham/builds/${CMAKE_PROJECT_NAME}.pdf") +set(PROJECT_HTML_URI "${JENKINS_BASE_URI}/job/${JENKINS_DOCS_JOB_NAME}/lastSuccessfulBuild/artifact/cham/builds/${CMAKE_PROJECT_NAME}HTML.tar.gz") +set(DOCS_OUTPUT_DIR "${CMAKE_CURRENT_BINARY_DIR}/..") + +if (BUILD_DOCS) + find_program(SPHINX_EXECUTABLE NAMES sphinx-build DOC "Sphinx documentation builder") + if (NOT SPHINX_EXECUTABLE) + set(BUILD_DOCS off) + message("-- Unable to find sphinx-build executable -> download documentation") + endif() +endif() + +if (BUILD_DOCS) + find_package(Doxygen) + if (NOT Doxygen_FOUND) + set(BUILD_DOCS off) + message("-- Unable to find Doxygen -> download documentation") + endif() +endif() + +if (BUILD_DOCS) + # Creating pdf from latex requires latexmk (which depends on perl, latexpdf et. al) + find_program(LATEXMK_EXECUTABLE NAMES latexmk DOC "LateX PDF Generator") + if (NOT LATEXMK_EXECUTABLE) + set(BUILD_DOCS off) + message("-- Unable to find latexmk executable -> download documentation") + endif() +endif() + +if (BUILD_DOCS) + # Generate ddsc API docs in XML format using Doxygen + # The XML will serve as input for sphinx' breathe plugin + if (TARGET ${CMAKE_PROJECT_NAME}::ddsc) + # Process doxygen configuration file, for ddsc + set(doxy_conf_project "${CMAKE_PROJECT_NAME_FULL} C API Documentation") + set(doxy_conf_outputdir "ddsc_api") + set(doxy_conf_input "${PROJECT_SOURCE_DIR}/core/ddsc/include/ddsc/dds.h ${PROJECT_SOURCE_DIR}/core/ddsc/include/ddsc") + configure_file(Doxyfile.in Doxyfile @ONLY) + + add_custom_target(ddsc_docs + ${DOXYGEN_EXECUTABLE} Doxyfile + WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} + COMMENT "Running Doxygen for API docs generation" + VERBATIM + ) + + # Remove generated files when cleaning the build tree + set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${doxy_conf_outputdir}) + + # Add ddsc api docs to sphinx' breathe projects + set(sph_conf_breathe_projs "\"ddsc_api\": \"${doxy_conf_outputdir}/xml\"") + + add_custom_command( + TARGET ddsc_docs + POST_BUILD + WORKING_DIRECTORY "${doxy_conf_outputdir}" + COMMAND ${CMAKE_COMMAND} + -E tar "zcf" "${DOCS_OUTPUT_DIR}/${CMAKE_PROJECT_NAME}_C_HTML.tar.gz" "ddsc" + ) + endif() + + # Process sphinx configuration file + set(sph_conf_author "ADLINK") + string(TIMESTAMP sph_conf_copyright "%Y, ADLINK") + set(sph_conf_version "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}.${PROJECT_VERSION_PATCH}") + set(sph_conf_release "${PROJECT_VERSION}") + set(sph_logo "${PROJECT_SOURCE_DIR}/docs/_static/pictures/VORTEX_LOGO.png") + configure_file(conf.py.in conf.py @ONLY) + + # Define a list of output formats (-b option for sphinx-build) + set(docs_builders "") + list(APPEND docs_builders html) + list(APPEND docs_builders latex) + + # Define custom commands for running sphinx-build for different docs builders + set(docs_outputs "") + foreach(builder ${docs_builders}) + set(docs_builder_output "docs_${builder}_output") + # Log stdout (not stderr) to a file instead of messing up build output + set(docs_builder_log "sphinx-build-${builder}.log") + + add_custom_command( + OUTPUT ${docs_builder_output} + COMMAND ${SPHINX_EXECUTABLE} + -b ${builder} + -d ${CMAKE_CURRENT_BINARY_DIR}/cache + -c ${CMAKE_CURRENT_BINARY_DIR} + ${PROJECT_SOURCE_DIR}/docs + ${CMAKE_CURRENT_BINARY_DIR}/${builder} + > ${docs_builder_log} + COMMENT "Running Sphinx for ${builder} output" + VERBATIM + ) + + # FIXME: This is definitely in the wrong location + if(builder STREQUAL html) + add_custom_command( + OUTPUT ${docs_builder_output} + COMMAND ${CMAKE_COMMAND} + -E tar "zcf" + "${DOCS_OUTPUT_DIR}/${CMAKE_PROJECT_NAME}HTML.tar.gz" + "${CMAKE_CURRENT_BINARY_DIR}/html" + APPEND + VERBATIM + ) + endif() + + # Create a pdf from the latex builder output, by appending a latexmk command + # TODO look into rinohtype as an alternative (don't bother with rst2pdf, it's no good) + if(builder STREQUAL latex) + add_custom_command( + OUTPUT ${docs_builder_output} + COMMAND ${LATEXMK_EXECUTABLE} + -interaction=nonstopmode + -silent + -output-directory=${builder} + -pdf -dvi- -ps- -cd- ${builder}/${CMAKE_PROJECT_NAME}.tex + APPEND + VERBATIM + ) + add_custom_command( + OUTPUT ${docs_builder_output} + COMMAND ${CMAKE_COMMAND} + -E rename + ${CMAKE_CURRENT_BINARY_DIR}/latex/${CMAKE_PROJECT_NAME}.pdf + ${DOCS_OUTPUT_DIR}/${CMAKE_PROJECT_NAME}.pdf + APPEND + VERBATIM + ) + endif() + + # OUTPUT is a fake target / symbolic name, not an actual file + set_property(SOURCE ${docs_builder_output} PROPERTY SYMBOLIC 1) + # Remove generated files when cleaning the build tree + set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${builder} ${docs_builder_log}) + # Include this builder as a dependency of the general 'docs' target + list(APPEND docs_outputs ${docs_builder_output}) + endforeach() + + # Remove generated files when cleaning the build tree + set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES ${DOCS_OUTPUT_DIR}/${CMAKE_PROJECT_NAME}HTML.tar.gz ${DOCS_OUTPUT_DIR}/${CMAKE_PROJECT_NAME}.pdf) + + add_custom_target(docs ALL DEPENDS ddsc_docs ${docs_outputs}) +else() + add_custom_target(docs ALL) + find_program(WGET_EXECUTABLE NAMES wget DOC "wget") + if (WGET_EXECUTABLE) + # prevent wget to create numbered downloads. + add_custom_command( + TARGET docs + COMMAND ${CMAKE_COMMAND} + -E remove -f "${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}HTML.tar.gz" + VERBATIM + ) + add_custom_command( + TARGET docs + COMMAND ${WGET_EXECUTABLE} + -q + ${PROJECT_HTML_URI} + ${PROJECT_PDF_URI} + COMMENT "Downloading documentation from target." + VERBATIM + ) + # To make downloading and packaging easier. + add_custom_command( + TARGET docs + COMMAND ${CMAKE_COMMAND} + -E rename + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}.pdf + ${DOCS_OUTPUT_DIR}/${CMAKE_PROJECT_NAME}.pdf + VERBATIM + ) + else() + message("-- Unable to find wget. Download docs now.") + # Just try to download the docs straight away. + file(DOWNLOAD + ${PROJECT_HTML_URI} + ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}HTML.tar.gz) + file(DOWNLOAD + ${PROJECT_PDF_URI} + ${DOCS_OUTPUT_DIR}/${CMAKE_PROJECT_NAME}.pdf) + endif() + + add_custom_command( + TARGET docs + COMMAND ${CMAKE_COMMAND} + -E tar "zxf" "${CMAKE_PROJECT_NAME}HTML.tar.gz" . + VERBATIM + ) + # Remove generated files when cleaning the build tree + set_property(DIRECTORY APPEND PROPERTY ADDITIONAL_MAKE_CLEAN_FILES html ${CMAKE_CURRENT_BINARY_DIR}/${CMAKE_PROJECT_NAME}HTML.tar.gz ${DOCS_OUTPUT_DIR}/${CMAKE_PROJECT_NAME}.pdf) + +endif() + +install( + DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html + DESTINATION ${CMAKE_INSTALL_DOCDIR} + COMPONENT dev) +install( + FILES ${DOCS_OUTPUT_DIR}/${CMAKE_PROJECT_NAME}.pdf + DESTINATION ${CMAKE_INSTALL_DOCDIR} + COMPONENT dev) diff --git a/src/docs/Doxyfile.in b/src/docs/Doxyfile.in new file mode 100644 index 0000000..ea6b34a --- /dev/null +++ b/src/docs/Doxyfile.in @@ -0,0 +1,2427 @@ +# Doxyfile 1.8.11 + +# This file describes the settings to be used by the documentation system +# doxygen (www.doxygen.org) for a project. +# +# All text after a double hash (##) is considered a comment and is placed in +# front of the TAG it is preceding. +# +# All text after a single hash (#) is considered a comment and will be ignored. +# The format is: +# TAG = value [value, ...] +# For lists, items can also be appended using: +# TAG += value [value, ...] +# Values that contain spaces should be placed between quotes (\" \"). + +#--------------------------------------------------------------------------- +# Project related configuration options +#--------------------------------------------------------------------------- + +# This tag specifies the encoding used for all characters in the config file +# that follow. The default is UTF-8 which is also the encoding used for all text +# before the first occurrence of this tag. Doxygen uses libiconv (or the iconv +# built into libc) for the transcoding. See http://www.gnu.org/software/libiconv +# for the list of possible encodings. +# The default value is: UTF-8. + +DOXYFILE_ENCODING = UTF-8 + +# The PROJECT_NAME tag is a single word (or a sequence of words surrounded by +# double-quotes, unless you are using Doxywizard) that should identify the +# project for which the documentation is generated. This name is used in the +# title of most generated pages and in a few other places. +# The default value is: My Project. + +PROJECT_NAME = "@doxy_conf_project@" + +# The PROJECT_NUMBER tag can be used to enter a project or revision number. This +# could be handy for archiving the generated documentation or if some version +# control system is used. + +PROJECT_NUMBER = + +# Using the PROJECT_BRIEF tag one can provide an optional one line description +# for a project that appears at the top of each page and should give viewer a +# quick idea about the purpose of the project. Keep the description short. + +PROJECT_BRIEF = + +# With the PROJECT_LOGO tag one can specify a logo or an icon that is included +# in the documentation. The maximum height of the logo should not exceed 55 +# pixels and the maximum width should not exceed 200 pixels. Doxygen will copy +# the logo to the output directory. + +PROJECT_LOGO = + +# The OUTPUT_DIRECTORY tag is used to specify the (relative or absolute) path +# into which the generated documentation will be written. If a relative path is +# entered, it will be relative to the location where doxygen was started. If +# left blank the current directory will be used. + +OUTPUT_DIRECTORY = @doxy_conf_outputdir@ + +# If the CREATE_SUBDIRS tag is set to YES then doxygen will create 4096 sub- +# directories (in 2 levels) under the output directory of each output format and +# will distribute the generated files over these directories. Enabling this +# option can be useful when feeding doxygen a huge amount of source files, where +# putting all generated files in the same directory would otherwise causes +# performance problems for the file system. +# The default value is: NO. + +CREATE_SUBDIRS = NO + +# If the ALLOW_UNICODE_NAMES tag is set to YES, doxygen will allow non-ASCII +# characters to appear in the names of generated files. If set to NO, non-ASCII +# characters will be escaped, for example _xE3_x81_x84 will be used for Unicode +# U+3044. +# The default value is: NO. + +ALLOW_UNICODE_NAMES = NO + +# The OUTPUT_LANGUAGE tag is used to specify the language in which all +# documentation generated by doxygen is written. Doxygen will use this +# information to generate all constant output in the proper language. +# Possible values are: Afrikaans, Arabic, Armenian, Brazilian, Catalan, Chinese, +# Chinese-Traditional, Croatian, Czech, Danish, Dutch, English (United States), +# Esperanto, Farsi (Persian), Finnish, French, German, Greek, Hungarian, +# Indonesian, Italian, Japanese, Japanese-en (Japanese with English messages), +# Korean, Korean-en (Korean with English messages), Latvian, Lithuanian, +# Macedonian, Norwegian, Persian (Farsi), Polish, Portuguese, Romanian, Russian, +# Serbian, Serbian-Cyrillic, Slovak, Slovene, Spanish, Swedish, Turkish, +# Ukrainian and Vietnamese. +# The default value is: English. + +OUTPUT_LANGUAGE = English + +# If the BRIEF_MEMBER_DESC tag is set to YES, doxygen will include brief member +# descriptions after the members that are listed in the file and class +# documentation (similar to Javadoc). Set to NO to disable this. +# The default value is: YES. + +BRIEF_MEMBER_DESC = YES + +# If the REPEAT_BRIEF tag is set to YES, doxygen will prepend the brief +# description of a member or function before the detailed description +# +# Note: If both HIDE_UNDOC_MEMBERS and BRIEF_MEMBER_DESC are set to NO, the +# brief descriptions will be completely suppressed. +# The default value is: YES. + +REPEAT_BRIEF = YES + +# This tag implements a quasi-intelligent brief description abbreviator that is +# used to form the text in various listings. Each string in this list, if found +# as the leading text of the brief description, will be stripped from the text +# and the result, after processing the whole list, is used as the annotated +# text. Otherwise, the brief description is used as-is. If left blank, the +# following values are used ($name is automatically replaced with the name of +# the entity):The $name class, The $name widget, The $name file, is, provides, +# specifies, contains, represents, a, an and the. + +ABBREVIATE_BRIEF = + +# If the ALWAYS_DETAILED_SEC and REPEAT_BRIEF tags are both set to YES then +# doxygen will generate a detailed section even if there is only a brief +# description. +# The default value is: NO. + +ALWAYS_DETAILED_SEC = NO + +# If the INLINE_INHERITED_MEMB tag is set to YES, doxygen will show all +# inherited members of a class in the documentation of that class as if those +# members were ordinary class members. Constructors, destructors and assignment +# operators of the base classes will not be shown. +# The default value is: NO. + +INLINE_INHERITED_MEMB = NO + +# If the FULL_PATH_NAMES tag is set to YES, doxygen will prepend the full path +# before files name in the file list and in the header files. If set to NO the +# shortest path that makes the file name unique will be used +# The default value is: YES. + +FULL_PATH_NAMES = YES + +# The STRIP_FROM_PATH tag can be used to strip a user-defined part of the path. +# Stripping is only done if one of the specified strings matches the left-hand +# part of the path. The tag can be used to show relative paths in the file list. +# If left blank the directory from which doxygen is run is used as the path to +# strip. +# +# Note that you can specify absolute paths here, but also relative paths, which +# will be relative from the directory where doxygen is started. +# This tag requires that the tag FULL_PATH_NAMES is set to YES. + +STRIP_FROM_PATH = + +# The STRIP_FROM_INC_PATH tag can be used to strip a user-defined part of the +# path mentioned in the documentation of a class, which tells the reader which +# header file to include in order to use a class. If left blank only the name of +# the header file containing the class definition is used. Otherwise one should +# specify the list of include paths that are normally passed to the compiler +# using the -I flag. + +STRIP_FROM_INC_PATH = + +# If the SHORT_NAMES tag is set to YES, doxygen will generate much shorter (but +# less readable) file names. This can be useful is your file systems doesn't +# support long names like on DOS, Mac, or CD-ROM. +# The default value is: NO. + +SHORT_NAMES = NO + +# If the JAVADOC_AUTOBRIEF tag is set to YES then doxygen will interpret the +# first line (until the first dot) of a Javadoc-style comment as the brief +# description. If set to NO, the Javadoc-style will behave just like regular Qt- +# style comments (thus requiring an explicit @brief command for a brief +# description.) +# The default value is: NO. + +JAVADOC_AUTOBRIEF = NO + +# If the QT_AUTOBRIEF tag is set to YES then doxygen will interpret the first +# line (until the first dot) of a Qt-style comment as the brief description. If +# set to NO, the Qt-style will behave just like regular Qt-style comments (thus +# requiring an explicit \brief command for a brief description.) +# The default value is: NO. + +QT_AUTOBRIEF = NO + +# The MULTILINE_CPP_IS_BRIEF tag can be set to YES to make doxygen treat a +# multi-line C++ special comment block (i.e. a block of //! or /// comments) as +# a brief description. This used to be the default behavior. The new default is +# to treat a multi-line C++ comment block as a detailed description. Set this +# tag to YES if you prefer the old behavior instead. +# +# Note that setting this tag to YES also means that rational rose comments are +# not recognized any more. +# The default value is: NO. + +MULTILINE_CPP_IS_BRIEF = NO + +# If the INHERIT_DOCS tag is set to YES then an undocumented member inherits the +# documentation from any documented member that it re-implements. +# The default value is: YES. + +INHERIT_DOCS = YES + +# If the SEPARATE_MEMBER_PAGES tag is set to YES then doxygen will produce a new +# page for each member. If set to NO, the documentation of a member will be part +# of the file/class/namespace that contains it. +# The default value is: NO. + +SEPARATE_MEMBER_PAGES = NO + +# The TAB_SIZE tag can be used to set the number of spaces in a tab. Doxygen +# uses this value to replace tabs by spaces in code fragments. +# Minimum value: 1, maximum value: 16, default value: 4. + +TAB_SIZE = 4 + +# This tag can be used to specify a number of aliases that act as commands in +# the documentation. An alias has the form: +# name=value +# For example adding +# "sideeffect=@par Side Effects:\n" +# will allow you to put the command \sideeffect (or @sideeffect) in the +# documentation, which will result in a user-defined paragraph with heading +# "Side Effects:". You can put \n's in the value part of an alias to insert +# newlines. + +ALIASES = + +# This tag can be used to specify a number of word-keyword mappings (TCL only). +# A mapping has the form "name=value". For example adding "class=itcl::class" +# will allow you to use the command class in the itcl::class meaning. + +TCL_SUBST = + +# Set the OPTIMIZE_OUTPUT_FOR_C tag to YES if your project consists of C sources +# only. Doxygen will then generate output that is more tailored for C. For +# instance, some of the names that are used will be different. The list of all +# members will be omitted, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_FOR_C = NO + +# Set the OPTIMIZE_OUTPUT_JAVA tag to YES if your project consists of Java or +# Python sources only. Doxygen will then generate output that is more tailored +# for that language. For instance, namespaces will be presented as packages, +# qualified scopes will look different, etc. +# The default value is: NO. + +OPTIMIZE_OUTPUT_JAVA = NO + +# Set the OPTIMIZE_FOR_FORTRAN tag to YES if your project consists of Fortran +# sources. Doxygen will then generate output that is tailored for Fortran. +# The default value is: NO. + +OPTIMIZE_FOR_FORTRAN = NO + +# Set the OPTIMIZE_OUTPUT_VHDL tag to YES if your project consists of VHDL +# sources. Doxygen will then generate output that is tailored for VHDL. +# The default value is: NO. + +OPTIMIZE_OUTPUT_VHDL = NO + +# Doxygen selects the parser to use depending on the extension of the files it +# parses. With this tag you can assign which parser to use for a given +# extension. Doxygen has a built-in mapping, but you can override or extend it +# using this tag. The format is ext=language, where ext is a file extension, and +# language is one of the parsers supported by doxygen: IDL, Java, Javascript, +# C#, C, C++, D, PHP, Objective-C, Python, Fortran (fixed format Fortran: +# FortranFixed, free formatted Fortran: FortranFree, unknown formatted Fortran: +# Fortran. In the later case the parser tries to guess whether the code is fixed +# or free formatted code, this is the default for Fortran type files), VHDL. For +# instance to make doxygen treat .inc files as Fortran files (default is PHP), +# and .f files as C (default is Fortran), use: inc=Fortran f=C. +# +# Note: For files without extension you can use no_extension as a placeholder. +# +# Note that for custom extensions you also need to set FILE_PATTERNS otherwise +# the files are not read by doxygen. + +EXTENSION_MAPPING = + +# If the MARKDOWN_SUPPORT tag is enabled then doxygen pre-processes all comments +# according to the Markdown format, which allows for more readable +# documentation. See http://daringfireball.net/projects/markdown/ for details. +# The output of markdown processing is further processed by doxygen, so you can +# mix doxygen, HTML, and XML commands with Markdown formatting. Disable only in +# case of backward compatibilities issues. +# The default value is: YES. + +MARKDOWN_SUPPORT = YES + +# When enabled doxygen tries to link words that correspond to documented +# classes, or namespaces to their corresponding documentation. Such a link can +# be prevented in individual cases by putting a % sign in front of the word or +# globally by setting AUTOLINK_SUPPORT to NO. +# The default value is: YES. + +AUTOLINK_SUPPORT = YES + +# If you use STL classes (i.e. std::string, std::vector, etc.) but do not want +# to include (a tag file for) the STL sources as input, then you should set this +# tag to YES in order to let doxygen match functions declarations and +# definitions whose arguments contain STL classes (e.g. func(std::string); +# versus func(std::string) {}). This also make the inheritance and collaboration +# diagrams that involve STL classes more complete and accurate. +# The default value is: NO. + +BUILTIN_STL_SUPPORT = NO + +# If you use Microsoft's C++/CLI language, you should set this option to YES to +# enable parsing support. +# The default value is: NO. + +CPP_CLI_SUPPORT = NO + +# Set the SIP_SUPPORT tag to YES if your project consists of sip (see: +# http://www.riverbankcomputing.co.uk/software/sip/intro) sources only. Doxygen +# will parse them like normal C++ but will assume all classes use public instead +# of private inheritance when no explicit protection keyword is present. +# The default value is: NO. + +SIP_SUPPORT = NO + +# For Microsoft's IDL there are propget and propput attributes to indicate +# getter and setter methods for a property. Setting this option to YES will make +# doxygen to replace the get and set methods by a property in the documentation. +# This will only work if the methods are indeed getting or setting a simple +# type. If this is not the case, or you want to show the methods anyway, you +# should set this option to NO. +# The default value is: YES. + +IDL_PROPERTY_SUPPORT = YES + +# If member grouping is used in the documentation and the DISTRIBUTE_GROUP_DOC +# tag is set to YES then doxygen will reuse the documentation of the first +# member in the group (if any) for the other members of the group. By default +# all members of a group must be documented explicitly. +# The default value is: NO. + +DISTRIBUTE_GROUP_DOC = NO + +# If one adds a struct or class to a group and this option is enabled, then also +# any nested class or struct is added to the same group. By default this option +# is disabled and one has to add nested compounds explicitly via \ingroup. +# The default value is: NO. + +GROUP_NESTED_COMPOUNDS = NO + +# Set the SUBGROUPING tag to YES to allow class member groups of the same type +# (for instance a group of public functions) to be put as a subgroup of that +# type (e.g. under the Public Functions section). Set it to NO to prevent +# subgrouping. Alternatively, this can be done per class using the +# \nosubgrouping command. +# The default value is: YES. + +SUBGROUPING = YES + +# When the INLINE_GROUPED_CLASSES tag is set to YES, classes, structs and unions +# are shown inside the group in which they are included (e.g. using \ingroup) +# instead of on a separate page (for HTML and Man pages) or section (for LaTeX +# and RTF). +# +# Note that this feature does not work in combination with +# SEPARATE_MEMBER_PAGES. +# The default value is: NO. + +INLINE_GROUPED_CLASSES = NO + +# When the INLINE_SIMPLE_STRUCTS tag is set to YES, structs, classes, and unions +# with only public data fields or simple typedef fields will be shown inline in +# the documentation of the scope in which they are defined (i.e. file, +# namespace, or group documentation), provided this scope is documented. If set +# to NO, structs, classes, and unions are shown on a separate page (for HTML and +# Man pages) or section (for LaTeX and RTF). +# The default value is: NO. + +INLINE_SIMPLE_STRUCTS = NO + +# When TYPEDEF_HIDES_STRUCT tag is enabled, a typedef of a struct, union, or +# enum is documented as struct, union, or enum with the name of the typedef. So +# typedef struct TypeS {} TypeT, will appear in the documentation as a struct +# with name TypeT. When disabled the typedef will appear as a member of a file, +# namespace, or class. And the struct will be named TypeS. This can typically be +# useful for C code in case the coding convention dictates that all compound +# types are typedef'ed and only the typedef is referenced, never the tag name. +# The default value is: NO. + +TYPEDEF_HIDES_STRUCT = NO + +# The size of the symbol lookup cache can be set using LOOKUP_CACHE_SIZE. This +# cache is used to resolve symbols given their name and scope. Since this can be +# an expensive process and often the same symbol appears multiple times in the +# code, doxygen keeps a cache of pre-resolved symbols. If the cache is too small +# doxygen will become slower. If the cache is too large, memory is wasted. The +# cache size is given by this formula: 2^(16+LOOKUP_CACHE_SIZE). The valid range +# is 0..9, the default is 0, corresponding to a cache size of 2^16=65536 +# symbols. At the end of a run doxygen will report the cache usage and suggest +# the optimal cache size from a speed point of view. +# Minimum value: 0, maximum value: 9, default value: 0. + +LOOKUP_CACHE_SIZE = 0 + +#--------------------------------------------------------------------------- +# Build related configuration options +#--------------------------------------------------------------------------- + +# If the EXTRACT_ALL tag is set to YES, doxygen will assume all entities in +# documentation are documented, even if no documentation was available. Private +# class members and static file members will be hidden unless the +# EXTRACT_PRIVATE respectively EXTRACT_STATIC tags are set to YES. +# Note: This will also disable the warnings about undocumented members that are +# normally produced when WARNINGS is set to YES. +# The default value is: NO. + +EXTRACT_ALL = NO + +# If the EXTRACT_PRIVATE tag is set to YES, all private members of a class will +# be included in the documentation. +# The default value is: NO. + +EXTRACT_PRIVATE = NO + +# If the EXTRACT_PACKAGE tag is set to YES, all members with package or internal +# scope will be included in the documentation. +# The default value is: NO. + +EXTRACT_PACKAGE = NO + +# If the EXTRACT_STATIC tag is set to YES, all static members of a file will be +# included in the documentation. +# The default value is: NO. + +EXTRACT_STATIC = NO + +# If the EXTRACT_LOCAL_CLASSES tag is set to YES, classes (and structs) defined +# locally in source files will be included in the documentation. If set to NO, +# only classes defined in header files are included. Does not have any effect +# for Java sources. +# The default value is: YES. + +EXTRACT_LOCAL_CLASSES = YES + +# This flag is only useful for Objective-C code. If set to YES, local methods, +# which are defined in the implementation section but not in the interface are +# included in the documentation. If set to NO, only methods in the interface are +# included. +# The default value is: NO. + +EXTRACT_LOCAL_METHODS = NO + +# If this flag is set to YES, the members of anonymous namespaces will be +# extracted and appear in the documentation as a namespace called +# 'anonymous_namespace{file}', where file will be replaced with the base name of +# the file that contains the anonymous namespace. By default anonymous namespace +# are hidden. +# The default value is: NO. + +EXTRACT_ANON_NSPACES = NO + +# If the HIDE_UNDOC_MEMBERS tag is set to YES, doxygen will hide all +# undocumented members inside documented classes or files. If set to NO these +# members will be included in the various overviews, but no documentation +# section is generated. This option has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_MEMBERS = NO + +# If the HIDE_UNDOC_CLASSES tag is set to YES, doxygen will hide all +# undocumented classes that are normally visible in the class hierarchy. If set +# to NO, these classes will be included in the various overviews. This option +# has no effect if EXTRACT_ALL is enabled. +# The default value is: NO. + +HIDE_UNDOC_CLASSES = NO + +# If the HIDE_FRIEND_COMPOUNDS tag is set to YES, doxygen will hide all friend +# (class|struct|union) declarations. If set to NO, these declarations will be +# included in the documentation. +# The default value is: NO. + +HIDE_FRIEND_COMPOUNDS = NO + +# If the HIDE_IN_BODY_DOCS tag is set to YES, doxygen will hide any +# documentation blocks found inside the body of a function. If set to NO, these +# blocks will be appended to the function's detailed documentation block. +# The default value is: NO. + +HIDE_IN_BODY_DOCS = NO + +# The INTERNAL_DOCS tag determines if documentation that is typed after a +# \internal command is included. If the tag is set to NO then the documentation +# will be excluded. Set it to YES to include the internal documentation. +# The default value is: NO. + +INTERNAL_DOCS = NO + +# If the CASE_SENSE_NAMES tag is set to NO then doxygen will only generate file +# names in lower-case letters. If set to YES, upper-case letters are also +# allowed. This is useful if you have classes or files whose names only differ +# in case and if your file system supports case sensitive file names. Windows +# and Mac users are advised to set this option to NO. +# The default value is: system dependent. + +CASE_SENSE_NAMES = YES + +# If the HIDE_SCOPE_NAMES tag is set to NO then doxygen will show members with +# their full class and namespace scopes in the documentation. If set to YES, the +# scope will be hidden. +# The default value is: NO. + +HIDE_SCOPE_NAMES = NO + +# If the HIDE_COMPOUND_REFERENCE tag is set to NO (default) then doxygen will +# append additional text to a page's title, such as Class Reference. If set to +# YES the compound reference will be hidden. +# The default value is: NO. + +HIDE_COMPOUND_REFERENCE = NO + +# If the SHOW_INCLUDE_FILES tag is set to YES then doxygen will put a list of +# the files that are included by a file in the documentation of that file. +# The default value is: YES. + +SHOW_INCLUDE_FILES = YES + +# If the SHOW_GROUPED_MEMB_INC tag is set to YES then Doxygen will add for each +# grouped member an include statement to the documentation, telling the reader +# which file to include in order to use the member. +# The default value is: NO. + +SHOW_GROUPED_MEMB_INC = NO + +# If the FORCE_LOCAL_INCLUDES tag is set to YES then doxygen will list include +# files with double quotes in the documentation rather than with sharp brackets. +# The default value is: NO. + +FORCE_LOCAL_INCLUDES = NO + +# If the INLINE_INFO tag is set to YES then a tag [inline] is inserted in the +# documentation for inline members. +# The default value is: YES. + +INLINE_INFO = YES + +# If the SORT_MEMBER_DOCS tag is set to YES then doxygen will sort the +# (detailed) documentation of file and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. +# The default value is: YES. + +SORT_MEMBER_DOCS = YES + +# If the SORT_BRIEF_DOCS tag is set to YES then doxygen will sort the brief +# descriptions of file, namespace and class members alphabetically by member +# name. If set to NO, the members will appear in declaration order. Note that +# this will also influence the order of the classes in the class list. +# The default value is: NO. + +SORT_BRIEF_DOCS = NO + +# If the SORT_MEMBERS_CTORS_1ST tag is set to YES then doxygen will sort the +# (brief and detailed) documentation of class members so that constructors and +# destructors are listed first. If set to NO the constructors will appear in the +# respective orders defined by SORT_BRIEF_DOCS and SORT_MEMBER_DOCS. +# Note: If SORT_BRIEF_DOCS is set to NO this option is ignored for sorting brief +# member documentation. +# Note: If SORT_MEMBER_DOCS is set to NO this option is ignored for sorting +# detailed member documentation. +# The default value is: NO. + +SORT_MEMBERS_CTORS_1ST = NO + +# If the SORT_GROUP_NAMES tag is set to YES then doxygen will sort the hierarchy +# of group names into alphabetical order. If set to NO the group names will +# appear in their defined order. +# The default value is: NO. + +SORT_GROUP_NAMES = NO + +# If the SORT_BY_SCOPE_NAME tag is set to YES, the class list will be sorted by +# fully-qualified names, including namespaces. If set to NO, the class list will +# be sorted only by class name, not including the namespace part. +# Note: This option is not very useful if HIDE_SCOPE_NAMES is set to YES. +# Note: This option applies only to the class list, not to the alphabetical +# list. +# The default value is: NO. + +SORT_BY_SCOPE_NAME = NO + +# If the STRICT_PROTO_MATCHING option is enabled and doxygen fails to do proper +# type resolution of all parameters of a function it will reject a match between +# the prototype and the implementation of a member function even if there is +# only one candidate or it is obvious which candidate to choose by doing a +# simple string match. By disabling STRICT_PROTO_MATCHING doxygen will still +# accept a match between prototype and implementation in such cases. +# The default value is: NO. + +STRICT_PROTO_MATCHING = NO + +# The GENERATE_TODOLIST tag can be used to enable (YES) or disable (NO) the todo +# list. This list is created by putting \todo commands in the documentation. +# The default value is: YES. + +GENERATE_TODOLIST = YES + +# The GENERATE_TESTLIST tag can be used to enable (YES) or disable (NO) the test +# list. This list is created by putting \test commands in the documentation. +# The default value is: YES. + +GENERATE_TESTLIST = YES + +# The GENERATE_BUGLIST tag can be used to enable (YES) or disable (NO) the bug +# list. This list is created by putting \bug commands in the documentation. +# The default value is: YES. + +GENERATE_BUGLIST = YES + +# The GENERATE_DEPRECATEDLIST tag can be used to enable (YES) or disable (NO) +# the deprecated list. This list is created by putting \deprecated commands in +# the documentation. +# The default value is: YES. + +GENERATE_DEPRECATEDLIST = YES + +# The ENABLED_SECTIONS tag can be used to enable conditional documentation +# sections, marked by \if ... \endif and \cond +# ... \endcond blocks. + +ENABLED_SECTIONS = + +# The MAX_INITIALIZER_LINES tag determines the maximum number of lines that the +# initial value of a variable or macro / define can have for it to appear in the +# documentation. If the initializer consists of more lines than specified here +# it will be hidden. Use a value of 0 to hide initializers completely. The +# appearance of the value of individual variables and macros / defines can be +# controlled using \showinitializer or \hideinitializer command in the +# documentation regardless of this setting. +# Minimum value: 0, maximum value: 10000, default value: 30. + +MAX_INITIALIZER_LINES = 30 + +# Set the SHOW_USED_FILES tag to NO to disable the list of files generated at +# the bottom of the documentation of classes and structs. If set to YES, the +# list will mention the files that were used to generate the documentation. +# The default value is: YES. + +SHOW_USED_FILES = YES + +# Set the SHOW_FILES tag to NO to disable the generation of the Files page. This +# will remove the Files entry from the Quick Index and from the Folder Tree View +# (if specified). +# The default value is: YES. + +SHOW_FILES = YES + +# Set the SHOW_NAMESPACES tag to NO to disable the generation of the Namespaces +# page. This will remove the Namespaces entry from the Quick Index and from the +# Folder Tree View (if specified). +# The default value is: YES. + +SHOW_NAMESPACES = YES + +# The FILE_VERSION_FILTER tag can be used to specify a program or script that +# doxygen should invoke to get the current version for each file (typically from +# the version control system). Doxygen will invoke the program by executing (via +# popen()) the command command input-file, where command is the value of the +# FILE_VERSION_FILTER tag, and input-file is the name of an input file provided +# by doxygen. Whatever the program writes to standard output is used as the file +# version. For an example see the documentation. + +FILE_VERSION_FILTER = + +# The LAYOUT_FILE tag can be used to specify a layout file which will be parsed +# by doxygen. The layout file controls the global structure of the generated +# output files in an output format independent way. To create the layout file +# that represents doxygen's defaults, run doxygen with the -l option. You can +# optionally specify a file name after the option, if omitted DoxygenLayout.xml +# will be used as the name of the layout file. +# +# Note that if you run doxygen from a directory containing a file called +# DoxygenLayout.xml, doxygen will parse it automatically even if the LAYOUT_FILE +# tag is left empty. + +LAYOUT_FILE = + +# The CITE_BIB_FILES tag can be used to specify one or more bib files containing +# the reference definitions. This must be a list of .bib files. The .bib +# extension is automatically appended if omitted. This requires the bibtex tool +# to be installed. See also http://en.wikipedia.org/wiki/BibTeX for more info. +# For LaTeX the style of the bibliography can be controlled using +# LATEX_BIB_STYLE. To use this feature you need bibtex and perl available in the +# search path. See also \cite for info how to create references. + +CITE_BIB_FILES = + +#--------------------------------------------------------------------------- +# Configuration options related to warning and progress messages +#--------------------------------------------------------------------------- + +# The QUIET tag can be used to turn on/off the messages that are generated to +# standard output by doxygen. If QUIET is set to YES this implies that the +# messages are off. +# The default value is: NO. + +QUIET = YES + +# The WARNINGS tag can be used to turn on/off the warning messages that are +# generated to standard error (stderr) by doxygen. If WARNINGS is set to YES +# this implies that the warnings are on. +# +# Tip: Turn warnings on while writing the documentation. +# The default value is: YES. + +WARNINGS = NO + +# If the WARN_IF_UNDOCUMENTED tag is set to YES then doxygen will generate +# warnings for undocumented members. If EXTRACT_ALL is set to YES then this flag +# will automatically be disabled. +# The default value is: YES. + +WARN_IF_UNDOCUMENTED = NO + +# If the WARN_IF_DOC_ERROR tag is set to YES, doxygen will generate warnings for +# potential errors in the documentation, such as not documenting some parameters +# in a documented function, or documenting parameters that don't exist or using +# markup commands wrongly. +# The default value is: YES. + +WARN_IF_DOC_ERROR = NO + +# This WARN_NO_PARAMDOC option can be enabled to get warnings for functions that +# are documented, but have no documentation for their parameters or return +# value. If set to NO, doxygen will only warn about wrong or incomplete +# parameter documentation, but not about the absence of documentation. +# The default value is: NO. + +WARN_NO_PARAMDOC = NO + +# If the WARN_AS_ERROR tag is set to YES then doxygen will immediately stop when +# a warning is encountered. +# The default value is: NO. + +WARN_AS_ERROR = NO + +# The WARN_FORMAT tag determines the format of the warning messages that doxygen +# can produce. The string should contain the $file, $line, and $text tags, which +# will be replaced by the file and line number from which the warning originated +# and the warning text. Optionally the format may contain $version, which will +# be replaced by the version of the file (if it could be obtained via +# FILE_VERSION_FILTER) +# The default value is: $file:$line: $text. + +WARN_FORMAT = "$file:$line: $text" + +# The WARN_LOGFILE tag can be used to specify a file to which warning and error +# messages should be written. If left blank the output is written to standard +# error (stderr). + +WARN_LOGFILE = + +#--------------------------------------------------------------------------- +# Configuration options related to the input files +#--------------------------------------------------------------------------- + +# The INPUT tag is used to specify the files and/or directories that contain +# documented source files. You may enter file names like myfile.cpp or +# directories like /usr/src/myproject. Separate the files or directories with +# spaces. See also FILE_PATTERNS and EXTENSION_MAPPING +# Note: If this tag is empty the current directory is searched. + +INPUT = @doxy_conf_input@ + +# This tag can be used to specify the character encoding of the source files +# that doxygen parses. Internally doxygen uses the UTF-8 encoding. Doxygen uses +# libiconv (or the iconv built into libc) for the transcoding. See the libiconv +# documentation (see: http://www.gnu.org/software/libiconv) for the list of +# possible encodings. +# The default value is: UTF-8. + +INPUT_ENCODING = UTF-8 + +# If the value of the INPUT tag contains directories, you can use the +# FILE_PATTERNS tag to specify one or more wildcard patterns (like *.cpp and +# *.h) to filter out the source-files in the directories. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# read by doxygen. +# +# If left blank the following patterns are tested:*.c, *.cc, *.cxx, *.cpp, +# *.c++, *.java, *.ii, *.ixx, *.ipp, *.i++, *.inl, *.idl, *.ddl, *.odl, *.h, +# *.hh, *.hxx, *.hpp, *.h++, *.cs, *.d, *.php, *.php4, *.php5, *.phtml, *.inc, +# *.m, *.markdown, *.md, *.mm, *.dox, *.py, *.pyw, *.f90, *.f, *.for, *.tcl, +# *.vhd, *.vhdl, *.ucf, *.qsf, *.as and *.js. + +FILE_PATTERNS = + +# The RECURSIVE tag can be used to specify whether or not subdirectories should +# be searched for input files as well. +# The default value is: NO. + +RECURSIVE = YES + +# The EXCLUDE tag can be used to specify files and/or directories that should be +# excluded from the INPUT source files. This way you can easily exclude a +# subdirectory from a directory tree whose root is specified with the INPUT tag. +# +# Note that relative paths are relative to the directory from which doxygen is +# run. + +EXCLUDE = + +# The EXCLUDE_SYMLINKS tag can be used to select whether or not files or +# directories that are symbolic links (a Unix file system feature) are excluded +# from the input. +# The default value is: NO. + +EXCLUDE_SYMLINKS = NO + +# If the value of the INPUT tag contains directories, you can use the +# EXCLUDE_PATTERNS tag to specify one or more wildcard patterns to exclude +# certain files from those directories. +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories for example use the pattern */test/* + +EXCLUDE_PATTERNS = */tests/* + +# The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names +# (namespaces, classes, functions, etc.) that should be excluded from the +# output. The symbol name can be a fully qualified name, a word, or if the +# wildcard * is used, a substring. Examples: ANamespace, AClass, +# AClass::ANamespace, ANamespace::*Test +# +# Note that the wildcards are matched against the file with absolute path, so to +# exclude all test directories use the pattern */test/* + +EXCLUDE_SYMBOLS = + +# The EXAMPLE_PATH tag can be used to specify one or more files or directories +# that contain example code fragments that are included (see the \include +# command). + +EXAMPLE_PATH = + +# If the value of the EXAMPLE_PATH tag contains directories, you can use the +# EXAMPLE_PATTERNS tag to specify one or more wildcard pattern (like *.cpp and +# *.h) to filter out the source-files in the directories. If left blank all +# files are included. + +EXAMPLE_PATTERNS = + +# If the EXAMPLE_RECURSIVE tag is set to YES then subdirectories will be +# searched for input files to be used with the \include or \dontinclude commands +# irrespective of the value of the RECURSIVE tag. +# The default value is: NO. + +EXAMPLE_RECURSIVE = NO + +# The IMAGE_PATH tag can be used to specify one or more files or directories +# that contain images that are to be included in the documentation (see the +# \image command). + +IMAGE_PATH = + +# The INPUT_FILTER tag can be used to specify a program that doxygen should +# invoke to filter for each input file. Doxygen will invoke the filter program +# by executing (via popen()) the command: +# +# +# +# where is the value of the INPUT_FILTER tag, and is the +# name of an input file. Doxygen will then use the output that the filter +# program writes to standard output. If FILTER_PATTERNS is specified, this tag +# will be ignored. +# +# Note that the filter must not add or remove lines; it is applied before the +# code is scanned, but not when the output code is generated. If lines are added +# or removed, the anchors will not be placed correctly. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +INPUT_FILTER = + +# The FILTER_PATTERNS tag can be used to specify filters on a per file pattern +# basis. Doxygen will compare the file name with each pattern and apply the +# filter if there is a match. The filters are a list of the form: pattern=filter +# (like *.cpp=my_cpp_filter). See INPUT_FILTER for further information on how +# filters are used. If the FILTER_PATTERNS tag is empty or if none of the +# patterns match the file name, INPUT_FILTER is applied. +# +# Note that for custom extensions or not directly supported extensions you also +# need to set EXTENSION_MAPPING for the extension otherwise the files are not +# properly processed by doxygen. + +FILTER_PATTERNS = + +# If the FILTER_SOURCE_FILES tag is set to YES, the input filter (if set using +# INPUT_FILTER) will also be used to filter the input files that are used for +# producing the source files to browse (i.e. when SOURCE_BROWSER is set to YES). +# The default value is: NO. + +FILTER_SOURCE_FILES = NO + +# The FILTER_SOURCE_PATTERNS tag can be used to specify source filters per file +# pattern. A pattern will override the setting for FILTER_PATTERN (if any) and +# it is also possible to disable source filtering for a specific pattern using +# *.ext= (so without naming a filter). +# This tag requires that the tag FILTER_SOURCE_FILES is set to YES. + +FILTER_SOURCE_PATTERNS = + +# If the USE_MDFILE_AS_MAINPAGE tag refers to the name of a markdown file that +# is part of the input, its contents will be placed on the main page +# (index.html). This can be useful if you have a project on for instance GitHub +# and want to reuse the introduction page also for the doxygen output. + +USE_MDFILE_AS_MAINPAGE = + +#--------------------------------------------------------------------------- +# Configuration options related to source browsing +#--------------------------------------------------------------------------- + +# If the SOURCE_BROWSER tag is set to YES then a list of source files will be +# generated. Documented entities will be cross-referenced with these sources. +# +# Note: To get rid of all source code in the generated output, make sure that +# also VERBATIM_HEADERS is set to NO. +# The default value is: NO. + +SOURCE_BROWSER = NO + +# Setting the INLINE_SOURCES tag to YES will include the body of functions, +# classes and enums directly into the documentation. +# The default value is: NO. + +INLINE_SOURCES = NO + +# Setting the STRIP_CODE_COMMENTS tag to YES will instruct doxygen to hide any +# special comment blocks from generated source code fragments. Normal C, C++ and +# Fortran comments will always remain visible. +# The default value is: YES. + +STRIP_CODE_COMMENTS = YES + +# If the REFERENCED_BY_RELATION tag is set to YES then for each documented +# function all documented functions referencing it will be listed. +# The default value is: NO. + +REFERENCED_BY_RELATION = NO + +# If the REFERENCES_RELATION tag is set to YES then for each documented function +# all documented entities called/used by that function will be listed. +# The default value is: NO. + +REFERENCES_RELATION = NO + +# If the REFERENCES_LINK_SOURCE tag is set to YES and SOURCE_BROWSER tag is set +# to YES then the hyperlinks from functions in REFERENCES_RELATION and +# REFERENCED_BY_RELATION lists will link to the source code. Otherwise they will +# link to the documentation. +# The default value is: YES. + +REFERENCES_LINK_SOURCE = YES + +# If SOURCE_TOOLTIPS is enabled (the default) then hovering a hyperlink in the +# source code will show a tooltip with additional information such as prototype, +# brief description and links to the definition and documentation. Since this +# will make the HTML file larger and loading of large files a bit slower, you +# can opt to disable this feature. +# The default value is: YES. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +SOURCE_TOOLTIPS = YES + +# If the USE_HTAGS tag is set to YES then the references to source code will +# point to the HTML generated by the htags(1) tool instead of doxygen built-in +# source browser. The htags tool is part of GNU's global source tagging system +# (see http://www.gnu.org/software/global/global.html). You will need version +# 4.8.6 or higher. +# +# To use it do the following: +# - Install the latest version of global +# - Enable SOURCE_BROWSER and USE_HTAGS in the config file +# - Make sure the INPUT points to the root of the source tree +# - Run doxygen as normal +# +# Doxygen will invoke htags (and that will in turn invoke gtags), so these +# tools must be available from the command line (i.e. in the search path). +# +# The result: instead of the source browser generated by doxygen, the links to +# source code will now point to the output of htags. +# The default value is: NO. +# This tag requires that the tag SOURCE_BROWSER is set to YES. + +USE_HTAGS = NO + +# If the VERBATIM_HEADERS tag is set the YES then doxygen will generate a +# verbatim copy of the header file for each class for which an include is +# specified. Set to NO to disable this. +# See also: Section \class. +# The default value is: YES. + +VERBATIM_HEADERS = YES + +# If the CLANG_ASSISTED_PARSING tag is set to YES then doxygen will use the +# clang parser (see: http://clang.llvm.org/) for more accurate parsing at the +# cost of reduced performance. This can be particularly helpful with template +# rich C++ code for which doxygen's built-in parser lacks the necessary type +# information. +# Note: The availability of this option depends on whether or not doxygen was +# generated with the -Duse-libclang=ON option for CMake. +# The default value is: NO. + +# CLANG_ASSISTED_PARSING = NO + +# If clang assisted parsing is enabled you can provide the compiler with command +# line options that you would normally use when invoking the compiler. Note that +# the include paths will already be set by doxygen for the files and directories +# specified with INPUT and INCLUDE_PATH. +# This tag requires that the tag CLANG_ASSISTED_PARSING is set to YES. + +# CLANG_OPTIONS = + +#--------------------------------------------------------------------------- +# Configuration options related to the alphabetical class index +#--------------------------------------------------------------------------- + +# If the ALPHABETICAL_INDEX tag is set to YES, an alphabetical index of all +# compounds will be generated. Enable this if the project contains a lot of +# classes, structs, unions or interfaces. +# The default value is: YES. + +ALPHABETICAL_INDEX = YES + +# The COLS_IN_ALPHA_INDEX tag can be used to specify the number of columns in +# which the alphabetical index list will be split. +# Minimum value: 1, maximum value: 20, default value: 5. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +COLS_IN_ALPHA_INDEX = 5 + +# In case all classes in a project start with a common prefix, all classes will +# be put under the same header in the alphabetical index. The IGNORE_PREFIX tag +# can be used to specify a prefix (or a list of prefixes) that should be ignored +# while generating the index headers. +# This tag requires that the tag ALPHABETICAL_INDEX is set to YES. + +IGNORE_PREFIX = + +#--------------------------------------------------------------------------- +# Configuration options related to the HTML output +#--------------------------------------------------------------------------- + +# If the GENERATE_HTML tag is set to YES, doxygen will generate HTML output +# The default value is: YES. + +GENERATE_HTML = YES + +# The HTML_OUTPUT tag is used to specify where the HTML docs will be put. If a +# relative path is entered the value of OUTPUT_DIRECTORY will be put in front of +# it. +# The default directory is: html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_OUTPUT = ddsc + +# The HTML_FILE_EXTENSION tag can be used to specify the file extension for each +# generated HTML page (for example: .htm, .php, .asp). +# The default value is: .html. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FILE_EXTENSION = .html + +# The HTML_HEADER tag can be used to specify a user-defined HTML header file for +# each generated HTML page. If the tag is left blank doxygen will generate a +# standard header. +# +# To get valid HTML the header file that includes any scripts and style sheets +# that doxygen needs, which is dependent on the configuration options used (e.g. +# the setting GENERATE_TREEVIEW). It is highly recommended to start with a +# default header using +# doxygen -w html new_header.html new_footer.html new_stylesheet.css +# YourConfigFile +# and then modify the file new_header.html. See also section "Doxygen usage" +# for information on how to generate the default header that doxygen normally +# uses. +# Note: The header is subject to change so you typically have to regenerate the +# default header when upgrading to a newer version of doxygen. For a description +# of the possible markers and block names see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_HEADER = + +# The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each +# generated HTML page. If the tag is left blank doxygen will generate a standard +# footer. See HTML_HEADER for more information on how to generate a default +# footer and what special commands can be used inside the footer. See also +# section "Doxygen usage" for information on how to generate the default footer +# that doxygen normally uses. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_FOOTER = + +# The HTML_STYLESHEET tag can be used to specify a user-defined cascading style +# sheet that is used by each HTML page. It can be used to fine-tune the look of +# the HTML output. If left blank doxygen will generate a default style sheet. +# See also section "Doxygen usage" for information on how to generate the style +# sheet that doxygen normally uses. +# Note: It is recommended to use HTML_EXTRA_STYLESHEET instead of this tag, as +# it is more robust and this tag (HTML_STYLESHEET) will in the future become +# obsolete. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_STYLESHEET = + +# The HTML_EXTRA_STYLESHEET tag can be used to specify additional user-defined +# cascading style sheets that are included after the standard style sheets +# created by doxygen. Using this option one can overrule certain style aspects. +# This is preferred over using HTML_STYLESHEET since it does not replace the +# standard style sheet and is therefore more robust against future updates. +# Doxygen will copy the style sheet files to the output directory. +# Note: The order of the extra style sheet files is of importance (e.g. the last +# style sheet in the list overrules the setting of the previous ones in the +# list). For an example see the documentation. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_STYLESHEET = + +# The HTML_EXTRA_FILES tag can be used to specify one or more extra images or +# other source files which should be copied to the HTML output directory. Note +# that these files will be copied to the base HTML output directory. Use the +# $relpath^ marker in the HTML_HEADER and/or HTML_FOOTER files to load these +# files. In the HTML_STYLESHEET file, use the file name only. Also note that the +# files will be copied as-is; there are no commands or markers available. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_EXTRA_FILES = + +# The HTML_COLORSTYLE_HUE tag controls the color of the HTML output. Doxygen +# will adjust the colors in the style sheet and background images according to +# this color. Hue is specified as an angle on a colorwheel, see +# http://en.wikipedia.org/wiki/Hue for more information. For instance the value +# 0 represents red, 60 is yellow, 120 is green, 180 is cyan, 240 is blue, 300 +# purple, and 360 is red again. +# Minimum value: 0, maximum value: 359, default value: 220. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_HUE = 220 + +# The HTML_COLORSTYLE_SAT tag controls the purity (or saturation) of the colors +# in the HTML output. For a value of 0 the output will use grayscales only. A +# value of 255 will produce the most vivid colors. +# Minimum value: 0, maximum value: 255, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_SAT = 100 + +# The HTML_COLORSTYLE_GAMMA tag controls the gamma correction applied to the +# luminance component of the colors in the HTML output. Values below 100 +# gradually make the output lighter, whereas values above 100 make the output +# darker. The value divided by 100 is the actual gamma applied, so 80 represents +# a gamma of 0.8, The value 220 represents a gamma of 2.2, and 100 does not +# change the gamma. +# Minimum value: 40, maximum value: 240, default value: 80. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_COLORSTYLE_GAMMA = 80 + +# If the HTML_TIMESTAMP tag is set to YES then the footer of each generated HTML +# page will contain the date and time when the page was generated. Setting this +# to YES can help to show when doxygen was last run and thus if the +# documentation is up to date. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_TIMESTAMP = NO + +# If the HTML_DYNAMIC_SECTIONS tag is set to YES then the generated HTML +# documentation will contain sections that can be hidden and shown after the +# page has loaded. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_DYNAMIC_SECTIONS = NO + +# With HTML_INDEX_NUM_ENTRIES one can control the preferred number of entries +# shown in the various tree structured indices initially; the user can expand +# and collapse entries dynamically later on. Doxygen will expand the tree to +# such a level that at most the specified number of entries are visible (unless +# a fully collapsed tree already exceeds this amount). So setting the number of +# entries 1 will produce a full collapsed tree by default. 0 is a special value +# representing an infinite number of entries and will result in a full expanded +# tree by default. +# Minimum value: 0, maximum value: 9999, default value: 100. +# This tag requires that the tag GENERATE_HTML is set to YES. + +HTML_INDEX_NUM_ENTRIES = 100 + +# If the GENERATE_DOCSET tag is set to YES, additional index files will be +# generated that can be used as input for Apple's Xcode 3 integrated development +# environment (see: http://developer.apple.com/tools/xcode/), introduced with +# OSX 10.5 (Leopard). To create a documentation set, doxygen will generate a +# Makefile in the HTML output directory. Running make will produce the docset in +# that directory and running make install will install the docset in +# ~/Library/Developer/Shared/Documentation/DocSets so that Xcode will find it at +# startup. See http://developer.apple.com/tools/creatingdocsetswithdoxygen.html +# for more information. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_DOCSET = NO + +# This tag determines the name of the docset feed. A documentation feed provides +# an umbrella under which multiple documentation sets from a single provider +# (such as a company or product suite) can be grouped. +# The default value is: Doxygen generated docs. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_FEEDNAME = "Doxygen generated docs" + +# This tag specifies a string that should uniquely identify the documentation +# set bundle. This should be a reverse domain-name style string, e.g. +# com.mycompany.MyDocSet. Doxygen will append .docset to the name. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_BUNDLE_ID = org.doxygen.Project + +# The DOCSET_PUBLISHER_ID tag specifies a string that should uniquely identify +# the documentation publisher. This should be a reverse domain-name style +# string, e.g. com.mycompany.MyDocSet.documentation. +# The default value is: org.doxygen.Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_ID = org.doxygen.Publisher + +# The DOCSET_PUBLISHER_NAME tag identifies the documentation publisher. +# The default value is: Publisher. +# This tag requires that the tag GENERATE_DOCSET is set to YES. + +DOCSET_PUBLISHER_NAME = Publisher + +# If the GENERATE_HTMLHELP tag is set to YES then doxygen generates three +# additional HTML index files: index.hhp, index.hhc, and index.hhk. The +# index.hhp is a project file that can be read by Microsoft's HTML Help Workshop +# (see: http://www.microsoft.com/en-us/download/details.aspx?id=21138) on +# Windows. +# +# The HTML Help Workshop contains a compiler that can convert all HTML output +# generated by doxygen into a single compiled HTML file (.chm). Compiled HTML +# files are now used as the Windows 98 help format, and will replace the old +# Windows help format (.hlp) on all Windows platforms in the future. Compressed +# HTML files also contain an index, a table of contents, and you can search for +# words in the documentation. The HTML workshop also contains a viewer for +# compressed HTML files. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_HTMLHELP = NO + +# The CHM_FILE tag can be used to specify the file name of the resulting .chm +# file. You can add a path in front of the file if the result should not be +# written to the html output directory. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_FILE = + +# The HHC_LOCATION tag can be used to specify the location (absolute path +# including file name) of the HTML help compiler (hhc.exe). If non-empty, +# doxygen will try to run the HTML help compiler on the generated index.hhp. +# The file has to be specified with full path. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +HHC_LOCATION = + +# The GENERATE_CHI flag controls if a separate .chi index file is generated +# (YES) or that it should be included in the master .chm file (NO). +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +GENERATE_CHI = NO + +# The CHM_INDEX_ENCODING is used to encode HtmlHelp index (hhk), content (hhc) +# and project file content. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +CHM_INDEX_ENCODING = + +# The BINARY_TOC flag controls whether a binary table of contents is generated +# (YES) or a normal table of contents (NO) in the .chm file. Furthermore it +# enables the Previous and Next buttons. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +BINARY_TOC = NO + +# The TOC_EXPAND flag can be set to YES to add extra items for group members to +# the table of contents of the HTML help documentation and to the tree view. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTMLHELP is set to YES. + +TOC_EXPAND = NO + +# If the GENERATE_QHP tag is set to YES and both QHP_NAMESPACE and +# QHP_VIRTUAL_FOLDER are set, an additional index file will be generated that +# can be used as input for Qt's qhelpgenerator to generate a Qt Compressed Help +# (.qch) of the generated HTML documentation. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_QHP = NO + +# If the QHG_LOCATION tag is specified, the QCH_FILE tag can be used to specify +# the file name of the resulting .qch file. The path specified is relative to +# the HTML output folder. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QCH_FILE = + +# The QHP_NAMESPACE tag specifies the namespace to use when generating Qt Help +# Project output. For more information please see Qt Help Project / Namespace +# (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#namespace). +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_NAMESPACE = org.doxygen.Project + +# The QHP_VIRTUAL_FOLDER tag specifies the namespace to use when generating Qt +# Help Project output. For more information please see Qt Help Project / Virtual +# Folders (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#virtual- +# folders). +# The default value is: doc. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_VIRTUAL_FOLDER = doc + +# If the QHP_CUST_FILTER_NAME tag is set, it specifies the name of a custom +# filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_NAME = + +# The QHP_CUST_FILTER_ATTRS tag specifies the list of the attributes of the +# custom filter to add. For more information please see Qt Help Project / Custom +# Filters (see: http://qt-project.org/doc/qt-4.8/qthelpproject.html#custom- +# filters). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_CUST_FILTER_ATTRS = + +# The QHP_SECT_FILTER_ATTRS tag specifies the list of the attributes this +# project's filter section matches. Qt Help Project / Filter Attributes (see: +# http://qt-project.org/doc/qt-4.8/qthelpproject.html#filter-attributes). +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHP_SECT_FILTER_ATTRS = + +# The QHG_LOCATION tag can be used to specify the location of Qt's +# qhelpgenerator. If non-empty doxygen will try to run qhelpgenerator on the +# generated .qhp file. +# This tag requires that the tag GENERATE_QHP is set to YES. + +QHG_LOCATION = + +# If the GENERATE_ECLIPSEHELP tag is set to YES, additional index files will be +# generated, together with the HTML files, they form an Eclipse help plugin. To +# install this plugin and make it available under the help contents menu in +# Eclipse, the contents of the directory containing the HTML and XML files needs +# to be copied into the plugins directory of eclipse. The name of the directory +# within the plugins directory should be the same as the ECLIPSE_DOC_ID value. +# After copying Eclipse needs to be restarted before the help appears. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_ECLIPSEHELP = NO + +# A unique identifier for the Eclipse help plugin. When installing the plugin +# the directory name containing the HTML and XML files should also have this +# name. Each documentation set should have its own identifier. +# The default value is: org.doxygen.Project. +# This tag requires that the tag GENERATE_ECLIPSEHELP is set to YES. + +ECLIPSE_DOC_ID = org.doxygen.Project + +# If you want full control over the layout of the generated HTML pages it might +# be necessary to disable the index and replace it with your own. The +# DISABLE_INDEX tag can be used to turn on/off the condensed index (tabs) at top +# of each HTML page. A value of NO enables the index and the value YES disables +# it. Since the tabs in the index contain the same information as the navigation +# tree, you can set this option to YES if you also set GENERATE_TREEVIEW to YES. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +DISABLE_INDEX = NO + +# The GENERATE_TREEVIEW tag is used to specify whether a tree-like index +# structure should be generated to display hierarchical information. If the tag +# value is set to YES, a side panel will be generated containing a tree-like +# index structure (just like the one that is generated for HTML Help). For this +# to work a browser that supports JavaScript, DHTML, CSS and frames is required +# (i.e. any modern browser). Windows users are probably better off using the +# HTML help feature. Via custom style sheets (see HTML_EXTRA_STYLESHEET) one can +# further fine-tune the look of the index. As an example, the default style +# sheet generated by doxygen has an example that shows how to put an image at +# the root of the tree instead of the PROJECT_NAME. Since the tree basically has +# the same information as the tab index, you could consider setting +# DISABLE_INDEX to YES when enabling this option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +GENERATE_TREEVIEW = YES + +# The ENUM_VALUES_PER_LINE tag can be used to set the number of enum values that +# doxygen will group on one line in the generated HTML documentation. +# +# Note that a value of 0 will completely suppress the enum values from appearing +# in the overview section. +# Minimum value: 0, maximum value: 20, default value: 4. +# This tag requires that the tag GENERATE_HTML is set to YES. + +ENUM_VALUES_PER_LINE = 4 + +# If the treeview is enabled (see GENERATE_TREEVIEW) then this tag can be used +# to set the initial width (in pixels) of the frame in which the tree is shown. +# Minimum value: 0, maximum value: 1500, default value: 250. +# This tag requires that the tag GENERATE_HTML is set to YES. + +TREEVIEW_WIDTH = 250 + +# If the EXT_LINKS_IN_WINDOW option is set to YES, doxygen will open links to +# external symbols imported via tag files in a separate window. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +EXT_LINKS_IN_WINDOW = NO + +# Use this tag to change the font size of LaTeX formulas included as images in +# the HTML documentation. When you change the font size after a successful +# doxygen run you need to manually remove any form_*.png images from the HTML +# output directory to force them to be regenerated. +# Minimum value: 8, maximum value: 50, default value: 10. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_FONTSIZE = 10 + +# Use the FORMULA_TRANPARENT tag to determine whether or not the images +# generated for formulas are transparent PNGs. Transparent PNGs are not +# supported properly for IE 6.0, but are supported on all modern browsers. +# +# Note that when changing this option you need to delete any form_*.png files in +# the HTML output directory before the changes have effect. +# The default value is: YES. +# This tag requires that the tag GENERATE_HTML is set to YES. + +FORMULA_TRANSPARENT = YES + +# Enable the USE_MATHJAX option to render LaTeX formulas using MathJax (see +# http://www.mathjax.org) which uses client side Javascript for the rendering +# instead of using pre-rendered bitmaps. Use this if you do not have LaTeX +# installed or if you want to formulas look prettier in the HTML output. When +# enabled you may also need to install MathJax separately and configure the path +# to it using the MATHJAX_RELPATH option. +# The default value is: NO. +# This tag requires that the tag GENERATE_HTML is set to YES. + +USE_MATHJAX = NO + +# When MathJax is enabled you can set the default output format to be used for +# the MathJax output. See the MathJax site (see: +# http://docs.mathjax.org/en/latest/output.html) for more details. +# Possible values are: HTML-CSS (which is slower, but has the best +# compatibility), NativeMML (i.e. MathML) and SVG. +# The default value is: HTML-CSS. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_FORMAT = HTML-CSS + +# When MathJax is enabled you need to specify the location relative to the HTML +# output directory using the MATHJAX_RELPATH option. The destination directory +# should contain the MathJax.js script. For instance, if the mathjax directory +# is located at the same level as the HTML output directory, then +# MATHJAX_RELPATH should be ../mathjax. The default value points to the MathJax +# Content Delivery Network so you can quickly see the result without installing +# MathJax. However, it is strongly recommended to install a local copy of +# MathJax from http://www.mathjax.org before deployment. +# The default value is: http://cdn.mathjax.org/mathjax/latest. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_RELPATH = http://cdn.mathjax.org/mathjax/latest + +# The MATHJAX_EXTENSIONS tag can be used to specify one or more MathJax +# extension names that should be enabled during MathJax rendering. For example +# MATHJAX_EXTENSIONS = TeX/AMSmath TeX/AMSsymbols +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_EXTENSIONS = + +# The MATHJAX_CODEFILE tag can be used to specify a file with javascript pieces +# of code that will be used on startup of the MathJax code. See the MathJax site +# (see: http://docs.mathjax.org/en/latest/output.html) for more details. For an +# example see the documentation. +# This tag requires that the tag USE_MATHJAX is set to YES. + +MATHJAX_CODEFILE = + +# When the SEARCHENGINE tag is enabled doxygen will generate a search box for +# the HTML output. The underlying search engine uses javascript and DHTML and +# should work on any modern browser. Note that when using HTML help +# (GENERATE_HTMLHELP), Qt help (GENERATE_QHP), or docsets (GENERATE_DOCSET) +# there is already a search function so this one should typically be disabled. +# For large projects the javascript based search engine can be slow, then +# enabling SERVER_BASED_SEARCH may provide a better solution. It is possible to +# search using the keyboard; to jump to the search box use + S +# (what the is depends on the OS and browser, but it is typically +# , /