├── .gitmodules ├── PROJECT.name ├── debian ├── compat ├── copyright ├── source │ └── format ├── changelog ├── rules └── control ├── conanfile.txt ├── CPPLINT.cfg ├── test_package ├── example.cpp ├── CMakeLists.txt └── conanfile.py ├── libstyxe.pc.in ├── .github └── workflows │ ├── code-quality-check.yml │ ├── unit-test.yml │ └── conan-publish.yml ├── src ├── CMakeLists.txt ├── write_helper.hpp ├── parse_helper.hpp ├── errorDomain.cpp ├── encoder.cpp ├── 9p2000u.cpp ├── messageWriter.cpp ├── decoder.cpp ├── 9p2000e.cpp ├── 9p2000.cpp └── 9p2000_writer.cpp ├── test ├── CMakeLists.txt ├── testHarnes.cpp ├── testHarnes.hpp ├── main_gtest.cpp ├── ci │ ├── teamcity_gtest.h │ ├── teamcity_messages.h │ ├── teamcity_gtest.cpp │ └── teamcity_messages.cpp ├── test_9P2000L_dirReader.cpp ├── test_9PDirListingWriter.cpp ├── test_messageParser.cpp ├── test_9P2000u.cpp └── test_9P2000e.cpp ├── docs ├── examples.md └── 9p2000.e.txt ├── examples ├── CMakeLists.txt └── 9p-fuzz-parser.cpp ├── lgtm.yml ├── include └── styxe │ ├── styxe.hpp │ ├── version.hpp │ ├── errorDomain.hpp │ ├── encoder.hpp │ ├── decoder.hpp │ ├── 9p.hpp │ ├── 9p2000e.hpp │ ├── 9p2000u.hpp │ ├── messageWriter.hpp │ └── messageParser.hpp ├── .gitignore ├── CMakeLists.txt ├── conanfile.py ├── .travis.yml ├── CONTRIBUTING.md ├── configure ├── cmake └── compile_flags.cmake ├── Makefile.in ├── README.md └── LICENSE /.gitmodules: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /PROJECT.name: -------------------------------------------------------------------------------- 1 | styxe 2 | -------------------------------------------------------------------------------- /debian/compat: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | ../LICENSE -------------------------------------------------------------------------------- /debian/source/format: -------------------------------------------------------------------------------- 1 | 3.0 (native) 2 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | libstyxe (1.0.1) unstable; urgency=low 2 | 3 | * Initial package release. 4 | 5 | -- Ivan Ryabov Thu, 22 Jun 2018 12:43:38 +1100 6 | -------------------------------------------------------------------------------- /conanfile.txt: -------------------------------------------------------------------------------- 1 | [requires] 2 | libsolace/0.4.1@abbyssoul/stable 3 | 4 | [options] 5 | libsolace:shared=False 6 | 7 | [generators] 8 | cmake 9 | 10 | [build_requires] 11 | gtest/1.10.0 12 | 13 | -------------------------------------------------------------------------------- /CPPLINT.cfg: -------------------------------------------------------------------------------- 1 | set noparent 2 | linelength=120 3 | filter=-whitespace/tab,-whitespace/indent,-whitespace/blank_line,-whitespace/braces 4 | filter=-build/namespaces,-build/include_order,-build/include_what_you_use 5 | 6 | filter=-runtime/references,-runtime/explicit 7 | -------------------------------------------------------------------------------- /test_package/example.cpp: -------------------------------------------------------------------------------- 1 | #include "styxe/styxe.hpp" 2 | #include // EXIT_SUCCESS/EXIT_FAILURE 3 | 4 | 5 | int main() { 6 | return (styxe::headerSize() > 0 && 7 | styxe::headerSize() < styxe::kMaxMessageSize) 8 | ? EXIT_SUCCESS 9 | : EXIT_FAILURE; 10 | } 11 | -------------------------------------------------------------------------------- /libstyxe.pc.in: -------------------------------------------------------------------------------- 1 | prefix=@CMAKE_INSTALL_PREFIX@ 2 | exec_prefix=@CMAKE_INSTALL_PREFIX@ 3 | libdir=${exec_prefix}/@CMAKE_INSTALL_LIBDIR@ 4 | includedir=${prefix}/@CMAKE_INSTALL_INCLUDEDIR@ 5 | 6 | Name: @PROJECT_NAME@ 7 | Description: @PROJECT_DESCRIPTION@ 8 | Version: @PROJECT_VERSION@ 9 | 10 | Requires: 11 | Libs: -L${libdir} -l@PROJECT_NAME@ 12 | Cflags: -I${includedir} 13 | -------------------------------------------------------------------------------- /test_package/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.3) 2 | project(PackageTest CXX) 3 | 4 | include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) 5 | conan_basic_setup() 6 | 7 | # libstyxe requires at least C++17 8 | set(CMAKE_CXX_STANDARD 17) 9 | set(CMAKE_CXX_STANDARD_REQUIRED on) 10 | 11 | add_executable(example example.cpp) 12 | target_link_libraries(example ${CONAN_LIBS}) 13 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | #!/usr/bin/make -f 2 | # -*- makefile -*- 3 | 4 | # Uncomment this to turn on verbose mode. 5 | # export DH_VERBOSE = 1 6 | 7 | # This has to be exported to make some magic below work. 8 | export DH_OPTIONS 9 | 10 | DEB_HOST_MULTIARCH ?= $(shell dpkg-architecture -qDEB_HOST_MULTIARCH) 11 | VERSION := $(shell dpkg-parsechangelog | sed -n 's/^Version: \(.*\)-.*/\1/p') 12 | 13 | 14 | # see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/* 15 | DPKG_EXPORT_BUILDFLAGS = 1 16 | include /usr/share/dpkg/default.mk 17 | 18 | 19 | PKGDIR = $(CURDIR)/debian 20 | 21 | %: 22 | dh $@ 23 | -------------------------------------------------------------------------------- /.github/workflows/code-quality-check.yml: -------------------------------------------------------------------------------- 1 | name: "Code linter" 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@master 14 | - uses: actions/setup-python@master 15 | with: 16 | python-version: '3.7' 17 | - run: | 18 | python3 -m pip install cpplint -U 19 | 20 | - name: configure 21 | run: ./configure --enable-sanitizer 22 | 23 | - name: Cpplint 24 | run: make cpplint 25 | 26 | - name: Cppcheck 27 | run: make cppcheck 28 | -------------------------------------------------------------------------------- /src/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | 3 | set(SOURCE_FILES 4 | encoder.cpp 5 | decoder.cpp 6 | errorDomain.cpp 7 | 8 | 9p2000_writer.cpp 9 | 9p2000.cpp 10 | 9p2000e.cpp 11 | 9p2000u.cpp 12 | 9p2000L.cpp 13 | messageWriter.cpp 14 | messageParser.cpp 15 | ) 16 | 17 | add_library(${PROJECT_NAME} ${SOURCE_FILES}) 18 | target_link_libraries(${PROJECT_NAME} PUBLIC ${CONAN_LIBS}) 19 | 20 | install(TARGETS ${PROJECT_NAME} 21 | PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 22 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} 23 | ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}) 24 | -------------------------------------------------------------------------------- /test/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TEST_SOURCE_FILES 2 | main_gtest.cpp 3 | ci/teamcity_messages.cpp 4 | ci/teamcity_gtest.cpp 5 | testHarnes.cpp 6 | 7 | test_messageParser.cpp 8 | 9 | test_9P2000.cpp 10 | test_9P2000e.cpp 11 | test_9P2000u.cpp 12 | test_9P2000L.cpp 13 | 14 | test_9PDirListingWriter.cpp 15 | test_9P2000L_dirReader.cpp 16 | ) 17 | 18 | 19 | enable_testing() 20 | 21 | add_executable(test_${PROJECT_NAME} EXCLUDE_FROM_ALL ${TEST_SOURCE_FILES}) 22 | 23 | target_link_libraries(test_${PROJECT_NAME} 24 | ${PROJECT_NAME} 25 | $<$>:rt> 26 | ) 27 | 28 | add_test(NAME test_${PROJECT_NAME} 29 | COMMAND test_${PROJECT_NAME} 30 | ) 31 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: libstyxe 2 | Section: libs 3 | Priority: optional 4 | Maintainer: Ivan Ryabov 5 | Build-Depends: debhelper (>= 9), 6 | dh-autoreconf, 7 | libtool 8 | Standards-Version: 3.9.8 9 | Vcs-Browser: https://github.com/abbyssoul/libstyxe 10 | Vcs-Git: https://github.com/abbyssoul/libstyxe.git 11 | Homepage: https://github.com/abbyssoul/libstyxe 12 | 13 | 14 | Package: libstyxe-dev 15 | Architecture: any 16 | Section: libdevel 17 | Multi-Arch: same 18 | Depends: ${shlibs:Depends}, ${misc:Depends} 19 | libc6-dev 20 | libsolace-dev 21 | Description: Implementation of 9P2000 protocol -- development files 22 | This is a collection of classes to work with 9P2000 messages. 23 | . 24 | This package contains the development library and header files. 25 | -------------------------------------------------------------------------------- /docs/examples.md: -------------------------------------------------------------------------------- 1 | # Examples of using libstyxe 2 | 3 | [Examples](../examples) directory in the repository contains some examples of using this library. 4 | Please note that this library does not come with network capabilities thus examples only limited to writing and reading 5 | 9P messages from files. This functionality can be advantageous for fuzz testing the library. 6 | 7 | * [9pdecode](../examples/9pdecode.cpp) Is a useful CLI tool to read serialised 9P messages from files and print then in a human readable format. 8 | * [Corpus generator](../examples/corpus_generator.cpp) is another CLI tool to create all supported 9P messages - including 9P2000.e - and write them into files. 9 | * [fuzz-parser](../examples/fuzz-parser.cpp) is a ALF / fuzz tester entry point. It serves to fuzz-test the parser. 10 | -------------------------------------------------------------------------------- /examples/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Build examples 2 | 3 | # 9P message decode example 4 | set(EXAMPLE_9pdecode_SOURCE_FILES 9pdecode.cpp) 5 | add_executable(9pdecode ${EXAMPLE_9pdecode_SOURCE_FILES}) 6 | target_link_libraries(9pdecode ${PROJECT_NAME}) 7 | 8 | 9 | # 9P message corpus generator for fuzzer 10 | set(EXAMPLE_9p_corpus_generator_SOURCE_FILES 9p-corpus.cpp) 11 | add_executable(9p-corpus ${EXAMPLE_9p_corpus_generator_SOURCE_FILES}) 12 | target_link_libraries(9p-corpus ${PROJECT_NAME}) 13 | 14 | 15 | # 9P message parser fuzzer 16 | set(EXAMPLE_9p_fuzz_parser_SOURCE_FILES 9p-fuzz-parser.cpp) 17 | add_executable(9p-fuzz-parser ${EXAMPLE_9p_fuzz_parser_SOURCE_FILES}) 18 | target_link_libraries(9p-fuzz-parser ${PROJECT_NAME}) 19 | 20 | 21 | add_custom_target(examples 22 | DEPENDS 9pdecode 9p-corpus 9p-fuzz-parser) 23 | -------------------------------------------------------------------------------- /lgtm.yml: -------------------------------------------------------------------------------- 1 | extraction: 2 | cpp: 3 | prepare: 4 | packages: 5 | - "python3" 6 | - "python3-dev" 7 | - "python3-yaml" 8 | - "python3-wheel" 9 | - "python3-setuptools" 10 | - "python3-pip" 11 | - "python3-cryptography" 12 | - "python3-mccabe" 13 | after_prepare: 14 | - "pip3 install --user conan" 15 | - "ls ~/.local/bin" 16 | - "export PATH=$PATH:~/.local/bin" 17 | - "conan --version" 18 | - "conan profile new default --detect" 19 | - "conan remote add -i 0 abbyssoul https://api.bintray.com/conan/abbyssoul/public-conan False" 20 | 21 | configure: 22 | command: 23 | - ./configure --enable-debug 24 | -------------------------------------------------------------------------------- /test_package/conanfile.py: -------------------------------------------------------------------------------- 1 | import os 2 | from conans import ConanFile, CMake, tools 3 | 4 | 5 | class LibstyxeTestConan(ConanFile): 6 | settings = "os", "compiler", "build_type", "arch" 7 | generators = "cmake" 8 | 9 | def build(self): 10 | cmake = CMake(self) 11 | # Current dir is "test_package/build/" and CMakeLists.txt is 12 | # in "test_package" 13 | cmake.configure() 14 | cmake.build() 15 | 16 | def imports(self): 17 | self.copy("*.dll", dst="bin", src="bin") 18 | self.copy("*.dylib*", dst="bin", src="lib") 19 | self.copy('*.so*', dst='bin', src='lib') 20 | 21 | def test(self): 22 | if not tools.cross_building(self.settings): 23 | with tools.chdir("bin"): 24 | self.run(".%sexample" % os.sep, run_environment=True) 25 | -------------------------------------------------------------------------------- /include/styxe/styxe.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | #ifndef STYXE_STYXE_HPP 18 | #define STYXE_STYXE_HPP 19 | 20 | // Convenience include header 21 | 22 | #include "version.hpp" 23 | 24 | #include "9p2000.hpp" 25 | #include "9p2000e.hpp" 26 | #include "9p2000u.hpp" 27 | #include "9p2000L.hpp" 28 | 29 | #include "messageWriter.hpp" 30 | #include "messageParser.hpp" 31 | 32 | #endif // STYXE_STYXE_HPP 33 | -------------------------------------------------------------------------------- /test/testHarnes.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #include "testHarnes.hpp" 17 | #include 18 | 19 | using namespace Solace; 20 | 21 | styxe::Qid randomQid(styxe::QidType type) noexcept { 22 | std::random_device rd; 23 | std::default_random_engine randomGen{rd()}; 24 | std::linear_congruential_engine randomGen2{rd()}; 25 | 26 | return { 27 | randomGen(), 28 | randomGen2(), 29 | static_cast(type) 30 | }; 31 | } 32 | -------------------------------------------------------------------------------- /.github/workflows/unit-test.yml: -------------------------------------------------------------------------------- 1 | name: "Unit tests (Linux/MacOS)" 2 | 3 | on: 4 | push: 5 | branches: [master] 6 | pull_request: 7 | branches: [master] 8 | 9 | jobs: 10 | build: 11 | strategy: 12 | fail-fast: false 13 | matrix: 14 | # TODO: -- windows-latest 15 | os: [ubuntu-latest, macOS-latest] 16 | 17 | runs-on: ${{ matrix.os }} 18 | env: 19 | CONAN_UPLOAD: "https://api.bintray.com/conan/abbyssoul/public-conan" 20 | 21 | steps: 22 | - uses: actions/checkout@master 23 | - uses: actions/setup-python@master 24 | with: 25 | python-version: '3.7' 26 | - uses: seanmiddleditch/gha-setup-ninja@master 27 | - run: | 28 | python3 -m pip install cpplint conan -U 29 | conan profile new default --detect 30 | conan remote add abyss "${CONAN_UPLOAD}" 31 | - name: Set conan C++ ABI to 11 32 | if: runner.os != 'macOS' 33 | run: conan profile update settings.compiler.libcxx=libstdc++11 default 34 | 35 | - name: configure 36 | run: ./configure --enable-sanitizer 37 | - name: Build tests 38 | run: make tests 39 | - name: Run unit tests 40 | run: make test 41 | -------------------------------------------------------------------------------- /include/styxe/version.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | #ifndef STYXE_VERSION_HPP 18 | #define STYXE_VERSION_HPP 19 | 20 | #include 21 | 22 | 23 | #define STYXE_VERSION_MAJOR 0 24 | #define STYXE_VERSION_MINOR 7 25 | #define STYXE_VERSION_BUILD 9 26 | 27 | 28 | namespace styxe { 29 | 30 | /** 31 | * Get compiled version of the library. 32 | * @note This is not the protocol version, but the version of the library itself. 33 | * @return Build-version of the library. 34 | */ 35 | Solace::Version const& getVersion() noexcept; 36 | 37 | } // end of namespace styxe 38 | #endif // STYXE_VERSION_HPP 39 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # This file is used to ignore files which are generated 2 | # ---------------------------------------------------------------------------- 3 | bin/ 4 | build/ 5 | docs/html/* 6 | lib/ 7 | libs/*/ 8 | tools/*/ 9 | src/generated/ 10 | coverage.info 11 | Makefile 12 | 13 | < 14 | *~ 15 | *.autosave 16 | *.a 17 | *.core 18 | *.moc 19 | *.o 20 | *.obj 21 | *.orig 22 | *.rej 23 | *.so 24 | *.so.* 25 | *_pch.h.cpp 26 | *_resource.rc 27 | *.qm 28 | .#* 29 | *.*# 30 | core 31 | !core/ 32 | tags 33 | .DS_Store 34 | *.debug 35 | *.prl 36 | *.app 37 | moc_*.cpp 38 | ui_*.h 39 | qrc_*.cpp 40 | Thumbs.db 41 | *.res 42 | *.rc 43 | /.qmake.cache 44 | /.qmake.stash 45 | 46 | # qtcreator generated files 47 | *.pro.user* 48 | *.config 49 | *.creator 50 | *.creator.user 51 | *.files 52 | *.includes 53 | 54 | # xemacs temporary files 55 | *.flc 56 | 57 | # Vim temporary files 58 | .*.swp 59 | 60 | # Visual Studio generated files 61 | *.ib_pdb_index 62 | *.idb 63 | *.ilk 64 | *.pdb 65 | *.sln 66 | *.suo 67 | *.vcproj 68 | *vcproj.*.*.user 69 | *.ncb 70 | *.sdf 71 | *.opensdf 72 | *.vcxproj 73 | *vcxproj.* 74 | 75 | # MinGW generated files 76 | *.Debug 77 | *.Release 78 | 79 | # Python byte code 80 | *.pyc 81 | 82 | # Binaries 83 | # -------- 84 | *.dll 85 | *.exe 86 | 87 | 88 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.12) 2 | project(styxe) 3 | set(PROJECT_DESCRIPTION "9P2000 protocol implementation in modern C++") 4 | 5 | 6 | option(COVERAGE "Generate coverage data" OFF) 7 | option(SANITIZE "Enable 'sanitize' compiler flag" OFF) 8 | option(PROFILE "Enable profile information" OFF) 9 | 10 | # Include common compile flag 11 | include(cmake/compile_flags.cmake) 12 | include(GNUInstallDirs) 13 | include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake) 14 | conan_basic_setup() 15 | 16 | # Configure the project: 17 | configure_file(lib${PROJECT_NAME}.pc.in lib${PROJECT_NAME}.pc @ONLY) 18 | 19 | # --------------------------------- 20 | # Build project dependencies 21 | # --------------------------------- 22 | include_directories(include) 23 | 24 | add_subdirectory(src) 25 | add_subdirectory(test EXCLUDE_FROM_ALL) 26 | add_subdirectory(examples EXCLUDE_FROM_ALL) 27 | 28 | # Install include headers 29 | install(DIRECTORY include/ DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}) 30 | 31 | # Install pkgconfig descriptor 32 | install(FILES ${CMAKE_BINARY_DIR}/lib${PROJECT_NAME}.pc 33 | DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/pkgconfig) 34 | 35 | # --------------------------------- 36 | # Show build configuration status: 37 | # --------------------------------- 38 | message(STATUS, "BUILD_TYPE: ${CMAKE_BUILD_TYPE}") 39 | message(STATUS, "CXXFLAGS: ${CMAKE_CXX_FLAGS}") 40 | message(STATUS, "SANITIZE: ${SANITIZE}") 41 | message(STATUS, "COVERAGE: ${COVERAGE}") 42 | -------------------------------------------------------------------------------- /src/write_helper.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | #ifndef STYXE_INTERNAL_WRITE_HELPER_HPP 18 | #define STYXE_INTERNAL_WRITE_HELPER_HPP 19 | 20 | #include "styxe/messageWriter.hpp" 21 | 22 | 23 | namespace styxe { 24 | 25 | template 26 | RequestWriter& 27 | encode(RequestWriter& writer, MsgType const&, Args&& ...args) { 28 | (writer.messageTypeOf() 29 | << ... << args); 30 | writer.updateMessageSize(); 31 | 32 | return writer; 33 | } 34 | 35 | 36 | template 37 | ResponseWriter& 38 | encode(ResponseWriter& writer, MsgType const&, Args&& ...args) { 39 | (writer.messageTypeOf() 40 | << ... << args); 41 | writer.updateMessageSize(); 42 | 43 | return writer; 44 | } 45 | 46 | 47 | } // namespace styxe 48 | #endif // STYXE_INTERNAL_WRITE_HELPER_HPP 49 | -------------------------------------------------------------------------------- /test/testHarnes.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | #ifndef STYXE_TESTHARNES_HPP 18 | #define STYXE_TESTHARNES_HPP 19 | 20 | #include "styxe/errorDomain.hpp" 21 | #include "styxe/9p2000.hpp" 22 | 23 | #include 24 | 25 | #include 26 | 27 | 28 | styxe::Qid randomQid(styxe::QidType type = styxe::QidType::FILE) noexcept; 29 | 30 | 31 | struct TestHarnes : public ::testing::Test { 32 | 33 | // cppcheck-suppress unusedFunction 34 | void SetUp() override { 35 | _memBuf.view().fill(0xFE); 36 | 37 | _writer.rewind(); 38 | } 39 | 40 | void logFailure(styxe::Error const& e) { 41 | FAIL() << e.toString(); 42 | } 43 | 44 | Solace::MemoryManager _memManager {styxe::kMaxMessageSize}; 45 | Solace::MemoryResource _memBuf{_memManager.allocate(styxe::kMaxMessageSize).unwrap()}; 46 | Solace::ByteWriter _writer{_memBuf}; 47 | 48 | }; 49 | 50 | 51 | 52 | #endif // STYXE_TESTHARNES_HPP 53 | -------------------------------------------------------------------------------- /src/parse_helper.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | #ifndef STYXE_INTERNAL_PARSE_HELPER_HPP 18 | #define STYXE_INTERNAL_PARSE_HELPER_HPP 19 | 20 | #include "styxe/errorDomain.hpp" 21 | #include "styxe/decoder.hpp" 22 | 23 | #include 24 | #include 25 | 26 | namespace styxe { 27 | 28 | Solace::Result 29 | inline decode(Solace::ByteReader& data) noexcept { 30 | return Solace::Result{Solace::types::okTag, data}; 31 | } 32 | 33 | template 34 | Solace::Result 35 | decode(Solace::ByteReader& data, Args&& ...args) { 36 | Decoder decoder{data}; 37 | auto result = (decoder >> ... >> args); 38 | if (!result) return result.moveError(); 39 | 40 | return Solace::Result{Solace::types::okTag, data}; 41 | } 42 | 43 | } // namespace styxe 44 | #endif // STYXE_INTERNAL_PARSE_HELPER_HPP 45 | -------------------------------------------------------------------------------- /test/main_gtest.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /******************************************************************************* 17 | * libstyxe Unit Test Suit 18 | * @file: GTest entry point 19 | * @author: soultaker 20 | * 21 | *******************************************************************************/ 22 | #include "gtest/gtest.h" 23 | #include "ci/teamcity_gtest.h" 24 | 25 | 26 | int main(int argc, char **argv) { 27 | std::ios_base::sync_with_stdio(false); 28 | std::cin.tie(nullptr); 29 | 30 | testing::InitGoogleTest(&argc, argv); 31 | 32 | if (jetbrains::teamcity::underTeamcity()) { 33 | auto& listeners = testing::UnitTest::GetInstance()->listeners(); 34 | 35 | // Add unique flowId parameter if you want to run test processes in parallel 36 | // See http://confluence.jetbrains.net/display/TCD6/Build+Script+Interaction+with+TeamCity#BuildScriptInteractionwithTeamCity-MessageFlowId 37 | listeners.Append(new jetbrains::teamcity::TeamcityGoogleTestEventListener()); 38 | } 39 | 40 | return RUN_ALL_TESTS(); 41 | } 42 | -------------------------------------------------------------------------------- /include/styxe/errorDomain.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | #ifndef STYXE_ERRORDOMAIN_HPP 18 | #define STYXE_ERRORDOMAIN_HPP 19 | 20 | #include 21 | #include 22 | 23 | 24 | namespace styxe { 25 | 26 | /** Error type used to represent runtime error by the library */ 27 | using Error = Solace::Error; 28 | 29 | /// Result type shortrhand 30 | template 31 | using Result = Solace::Result; 32 | 33 | 34 | /** Error category for protocol specific errors */ 35 | extern Solace::AtomValue const kProtocolErrorCatergory; 36 | 37 | /** 38 | * Enum class for protocol error codes. 39 | */ 40 | enum class CannedError : int { 41 | UnsupportedProtocolVersion = 0, 42 | UnsupportedMessageType, 43 | IllFormedHeader, 44 | IllFormedHeader_FrameTooShort, 45 | IllFormedHeader_TooBig, 46 | NotEnoughData, 47 | MoreThenExpectedData, 48 | }; 49 | 50 | /** 51 | * Get canned error from the protocol error code. 52 | * @param errorId Error code of the canned error. 53 | * @return Error object for the error category 54 | */ 55 | Error 56 | getCannedError(CannedError errorId) noexcept; 57 | 58 | 59 | } // end of namespace styxe 60 | #endif // STYXE_ERRORDOMAIN_HPP 61 | -------------------------------------------------------------------------------- /test/ci/teamcity_gtest.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 Paul Shmakov 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | #ifndef H_TEAMCITY_GTEST 18 | #define H_TEAMCITY_GTEST 19 | 20 | #include 21 | #include 22 | 23 | #include "teamcity_messages.h" 24 | 25 | namespace jetbrains { 26 | namespace teamcity { 27 | 28 | class TeamcityGoogleTestEventListener: public ::testing::EmptyTestEventListener { 29 | public: 30 | TeamcityGoogleTestEventListener(const std::string& flowid); 31 | TeamcityGoogleTestEventListener(); 32 | 33 | // Fired before the test case starts. 34 | virtual void OnTestCaseStart(const ::testing::TestCase& test_case); 35 | // Fired before the test starts. 36 | virtual void OnTestStart(const ::testing::TestInfo& test_info); 37 | // Fired after the test ends. 38 | virtual void OnTestEnd(const ::testing::TestInfo& test_info); 39 | // Fired after the test case ends. 40 | virtual void OnTestCaseEnd(const ::testing::TestCase& test_case); 41 | 42 | private: 43 | TeamcityMessages messages; 44 | std::string flowid; 45 | 46 | // Prevent copying. 47 | TeamcityGoogleTestEventListener(const TeamcityGoogleTestEventListener&); 48 | void operator =(const TeamcityGoogleTestEventListener&); 49 | }; 50 | 51 | } 52 | } 53 | 54 | #endif /* H_TEAMCITY_GTEST */ 55 | -------------------------------------------------------------------------------- /src/errorDomain.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "styxe/errorDomain.hpp" 18 | 19 | 20 | using namespace Solace; 21 | using namespace styxe; 22 | 23 | 24 | AtomValue const 25 | styxe::kProtocolErrorCatergory = atom("styxe"); 26 | 27 | 28 | #define CANNE(id, msg) \ 29 | Error(kProtocolErrorCatergory, static_cast(id), msg) 30 | 31 | 32 | static Error const kCannedErrors[] = { 33 | CANNE(CannedError::UnsupportedProtocolVersion, "Unsupported protocol version"), 34 | CANNE(CannedError::UnsupportedMessageType, "Ill-formed message: Unsupported message type"), 35 | CANNE(CannedError::IllFormedHeader, "Ill-formed message header. Not enough data to read a header"), 36 | CANNE(CannedError::IllFormedHeader_FrameTooShort, "Ill-formed message: Declared frame size less than header"), 37 | CANNE(CannedError::IllFormedHeader_TooBig, "Ill-formed message: Declared frame size greater than negotiated one"), 38 | 39 | CANNE(CannedError::NotEnoughData, "Ill-formed message: Declared frame size larger than message data received"), 40 | CANNE(CannedError::MoreThenExpectedData, "Ill-formed message: Declared frame size less than message data received"), 41 | }; 42 | 43 | 44 | Error 45 | styxe::getCannedError(CannedError errorId) noexcept { 46 | return kCannedErrors[static_cast(errorId)]; 47 | } 48 | -------------------------------------------------------------------------------- /test/ci/teamcity_messages.h: -------------------------------------------------------------------------------- 1 | /* Copyright 2011 JetBrains s.r.o. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * $Revision: 88625 $ 16 | */ 17 | 18 | #ifndef H_TEAMCITY_MESSAGES 19 | #define H_TEAMCITY_MESSAGES 20 | 21 | #include 22 | #include 23 | 24 | namespace jetbrains { 25 | namespace teamcity { 26 | 27 | std::string getFlowIdFromEnvironment(); 28 | bool underTeamcity(); 29 | 30 | class TeamcityMessages { 31 | std::ostream *m_out; 32 | 33 | protected: 34 | std::string escape(std::string s); 35 | 36 | void openMsg(const std::string &name); 37 | void writeProperty(std::string name, std::string value); 38 | void closeMsg(); 39 | 40 | public: 41 | static const bool StdErr = true; 42 | static const bool StdOut = false; 43 | 44 | TeamcityMessages(); 45 | 46 | void setOutput(std::ostream &); 47 | 48 | void suiteStarted(std::string name, std::string flowid = std::string()); 49 | void suiteFinished(std::string name, std::string flowid = std::string()); 50 | 51 | void testStarted(std::string name, std::string flowid = std::string(), bool captureStandardOutput = false); 52 | void testFailed(std::string name, std::string message, std::string details, std::string flowid = std::string()); 53 | void testIgnored(std::string name, std::string message, std::string flowid = std::string()); 54 | void testOutput(std::string name, std::string output, std::string flowid, bool isStdErr = StdOut); 55 | void testFinished(std::string name, int durationMs = -1, std::string flowid = std::string()); 56 | }; 57 | 58 | } 59 | } 60 | 61 | #endif /* H_TEAMCITY_MESSAGES */ 62 | -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | """Conan recipe package for libstyxe 2 | """ 3 | from conans import CMake, ConanFile 4 | from conans.errors import ConanInvalidConfiguration 5 | from conans.model.version import Version 6 | 7 | 8 | class LibstyxeConan(ConanFile): 9 | name = "libstyxe" 10 | description = "9P2000(.u/.l/.e) file protocol parser implementation" 11 | license = "Apache-2.0" 12 | author = "Ivan Ryabov " 13 | url = "https://github.com/abbyssoul/%s.git" % name 14 | homepage = "https://github.com/abbyssoul/%s" % name 15 | topics = ("9P", "9P2000", "plan9", "styx", "protocol", "parser", "distributed", "distributed-file-system", "9pfs", "Modern C++") 16 | 17 | settings = "os", "compiler", "build_type", "arch" 18 | options = { 19 | "shared": [True, False], 20 | "fPIC": [True, False] 21 | } 22 | default_options = {"shared": False, "fPIC": True} 23 | generators = "cmake" 24 | build_requires = "gtest/1.10.0" 25 | requires = "libsolace/0.4.1@abbyssoul/stable" 26 | 27 | scm = { 28 | "type": "git", 29 | "subfolder": name, 30 | "url": "auto", 31 | "revision": "auto" 32 | } 33 | 34 | @property 35 | def _supported_cppstd(self): 36 | return ["17", "gnu17", "20", "gnu20"] 37 | 38 | @property 39 | def _source_subfolder(self): 40 | return self.name 41 | 42 | def config_options(self): 43 | compiler_version = Version(str(self.settings.compiler.version)) 44 | 45 | if self.settings.os == "Windows": 46 | del self.options.fPIC 47 | # Exclude compilers that claims to support C++17 but do not in practice 48 | if (self.settings.compiler == "gcc" and compiler_version < "7") or \ 49 | (self.settings.compiler == "clang" and compiler_version < "5") or \ 50 | (self.settings.compiler == "apple-clang" and compiler_version < "9"): 51 | raise ConanInvalidConfiguration("This library requires C++17 or higher support standard. {} {} is not supported".format(self.settings.compiler, self.settings.compiler.version)) 52 | if self.settings.compiler.cppstd and not self.settings.compiler.cppstd in self._supported_cppstd: 53 | raise ConanInvalidConfiguration("This library requires c++17 standard or higher. {} required".format(self.settings.compiler.cppstd)) 54 | 55 | def _configure_cmake(self): 56 | cmake = CMake(self, parallel=True) 57 | cmake.definitions["PKG_CONFIG"] = "OFF" 58 | cmake.configure(source_folder=self._source_subfolder) 59 | return cmake 60 | 61 | def build(self): 62 | cmake = self._configure_cmake() 63 | cmake.build() 64 | 65 | def package(self): 66 | cmake = self._configure_cmake() 67 | cmake.install() 68 | self.copy(pattern="LICENSE", dst="licenses", src=self._source_subfolder) 69 | 70 | def package_info(self): 71 | self.cpp_info.libs = ["styxe"] 72 | if self.settings.os == "Linux": 73 | self.cpp_info.libs.append("m") 74 | -------------------------------------------------------------------------------- /src/encoder.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "styxe/encoder.hpp" 18 | #include "styxe/9p.hpp" 19 | 20 | 21 | #include // narrow_cast 22 | #include 23 | 24 | 25 | 26 | using namespace Solace; 27 | using namespace styxe; 28 | 29 | 30 | 31 | size_type 32 | styxe::protocolSize(uint8 const& value) noexcept { 33 | return sizeof(value); 34 | } 35 | 36 | size_type 37 | styxe::protocolSize(uint16 const& value) noexcept { 38 | return sizeof(value); 39 | } 40 | 41 | size_type 42 | styxe::protocolSize(uint32 const& value) noexcept { 43 | return sizeof(value); 44 | } 45 | 46 | size_type 47 | styxe::protocolSize(uint64 const& value) noexcept { 48 | return sizeof(value); 49 | } 50 | 51 | size_type 52 | styxe::protocolSize(StringView const& value) noexcept { 53 | return sizeof(var_datum_size_type) + // Space for string var size 54 | value.size(); // Space for the actual string bytes 55 | } 56 | 57 | 58 | size_type 59 | styxe::protocolSize(MemoryView const& value) noexcept { 60 | constexpr auto const maxSize = static_cast(std::numeric_limits::max()); 61 | auto const valueSize = value.size(); 62 | assertIndexInRange(valueSize, maxSize, "protocolSize(:MemoryView)"); 63 | 64 | return sizeof(size_type) + // Var number of elements 65 | narrow_cast(valueSize); 66 | } 67 | 68 | 69 | 70 | 71 | Encoder& 72 | styxe::operator<< (Encoder& encoder, uint8 value) { 73 | encoder.buffer().writeLE(value); 74 | return encoder; 75 | } 76 | 77 | Encoder& styxe::operator<< (Encoder& encoder, uint16 value) { 78 | encoder.buffer().writeLE(value); 79 | return encoder; 80 | } 81 | 82 | Encoder& styxe::operator<< (Encoder& encoder, uint32 value) { 83 | encoder.buffer().writeLE(value); 84 | return encoder; 85 | } 86 | 87 | Encoder& styxe::operator<< (Encoder& encoder, uint64 value) { 88 | encoder.buffer().writeLE(value); 89 | return encoder; 90 | } 91 | 92 | Encoder& styxe::operator<< (Encoder& encoder, StringView str) { 93 | encoder << str.size(); 94 | encoder.buffer().write(str.view()); 95 | 96 | return encoder; 97 | } 98 | 99 | 100 | Encoder& styxe::operator<< (Encoder& encoder, MemoryView data) { 101 | encoder << narrow_cast(data.size()); 102 | encoder.buffer().write(data); 103 | 104 | return encoder; 105 | } 106 | -------------------------------------------------------------------------------- /test/ci/teamcity_gtest.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2015 Paul Shmakov 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | */ 16 | 17 | #include "teamcity_gtest.h" 18 | 19 | namespace jetbrains { 20 | namespace teamcity { 21 | 22 | using namespace testing; 23 | 24 | TeamcityGoogleTestEventListener::TeamcityGoogleTestEventListener() { 25 | flowid = getFlowIdFromEnvironment(); 26 | } 27 | 28 | TeamcityGoogleTestEventListener::TeamcityGoogleTestEventListener(const std::string& flowid_) 29 | : flowid(flowid_) { 30 | } 31 | 32 | // Fired before the test case starts. 33 | void TeamcityGoogleTestEventListener::OnTestCaseStart(const TestCase& test_case) { 34 | messages.suiteStarted(test_case.name(), flowid); 35 | } 36 | 37 | // Fired before the test starts. 38 | void TeamcityGoogleTestEventListener::OnTestStart(const TestInfo& test_info) { 39 | messages.testStarted(test_info.name(), flowid); 40 | } 41 | 42 | // Fired after the test ends. 43 | void TeamcityGoogleTestEventListener::OnTestEnd(const TestInfo& test_info) { 44 | const TestResult* result = test_info.result(); 45 | if (result->Failed()) { 46 | std::string message; 47 | std::string details; 48 | for (int i = 0; i < result->total_part_count(); ++i) { 49 | const TestPartResult& partResult = result->GetTestPartResult(i); 50 | if (partResult.passed()) { 51 | continue; 52 | } 53 | 54 | if (message.empty()) { 55 | message = partResult.summary(); 56 | } 57 | 58 | if (!details.empty()) { 59 | details.append("\n"); 60 | } 61 | details.append(partResult.message()); 62 | 63 | if (partResult.file_name() && partResult.line_number() >= 0) { 64 | std::stringstream ss; 65 | ss << "\n at " << partResult.file_name() << ":" << partResult.line_number(); 66 | details.append(ss.str()); 67 | } 68 | } 69 | 70 | messages.testFailed( 71 | test_info.name(), 72 | !message.empty() ? message : "failed", 73 | details, 74 | flowid 75 | ); 76 | } 77 | messages.testFinished(test_info.name(), static_cast(result->elapsed_time()), flowid); 78 | } 79 | 80 | // Fired after the test case ends. 81 | void TeamcityGoogleTestEventListener::OnTestCaseEnd(const TestCase& test_case) { 82 | messages.suiteFinished(test_case.name(), flowid); 83 | } 84 | 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /examples/9p-fuzz-parser.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "styxe/styxe.hpp" 18 | 19 | #include 20 | 21 | #include 22 | #include 23 | #include 24 | #include 25 | 26 | 27 | using namespace Solace; 28 | using namespace styxe; 29 | 30 | 31 | void dispayRequest(RequestMessage&&) { /*no-op*/ } 32 | void dispayResponse(ResponseMessage&&) { /*no-op*/ } 33 | 34 | 35 | extern "C" 36 | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 37 | ByteReader reader{wrapMemory(data, size)}; 38 | 39 | // Case1: parse message header 40 | auto maybeReqParser = createRequestParser(_9P2000E::kProtocolVersion, size); 41 | if (!maybeReqParser) { 42 | std::cerr << "Failed to create parser: " << maybeReqParser.getError() << std::endl; 43 | return EXIT_FAILURE; 44 | } 45 | 46 | auto maybeRespParser = createResponseParser(_9P2000E::kProtocolVersion, size); 47 | if (!maybeRespParser) { 48 | std::cerr << "Failed to create parser: " << maybeRespParser.getError() << std::endl; 49 | return EXIT_FAILURE; 50 | } 51 | 52 | 53 | auto& tparser = *maybeReqParser; 54 | auto& rparser = *maybeRespParser; 55 | 56 | parseMessageHeader(reader) 57 | .then([&](MessageHeader&& header) { 58 | bool const isRequest = ((header.type % 2) == 0); 59 | if (isRequest) { 60 | tparser.parseRequest(header, reader) 61 | .then(dispayRequest); 62 | } else { 63 | rparser.parseResponse(header, reader) 64 | .then(dispayResponse); 65 | } 66 | }); 67 | 68 | return 0; // Non-zero return values are reserved for future use. 69 | } 70 | 71 | 72 | inline 73 | void readDataAndTest(std::istream& in) { 74 | std::vector buf; 75 | buf.reserve(kMaxMessageSize); 76 | 77 | in.read(reinterpret_cast(buf.data()), buf.size()); 78 | size_t const got = in.gcount(); 79 | 80 | buf.resize(got); 81 | buf.shrink_to_fit(); 82 | 83 | LLVMFuzzerTestOneInput(buf.data(), got); 84 | } 85 | 86 | 87 | 88 | #if defined(__AFL_COMPILER) 89 | 90 | int main(int argc, char* argv[]) { 91 | #if defined(__AFL_LOOP) 92 | while (__AFL_LOOP(1000)) 93 | #endif 94 | { 95 | readDataAndTest(std::cin); 96 | } 97 | } 98 | 99 | #else // __AFL_COMPILER 100 | 101 | int main(int argc, const char **argv) { 102 | if (argc < 2) { 103 | std::cout << "Usage: " << argv[0] << "fuzz ..." << std::endl; 104 | return EXIT_FAILURE; 105 | } 106 | 107 | for (int i = 1; i < argc; ++i) { 108 | std::ifstream input(argv[i]); 109 | readDataAndTest(input); 110 | } 111 | 112 | return EXIT_SUCCESS; 113 | } 114 | #endif // __AFL_COMPILER 115 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: focal 2 | language: cpp 3 | compiler: 4 | - clang 5 | - gcc 6 | os: 7 | - linux 8 | env: 9 | global: 10 | # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created 11 | # via the "travis encrypt" command using the project repo's public key 12 | - secure: "V+hzBkW1v7lMoq+MQ2Q232b4vun0FMoQIs7d+en5npZygjPs2l+coxg20Cx2w+sCDOBUC4yHBb7y9snMJIhBmwJTgokq9Ia4LEeC2lFe1juh3NmPPs1Ul4CykGQNJROApK/vpKkJ9lx8rBmlJB21iqLWOHYd9MWItnVnbWaPFDL5rX7C7SFiOv2/C+qpMGl+Ro9DXM8C1kq0tat7ZYUZLJUZHp0gkP/2p3GQ4/w6wPXA1Jb3SPiBuDK9zidln1EqI6dP9JTDSnmYmV/qy5ZCIHkMyGtWTcrfb1JJ+Es5V8IbAjoaDav914mw3y6QmQ237CFRUvkUU+eadZTk7SWeB4lfBnm8Fvn4otO5BC1D+WhlqG29W/t0zFa7Azqe/PdxbvBsrBqOAuLzUeYnqxe0XaUwoWlp48LPwkYUBcqViWsz1RvmhhTE7WEw7CcpoOz+zjPOszFgLyLQ88+NF5twakV7oUeG/4WNTV1uoAq/yqIutXLLYwN4yNGYn75Bii/j6kV/4O34vsvbqu1XQEVXMBsHPptgECxqxjIZ5faJNpkCDz+XS8BliKt4GBm+Vm3tBhQ+wrquZPrNTwqB25Ll70XxPu0LQ9lcg+entGv9+goJWiAK67D/vu3Qa5LYdOo8lhxd7ne5wYLUkGhfTmdsuywtyhN1gmIrpK1pP8AZRq0=" 13 | addons: 14 | apt: 15 | update: true 16 | sources: 17 | - ubuntu-toolchain-r-test 18 | packages: 19 | - "pkg-config" 20 | - "python3" 21 | - "python3-dev" 22 | - "python3-yaml" 23 | - "python3-wheel" 24 | - "python3-setuptools" 25 | - "python3-cryptography" 26 | - "python3-pip" 27 | - gcc-7 28 | - g++-7 29 | - ninja-build 30 | - cmake 31 | - doxygen 32 | - libc6-dbg # needed by Valgrind 33 | - valgrind 34 | - lcov 35 | - curl 36 | coverity_scan: 37 | project: 38 | name: "abbyssoul/libstyxe" 39 | description: "9P2000 protocol implementation" 40 | notification_email: abbyssoul@gmail.com 41 | build_command_prepend: "./configure --enable-coverage --disable-sanitizer" 42 | build_command: make coverage_report 43 | branch_pattern: coverity_scan 44 | 45 | before_install: 46 | - echo -n | openssl s_client -connect https://scan.coverity.com:443 | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' | sudo tee -a /etc/ssl/certs/ca- 47 | - sudo -H pip3 install --upgrade pip 48 | - sudo -H pip3 install cpplint cpp-coveralls 49 | - sudo -H pip3 install conan --upgrade 50 | - gem install coveralls-lcov 51 | 52 | install: 53 | - gcov --version 54 | - lcov --version 55 | - sudo update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 90 --slave /usr/bin/g++ g++ /usr/bin/g++-7 56 | - sudo update-alternatives --install /usr/bin/gcov gcov /usr/bin/gcov-7 90 57 | - echo $CXX 58 | - $CXX --version 59 | - ld --version 60 | - conan --version 61 | - conan profile new default --detect 62 | - conan profile update settings.compiler.libcxx=libstdc++11 default 63 | - conan remote add abyss https://api.bintray.com/conan/abbyssoul/public-conan 64 | 65 | script: 66 | - ./configure --enable-debug --enable-coverage --enable-sanitizer && make clean; 67 | - if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then make codecheck; fi 68 | - if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then make test; fi 69 | # Disabled valgrind build as std::random_device causes failure on Valgrind-3.11.0 avaliable in travisCI 70 | #- if [ "${COVERITY_SCAN_BRANCH}" != 1 ]; then ./configure --disable-sanitizer --enable-debug && make clean && make verify ; fi 71 | 72 | after_success: 73 | - make coverage_report 74 | - bash <(curl -s https://codecov.io/bash) || echo "Codecov did not collect coverage reports" 75 | - coveralls-lcov --repo-token ${COVERALLS_TOKEN} coverage.info # uploads to coveralls 76 | -------------------------------------------------------------------------------- /src/9p2000u.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "styxe/9p2000u.hpp" 18 | 19 | #include "parse_helper.hpp" 20 | #include "write_helper.hpp" 21 | 22 | using namespace Solace; 23 | using namespace styxe; 24 | 25 | 26 | const StringLiteral _9P2000U::kProtocolVersion{"9P2000.u"}; 27 | const uint32 _9P2000U::kNonUid{static_cast(~0)}; 28 | 29 | 30 | RequestWriter& 31 | styxe::operator<< (RequestWriter& writer, _9P2000U::Request::Auth const& message) { 32 | return encode(writer, message, message.afid, message.uname, message.aname, message.n_uname); 33 | } 34 | 35 | 36 | RequestWriter& 37 | styxe::operator<< (RequestWriter& writer, _9P2000U::Request::Attach const& message) { 38 | return encode(writer, message, 39 | message.fid, 40 | message.afid, 41 | message.uname, 42 | message.aname, 43 | message.n_uname); 44 | } 45 | 46 | 47 | RequestWriter& 48 | styxe::operator<< (RequestWriter& writer, _9P2000U::Request::Create const& message) { 49 | return encode(writer, message, 50 | message.fid, 51 | message.name, 52 | message.perm, 53 | message.mode.mode, 54 | message.extension); 55 | } 56 | 57 | RequestWriter& 58 | styxe::operator<< (RequestWriter& writer, _9P2000U::Request::WStat const& message) { 59 | return encode(writer, message, message.fid, message.stat); 60 | } 61 | 62 | 63 | ResponseWriter& 64 | styxe::operator<< (ResponseWriter& writer, _9P2000U::Response::Error const& message) { 65 | return encode(writer, message, message.ename, message.errcode); 66 | } 67 | 68 | 69 | ResponseWriter& 70 | styxe::operator<< (ResponseWriter& writer, _9P2000U::Response::Stat const& message) { 71 | return encode(writer, message, message.dummySize, message.data); 72 | } 73 | 74 | 75 | styxe::Result 76 | styxe::operator>> (ByteReader& data, _9P2000U::Request::Auth& dest) { 77 | return (data >> static_cast(dest)) 78 | .then([&](ByteReader& reader) { 79 | return decode(reader, dest.n_uname); 80 | }); 81 | } 82 | 83 | 84 | styxe::Result 85 | styxe::operator>> (ByteReader& data, _9P2000U::Request::Attach& dest) { 86 | return (data >> static_cast(dest)) 87 | .then([&](ByteReader& reader) { 88 | return decode(reader, dest.n_uname); 89 | }); 90 | } 91 | 92 | styxe::Result 93 | styxe::operator>> (ByteReader& data, _9P2000U::Request::Create& dest) { 94 | return (data >> static_cast(dest)) 95 | .then([&](ByteReader& reader) { 96 | return decode(reader, dest.extension); 97 | }); 98 | } 99 | 100 | 101 | styxe::Result 102 | styxe::operator>> (ByteReader& data, _9P2000U::Request::WStat& dest) { 103 | return decode(data, dest.fid, dest.stat); 104 | } 105 | 106 | 107 | styxe::Result 108 | styxe::operator>> (ByteReader& data, _9P2000U::Response::Error& dest) { 109 | return (data >> static_cast(dest)) 110 | .then([&](ByteReader& reader) { 111 | return decode(reader, dest.errcode); 112 | }); 113 | } 114 | 115 | styxe::Result 116 | styxe::operator>> (ByteReader& data, _9P2000U::Response::Stat& dest) { 117 | return decode(data, dest.dummySize, dest.data); 118 | } 119 | -------------------------------------------------------------------------------- /src/messageWriter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "styxe/messageWriter.hpp" 18 | 19 | 20 | using namespace Solace; 21 | using namespace styxe; 22 | 23 | 24 | void MessageWriterBase::updateMessageSize() { 25 | auto const finalPos = _encoder.buffer().position(); 26 | auto const messageSize = finalPos - _pos; // Re-compute actual message size 27 | if (finalPos == _pos || _header.messageSize == messageSize) { // Nothing to do 28 | return; 29 | } 30 | 31 | // Update message size filed in the header 32 | _header.messageSize = narrow_cast(messageSize); 33 | 34 | auto& buffer = _encoder.buffer(); 35 | buffer.position(_pos); // Reset output stream to the start position 36 | 37 | // Write a new header with updated message size 38 | _encoder << _header; 39 | 40 | // Reset output stream to the current position 41 | buffer.position(finalPos); 42 | } 43 | 44 | 45 | ByteWriter& 46 | MessageWriterBase::build() { 47 | updateMessageSize(); 48 | 49 | return _encoder.buffer().flip(); 50 | } 51 | 52 | 53 | void 54 | PartialPathWriter::segment(Solace::StringView value) { 55 | auto& buffer = _writer.encoder().buffer(); 56 | auto const finalPos = buffer.position(); 57 | buffer.position(_segmentsPos); // Reset output stream to the start position 58 | 59 | _nSegments += 1; 60 | _writer.encoder() << _nSegments; 61 | 62 | buffer.position(finalPos); // Reset output stream to the final position 63 | _writer.encoder() << value; 64 | _writer.updateMessageSize(); 65 | } 66 | 67 | 68 | MutableMemoryView 69 | PartialDataWriter::viewRemainder() { 70 | auto& buffer = _writer.encoder().buffer(); 71 | return buffer.viewRemaining(); 72 | } 73 | 74 | 75 | MessageWriterBase& 76 | PartialDataWriter::update(size_type dataSize) { 77 | auto& buffer = _writer.encoder().buffer(); 78 | _dataSize = dataSize; 79 | 80 | buffer.position(_segmentsPos); // Reset output stream to the start position 81 | _writer.encoder() << _dataSize; 82 | buffer.advance(_dataSize); // TODO(abbyssoul): error check, new dataSize can be over the buffer capacity 83 | _writer.updateMessageSize(); 84 | 85 | return _writer; 86 | } 87 | 88 | 89 | MessageWriterBase& 90 | PartialDataWriter::data(MemoryView value) { 91 | auto& buffer = _writer.encoder().buffer(); 92 | buffer.write(value); 93 | 94 | return update(_dataSize + value.size()); 95 | } 96 | 97 | 98 | MessageWriterBase& 99 | PartialStringWriter::string(Solace::StringView value) { 100 | auto& buffer = _writer.encoder().buffer(); 101 | 102 | _dataSize += value.size(); 103 | buffer.write(value.view()); 104 | auto const finalPos = buffer.position(); 105 | 106 | buffer.position(_segmentsPos); // Reset output stream to the start position 107 | _writer.encoder() << _dataSize; 108 | buffer.position(finalPos); 109 | _writer.updateMessageSize(); 110 | 111 | return _writer; 112 | } 113 | 114 | 115 | RequestWriter& 116 | PathDataWriter::data(Solace::MemoryView value) { 117 | _writer.encoder() << value; 118 | _writer.updateMessageSize(); 119 | 120 | return _writer; 121 | } 122 | 123 | 124 | 125 | PartialPathWriter&& 126 | styxe::operator<< (PartialPathWriter&& writer, StringView segment) { 127 | writer.segment(segment); 128 | return mv(writer); 129 | } 130 | -------------------------------------------------------------------------------- /src/decoder.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "styxe/decoder.hpp" 18 | 19 | 20 | using namespace Solace; 21 | using namespace styxe; 22 | 23 | 24 | Result 25 | styxe::operator>> (Decoder& decoder, uint8& dest) { 26 | auto result = decoder.buffer().readLE(dest); 27 | if (!result) return result.moveError(); 28 | 29 | return Result{types::okTag, decoder}; 30 | } 31 | 32 | 33 | Result 34 | styxe::operator>> (Decoder& decoder, uint16& dest) { 35 | auto result = decoder.buffer().readLE(dest); 36 | if (!result) return result.moveError(); 37 | 38 | return Result{types::okTag, decoder}; 39 | } 40 | 41 | Result 42 | styxe::operator>> (Decoder& decoder, uint32& dest) { 43 | auto result = decoder.buffer().readLE(dest); 44 | if (!result) return result.moveError(); 45 | 46 | return Result{types::okTag, decoder}; 47 | } 48 | 49 | Result 50 | styxe::operator>> (Decoder& decoder, uint64& dest) { 51 | auto result = decoder.buffer().readLE(dest); 52 | if (!result) return result.moveError(); 53 | 54 | return Result{types::okTag, decoder}; 55 | } 56 | 57 | Result 58 | styxe::operator>> (Decoder& decoder, StringView& dest) { 59 | auto& buffer = decoder.buffer(); 60 | 61 | uint16 dataSize = 0; 62 | auto result = buffer.readLE(dataSize) 63 | .then([&]() -> Result { 64 | auto b = reinterpret_cast(buffer.viewRemaining().begin()); 65 | // Note: it is possible there is actully less then `dataSize` data in the buffer 66 | StringView view{b, dataSize}; 67 | 68 | return buffer.advance(dataSize) 69 | .then([&dest, &view]() { 70 | // Note: only assign if advanced by `dataSize` bytes succesfully 71 | dest = view; 72 | }); 73 | }); 74 | 75 | if (!result) return result.moveError(); 76 | 77 | return Result{types::okTag, decoder}; 78 | } 79 | 80 | 81 | Result 82 | styxe::operator>> (Decoder& decoder, MemoryView& data) { 83 | auto& buffer = decoder.buffer(); 84 | styxe::size_type dataSize = 0; 85 | 86 | // Read size of the following data. 87 | auto result = buffer.readLE(dataSize) 88 | .then([&]() { 89 | data = buffer.viewRemaining().slice(0, dataSize); 90 | return buffer.advance(dataSize); 91 | }); 92 | if (!result) return result.moveError(); 93 | 94 | return Result{types::okTag, decoder}; 95 | } 96 | 97 | 98 | Result 99 | styxe::operator>> (Decoder& decoder, WalkPath& path) { 100 | auto& buffer = decoder.buffer(); 101 | WalkPath::size_type componentsCount = 0; 102 | 103 | auto result = buffer.readLE(componentsCount) 104 | .then([&]() { 105 | path = WalkPath{componentsCount, buffer.viewRemaining()}; 106 | // Advance the byteReader: 107 | ByteReader::size_type skip = 0; 108 | for (auto segment : path) { 109 | skip += sizeof(styxe::var_datum_size_type) + segment.size(); 110 | } 111 | 112 | return buffer.advance(skip); 113 | }); 114 | 115 | if (!result) return result.moveError(); 116 | 117 | return Result{types::okTag, decoder}; 118 | } 119 | 120 | 121 | -------------------------------------------------------------------------------- /include/styxe/encoder.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | #ifndef STYXE_ENCODER_HPP 18 | #define STYXE_ENCODER_HPP 19 | 20 | 21 | #include 22 | #include 23 | 24 | #include // mv<> 25 | #include 26 | #include 27 | 28 | 29 | namespace styxe { 30 | 31 | /** Network protocol uses fixed width int32 to represent size of data in bytes */ 32 | using size_type = Solace::uint32; 33 | 34 | /** 35 | * Helper class to encode data into the protocol message format. 36 | */ 37 | struct Encoder { 38 | 39 | /** Construct an Encoder that writes data to the given stream. 40 | * @param dest A byte stream to write data to. 41 | */ 42 | constexpr Encoder(Solace::ByteWriter& dest) noexcept 43 | : _dest{dest} 44 | {} 45 | 46 | Encoder(Encoder const&) = delete; 47 | Encoder& operator= (Encoder const&) = delete; 48 | 49 | /** 50 | * Get underlaying output stream. 51 | * @return Byte stream where data is being written. 52 | */ 53 | constexpr Solace::ByteWriter& buffer() noexcept { return _dest; } 54 | 55 | private: 56 | 57 | /// Output byte stream to write data to. 58 | Solace::ByteWriter& _dest; 59 | }; 60 | 61 | 62 | 63 | size_type protocolSize(Solace::uint8 const& value) noexcept; 64 | size_type protocolSize(Solace::uint16 const& value) noexcept; 65 | size_type protocolSize(Solace::uint32 const& value) noexcept; 66 | size_type protocolSize(Solace::uint64 const& value) noexcept; 67 | size_type protocolSize(Solace::StringView const& value) noexcept; 68 | size_type protocolSize(Solace::MemoryView const& value) noexcept; 69 | 70 | 71 | 72 | /** Encode an uint8 value into the output straam. 73 | * @param encoder Encoder used to encode the value. 74 | * @param value Value to encode. 75 | * @return Ref to the encoder for fluency. 76 | */ 77 | Encoder& operator<< (Encoder& encoder, Solace::uint8 value); 78 | 79 | /** Encode an uint16 value into the output straam. 80 | * @param encoder Encoder used to encode the value. 81 | * @param value Value to encode. 82 | * @return Ref to the encoder for fluency. 83 | */ 84 | Encoder& operator<< (Encoder& encoder, Solace::uint16 value); 85 | 86 | /** Encode an uint32 value into the output straam. 87 | * @param encoder Encoder used to encode the value. 88 | * @param value Value to encode. 89 | * @return Ref to the encoder for fluency. 90 | */ 91 | Encoder& operator<< (Encoder& encoder, Solace::uint32 value); 92 | 93 | /** Encode an uint64 value into the output straam. 94 | * @param encoder Encoder used to encode the value. 95 | * @param value Value to encode. 96 | * @return Ref to the encoder for fluency. 97 | */ 98 | Encoder& operator<< (Encoder& encoder, Solace::uint64 value); 99 | 100 | /** Encode a string value into the output straam. 101 | * @param encoder Encoder used to encode the value. 102 | * @param value Value to encode. 103 | * @return Ref to the encoder for fluency. 104 | */ 105 | Encoder& operator<< (Encoder& encoder, Solace::StringView value); 106 | 107 | /** Encode a Raw byte buffer into the output straam. 108 | * @param encoder Encoder used to encode the value. 109 | * @param value Value to encode. 110 | * @return Ref to the encoder for fluency. 111 | */ 112 | Encoder& operator<< (Encoder& encoder, Solace::MemoryView value); 113 | 114 | } // namespace styxe 115 | #endif // STYXE_ENCODER_HPP 116 | -------------------------------------------------------------------------------- /test/test_9P2000L_dirReader.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /******************************************************************************* 17 | * libstyxe Unit Test Suit 18 | * @file: test/test_9P2000u.cpp 19 | * Specific test 9P2000.e 20 | *******************************************************************************/ 21 | #include "styxe/9p2000L.hpp" 22 | 23 | #include "testHarnes.hpp" 24 | 25 | using namespace Solace; 26 | using namespace styxe; 27 | 28 | 29 | TEST(P92000L, emptyDirReader) { 30 | MemoryView data{}; 31 | 32 | _9P2000L::DirEntryReader reader{data}; 33 | 34 | ASSERT_EQ(begin(reader), end(reader)); 35 | } 36 | 37 | 38 | TEST(P92000L, dirReader) { 39 | char buffer[127]; 40 | MutableMemoryView data = wrapMemory(buffer); 41 | ByteWriter dirStream{data}; 42 | 43 | _9P2000L::DirEntry entry { 44 | randomQid(), 45 | 0, 46 | 31, 47 | StringView{"Awesome file"} 48 | }; 49 | 50 | styxe::Encoder encoder{dirStream}; 51 | encoder << entry; 52 | 53 | 54 | _9P2000L::DirEntryReader reader{dirStream.viewWritten()}; 55 | 56 | auto i = begin(reader); 57 | ASSERT_NE(i, end(reader)); 58 | ASSERT_EQ(entry, *i); 59 | ASSERT_NE(i, end(reader)); 60 | 61 | 62 | ASSERT_EQ(++i, end(reader)); 63 | } 64 | 65 | 66 | 67 | TEST(P92000L, dirReader_multiple_enties) { 68 | char buffer[127]; 69 | MutableMemoryView data = wrapMemory(buffer); 70 | ByteWriter dirStream{data}; 71 | 72 | _9P2000L::DirEntry entries[] = { 73 | {randomQid(), 0, 31, StringView{"data"}}, 74 | {randomQid(), 4, 31, StringView{"Awesome file"}}, 75 | {randomQid(), 1, 32, StringView{"other file"}} 76 | }; 77 | 78 | styxe::Encoder encoder{dirStream}; 79 | for (auto const& e : entries) { 80 | encoder << e; 81 | } 82 | 83 | 84 | _9P2000L::DirEntryReader reader{dirStream.viewWritten()}; 85 | size_t index = 0; 86 | for (auto const& ent : reader) { 87 | ASSERT_EQ(entries[index], ent); 88 | index += 1; 89 | } 90 | ASSERT_EQ(3U, index); 91 | } 92 | 93 | 94 | TEST(P92000L, dirReader_incomplete_buffer_1) { 95 | char buffer[127]; 96 | MutableMemoryView data = wrapMemory(buffer); 97 | ByteWriter dirStream{data}; 98 | 99 | _9P2000L::DirEntry entries[] = { 100 | {randomQid(), 0, 31, StringView{"data"}}, 101 | }; 102 | 103 | styxe::Encoder encoder{dirStream}; 104 | for (auto const& e : entries) { 105 | encoder << e; 106 | } 107 | 108 | _9P2000L::DirEntryReader reader{dirStream.viewWritten() .slice(0, dirStream.position() - 10)}; 109 | size_t index = 0; 110 | for (auto ent : reader) { 111 | ASSERT_EQ(entries[index], ent); 112 | index += 1; 113 | } 114 | 115 | ASSERT_EQ(0U, index); 116 | } 117 | 118 | 119 | TEST(P92000L, dirReader_incomplete_buffer_2) { 120 | char buffer[127]; 121 | MutableMemoryView data = wrapMemory(buffer); 122 | 123 | _9P2000L::DirEntry const entries[] = { 124 | {randomQid(), 0, 31, StringView{"data"}}, 125 | {randomQid(), 4, 31, StringView{"Awesome file"}}, 126 | {randomQid(), 1, 32, StringView{"other file"}} 127 | }; 128 | 129 | ByteWriter dirStream{data}; 130 | styxe::Encoder encoder{dirStream}; 131 | for (auto const& e : entries) { 132 | encoder << e; 133 | } 134 | 135 | _9P2000L::DirEntryReader reader{dirStream.viewWritten() .slice(0, dirStream.position() - 10)}; 136 | size_t index = 0; 137 | for (auto ent : reader) { 138 | ASSERT_EQ(entries[index], ent); 139 | index += 1; 140 | } 141 | 142 | ASSERT_EQ(2U, index); 143 | } 144 | -------------------------------------------------------------------------------- /test/test_9PDirListingWriter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /******************************************************************************* 17 | * libstyxe Unit Test Suit 18 | * @file: test/test_P9DirListingWriter.cpp 19 | * 20 | *******************************************************************************/ 21 | #include "styxe/messageWriter.hpp" // Class being tested 22 | #include "styxe/messageParser.hpp" 23 | #include "styxe/9p2000.hpp" 24 | 25 | #include 26 | #include 27 | 28 | 29 | using namespace Solace; 30 | using namespace styxe; 31 | 32 | namespace { 33 | 34 | class P9DirListingWriter : public ::testing::Test { 35 | public: 36 | 37 | P9DirListingWriter() noexcept 38 | : _memManager{kMaxMessageSize} 39 | {} 40 | 41 | 42 | protected: 43 | 44 | void SetUp() override { 45 | _buffer = _memManager.allocate(kMaxMessageSize).unwrap(); 46 | _buffer.viewRemaining().fill(0xFE); 47 | } 48 | 49 | MemoryManager _memManager; 50 | ByteWriter _buffer; 51 | }; 52 | 53 | } // namespace 54 | 55 | 56 | TEST_F(P9DirListingWriter, directoryReadResponse) { 57 | Stat testStats[] = { 58 | { 59 | 0, 60 | 1, 61 | 2, 62 | {2, 0, 64}, 63 | 01000644, 64 | 0, 65 | 0, 66 | 4096, 67 | StringLiteral{"Root"}, 68 | StringLiteral{"User"}, 69 | StringLiteral{"Glanda"}, 70 | StringLiteral{"User"} 71 | } 72 | }; 73 | 74 | // Fix directory stat structs sizes 75 | for (auto& stat : testStats) { 76 | stat.size = DirListingWriter::sizeStat(stat); 77 | } 78 | 79 | auto responseWriter = ResponseWriter{_buffer, 1}; 80 | auto dirWriter = DirListingWriter{responseWriter, 4096}; 81 | for (auto const& stat : testStats) { 82 | if (!dirWriter.encode(stat)) { 83 | break; 84 | } 85 | } 86 | ASSERT_EQ(dirWriter.bytesEncoded(), protocolSize(testStats[0])); 87 | 88 | // Check response 89 | auto maybeParser = createResponseParser(kProtocolVersion, 128); 90 | ASSERT_TRUE(maybeParser.isOk()); 91 | 92 | auto& parser = *maybeParser; 93 | 94 | ByteReader reader{_buffer.viewWritten()}; 95 | 96 | auto maybeHeader = parseMessageHeader(reader); 97 | ASSERT_TRUE(maybeHeader.isOk()); 98 | 99 | auto maybeMessage = parser.parseResponse(*maybeHeader, reader); 100 | ASSERT_TRUE(maybeMessage.isOk()); 101 | 102 | auto& message = maybeMessage.unwrap(); 103 | ASSERT_TRUE(std::holds_alternative(message)); 104 | 105 | auto read = std::get(message); 106 | ASSERT_EQ(dirWriter.bytesEncoded(), read.data.size()); 107 | } 108 | 109 | 110 | TEST_F(P9DirListingWriter, emptyDirectoryReadResponseOk) { 111 | auto responseWriter = ResponseWriter{_buffer, 1}; 112 | auto dirWriter = DirListingWriter{responseWriter, 4096}; 113 | ASSERT_EQ(dirWriter.bytesEncoded(), 0U); 114 | 115 | // Check response 116 | auto maybeParser = createResponseParser(kProtocolVersion, 128); 117 | ASSERT_TRUE(maybeParser.isOk()); 118 | 119 | auto& parser = *maybeParser; 120 | ByteReader reader{_buffer.viewWritten()}; 121 | 122 | auto maybeHeader = parseMessageHeader(reader); 123 | ASSERT_TRUE(maybeHeader.isOk()); 124 | 125 | auto maybeMessage = parser.parseResponse(*maybeHeader, reader); 126 | ASSERT_TRUE(maybeMessage.isOk()); 127 | 128 | auto& message = maybeMessage.unwrap(); 129 | ASSERT_TRUE(std::holds_alternative(message)); 130 | 131 | auto read = std::get(message); 132 | ASSERT_EQ(dirWriter.bytesEncoded(), read.data.size()); 133 | } 134 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | Thank you so much for your interest in contributing! 4 | This project is in active development and all contributions are welcomed. 5 | 6 | There are multiple ways you can help. For example you can [Submit a patch](#submitting-patch) or you can [Contribute documentation](#submitting-docs). If you have encountered an issue while using the library in your project please [Report errors or bugs](#reporting-bugs) to help us fix the issue. If you feel that this library is missing something feel free to [Request a feature/improvement](#requesting-feature) - it might be that other people can benefit from this feature. 7 | 8 | ## Submit a patch 9 | If you want to fix an issue or add a feature - code contribution is the best way to do it. Easiest way to submit a patch is by raising a pull request (PR): 10 | 11 | * Clone the project. 12 | * Make any necessary changes to the source code. Please see [coding convention](docs/coding_convention.md) about code style. 13 | * Include any [additional documentation](#contribute-documentation) the changes might be needed. 14 | * When adding new feature it is essential to write tests that verify that your contribution works as expected. 15 | * When fixing existing issues it is essential to update existing tests accordingly. 16 | * All code changes are subject to automatic code quality checks run as part of CI. Code changes can not be accepted if they fail quality check so please make sure this checks pass by run `make codecheck` locally before submitting. 17 | * Write clear, concise commit message(s) using [conventional-changelog format](https://github.com/conventional-changelog/conventional-changelog-angular/blob/master/convention.md). 18 | * Go to https://github.com/abbyssoul/libstyxe/pulls and open a new pull request with your changes. 19 | * If your PR is connected to an open issue, add a line in your PR's description that says `Fixes: #123`, where `#123` is the number of the issue you're fixing. 20 | 21 | ### Testing 22 | This project is committed to maintaining highest level of code quality. That is why any new feature submitted must be covered by expensive unit tests and pass code quality checks. 23 | For fixes it is natural that existing test coverage was not sufficient to vet it. Thus it is expected that exising tests will be extended to cover issue being fixed when ever possible. This helps to avoid re-occurrence of the issue as code changes over time. 24 | 25 | 26 | ## Reporting errors or bugs 27 | If you think you run into an error or bug with the project please let us know. It is possible that other users also might experience issues. To report an issues: 28 | * Open an Issue at https://github.com/abbyssoul/libstyxe/issues 29 | * Include *reproduction steps* that someone else can follow to recreate the bug or error on their own. 30 | * It helps if you can specify what expected behaviour/outcome was and what was the actual result. 31 | * Provide project and platform versions (OS name, compiler version), depending on what seems relevant. If not, please be ready to provide that information if maintainers ask for it. 32 | 33 | 34 | ## Request a feature/improvement 35 | If the project doesn't do something you need or want it to do you can create a feature request. It is possible that there are othere users who need the same functions. To request a feature: 36 | 37 | * Open an Issue at https://github.com/abbyssoul/libstyxe/issues 38 | * Provide as much context as you can about what you're running into. 39 | * Please try and be clear about why existing features and alternatives would not work for you. 40 | 41 | ## Contributing documentation 42 | Documentation is essential part of any project. To contribute documentation: 43 | 44 | * Clone the project. 45 | * Edit or add any relevant documentation. 46 | * Make sure your changes are formatted correctly and consistently with the rest of the documentation. 47 | * Re-read what you wrote, and run a spell-checker on it to make sure you didn't miss anything. 48 | * Write clear, concise commit message(s) using [conventional-changelog format](https://github.com/conventional-changelog/conventional-changelog-angular/blob/master/convention.md). Documentation commits should use `docs(): `. 49 | * Go to https://github.com/abbyssoul/libstyxe/pulls and open a new pull request with your changes. 50 | * If your PR is connected to an open issue, add a line in your PR's description that says `Fixes: #123`, where `#123` is the number of the issue you're fixing. 51 | -------------------------------------------------------------------------------- /src/9p2000e.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "styxe/9p2000e.hpp" 18 | 19 | #include "parse_helper.hpp" 20 | #include "write_helper.hpp" 21 | 22 | 23 | using namespace Solace; 24 | using namespace styxe; 25 | 26 | 27 | const StringLiteral _9P2000E::kProtocolVersion{"9P2000.e"}; 28 | 29 | 30 | styxe::Result 31 | styxe::operator>> (ByteReader& data, _9P2000E::Request::Session& dest) { 32 | return decode(data, 33 | dest.key[0], 34 | dest.key[1], 35 | dest.key[2], 36 | dest.key[3], 37 | dest.key[4], 38 | dest.key[5], 39 | dest.key[6], 40 | dest.key[7]); 41 | } 42 | 43 | 44 | styxe::Result 45 | styxe::operator>> (ByteReader& data, _9P2000E::Request::ShortRead& dest) { 46 | return decode(data, dest.fid, dest.path); 47 | } 48 | 49 | styxe::Result 50 | styxe::operator>> (ByteReader& data, _9P2000E::Request::ShortWrite& dest) { 51 | return decode(data, dest.fid, dest.path, dest.data); 52 | } 53 | 54 | 55 | styxe::Result 56 | styxe::operator>> (ByteReader& data, _9P2000E::Response::Session& ) { 57 | return styxe::Result{types::okTag, data}; 58 | } 59 | 60 | 61 | styxe::Result 62 | styxe::operator>> (ByteReader& data, _9P2000E::Response::ShortRead& dest) { 63 | return decode(data, dest.data); 64 | } 65 | 66 | 67 | styxe::Result 68 | styxe::operator>> (ByteReader& data, _9P2000E::Response::ShortWrite& dest) { 69 | return decode(data, dest.count); 70 | } 71 | 72 | 73 | ResponseWriter& 74 | styxe::operator<< (ResponseWriter& writer, _9P2000E::Response::Session const& message) { 75 | return encode(writer, message); 76 | } 77 | 78 | ResponseWriter& 79 | styxe::operator<< (ResponseWriter& writer, _9P2000E::Response::ShortRead const& message) { 80 | return encode(writer, message, message.data); 81 | } 82 | 83 | 84 | ResponseWriter& 85 | styxe::operator<< (ResponseWriter& writer, _9P2000E::Response::ShortWrite const& message) { 86 | return encode(writer, message, message.count); 87 | } 88 | 89 | 90 | RequestWriter& 91 | styxe::operator<< (RequestWriter& writer, _9P2000E::Request::Session const& message) { 92 | return encode(writer, 93 | message, 94 | message.key[0], 95 | message.key[1], 96 | message.key[2], 97 | message.key[3], 98 | message.key[4], 99 | message.key[5], 100 | message.key[6], 101 | message.key[7]); 102 | } 103 | 104 | RequestWriter& 105 | styxe::operator<< (RequestWriter& writer, _9P2000E::Request::ShortRead const& message) { 106 | return encode(writer, message, message.fid, message.path); 107 | } 108 | 109 | RequestWriter& 110 | styxe::operator<< (RequestWriter& writer, _9P2000E::Request::ShortWrite const& message) { 111 | return encode(writer, message, message.fid, message.path, message.data); 112 | } 113 | 114 | 115 | PartialPathWriter 116 | styxe::operator<< (RequestWriter& writer, _9P2000E::Request::Partial::ShortRead const& message) { 117 | writer.messageTypeOf<_9P2000E::Request::ShortRead>() 118 | << message.fid; 119 | 120 | return PartialPathWriter{writer}; 121 | } 122 | 123 | 124 | PathDataWriter 125 | styxe::operator<< (RequestWriter& writer, _9P2000E::Request::Partial::ShortWrite const& message) { 126 | writer.messageTypeOf<_9P2000E::Request::ShortWrite>() 127 | << message.fid; 128 | 129 | return PathDataWriter{writer}; 130 | } 131 | 132 | 133 | StringView 134 | styxe::_9P2000E::messageTypeToString(byte type) noexcept { 135 | auto mType = static_cast<_9P2000E::MessageType>(type); 136 | switch (mType) { 137 | case _9P2000E::MessageType::TSession: return "TSession"; 138 | case _9P2000E::MessageType::RSession: return "RSession"; 139 | case _9P2000E::MessageType::TShortRead: return "TShortRead"; 140 | case _9P2000E::MessageType::RShortRead: return "RShortRead"; 141 | case _9P2000E::MessageType::TShortWrite: return "TShortWrite"; 142 | case _9P2000E::MessageType::RShortWrite: return "RShortWrite"; 143 | } 144 | 145 | return styxe::messageTypeToString(type); 146 | } 147 | 148 | -------------------------------------------------------------------------------- /include/styxe/decoder.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | #ifndef STYXE_DECODER_HPP 18 | #define STYXE_DECODER_HPP 19 | 20 | #include "9p.hpp" // size_type, Error, WalkPath 21 | 22 | #include 23 | #include 24 | 25 | #include // mv<> 26 | #include 27 | 28 | 29 | namespace styxe { 30 | 31 | /** 32 | * Helper class to decode data structures from the 9P2000 formatted messages. 33 | */ 34 | struct Decoder { 35 | 36 | /** Construct a Decoder that reads from the given stream. 37 | * @param src A byte stream to decode data from. 38 | */ 39 | constexpr Decoder(Solace::ByteReader& src) noexcept 40 | : _src{src} 41 | {} 42 | 43 | Decoder(Decoder const&) = delete; 44 | Decoder& operator= (Decoder const&) = delete; 45 | 46 | /** 47 | * Get underlaying byte stream. 48 | * @return Byte stream where data is read from. 49 | */ 50 | Solace::ByteReader& buffer() noexcept { return _src; } 51 | 52 | private: 53 | /// Data stream to read bytes from. 54 | Solace::ByteReader& _src; 55 | }; 56 | 57 | 58 | 59 | /** Decode uint8 value from the stream. 60 | * @param decoder A data stream to read a value from. 61 | * @param dest An address where to store decoded value. 62 | * @return Ref to the decoder or Error if operation has failed. 63 | */ 64 | Solace::Result operator>> (Decoder& decoder, Solace::uint8& dest); 65 | 66 | /** Decode uint16 value from the stream. 67 | * @param decoder A data stream to read a value from. 68 | * @param dest An address where to store decoded value. 69 | * @return Ref to the decoder or Error if operation has failed. 70 | */ 71 | Solace::Result operator>> (Decoder& decoder, Solace::uint16& dest); 72 | 73 | /** Decode uint32 value from the stream. 74 | * @param decoder A data stream to read a value from. 75 | * @param dest An address where to store decoded value. 76 | * @return Ref to the decoder or Error if operation has failed. 77 | */ 78 | Solace::Result operator>> (Decoder& decoder, Solace::uint32& dest); 79 | 80 | /** Decode uint64 value from the stream. 81 | * @param decoder A data stream to read a value from. 82 | * @param dest An address where to store decoded value. 83 | * @return Ref to the decoder or Error if operation has failed. 84 | */ 85 | Solace::Result operator>> (Decoder& decoder, Solace::uint64& dest); 86 | 87 | /** Decode a *String value from the stream. 88 | * @param decoder A data stream to read a value from. 89 | * @param dest An address where to store decoded value. 90 | * @return Ref to the decoder or Error if operation has failed. 91 | */ 92 | Solace::Result operator>> (Decoder& decoder, Solace::StringView& dest); 93 | 94 | /** Decode a raw byte view from the stream. 95 | * @param decoder A data stream to read a value from. 96 | * @param dest An address where to store decoded value. 97 | * @return Ref to the decoder or Error if operation has failed. 98 | */ 99 | Solace::Result operator>> (Decoder& decoder, Solace::MemoryView& dest); 100 | 101 | /** Decode a path view from the stream. 102 | * @param decoder A data stream to read a value from. 103 | * @param dest An address where to store decoded value. 104 | * @return Ref to the decoder or Error if operation has failed. 105 | */ 106 | Solace::Result operator>> (Decoder& decoder, WalkPath& dest); 107 | 108 | /** 109 | * An interop for Result. 110 | * @param decoder A data stream to read a value from. 111 | * @param dest An address where to store decoded value. 112 | * @return Ref to the decoder or Error if operation has failed. 113 | */ 114 | template 115 | Solace::Result 116 | operator>> (Solace::Result&& decoder, T& dest) { 117 | return (decoder) 118 | ? (decoder.unwrap() >> dest) 119 | : Solace::mv(decoder); 120 | } 121 | 122 | } // namespace styxe 123 | #endif // STYXE_DECODER_HPP 124 | -------------------------------------------------------------------------------- /include/styxe/9p.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | #ifndef STYXE_9P_HPP 18 | #define STYXE_9P_HPP 19 | 20 | #include 21 | #include 22 | 23 | 24 | namespace styxe { 25 | 26 | /** Network protocol uses fixed width int32 to represent size of data in bytes */ 27 | using size_type = Solace::uint32; 28 | 29 | /** Network protocol uses fixed width int16 to represent variable datum size */ 30 | using var_datum_size_type = Solace::uint16; 31 | 32 | /** Type of message Tag */ 33 | using Tag = Solace::uint16; 34 | 35 | /** Type of file identifiers client uses to identify a ``current file`` on the server*/ 36 | using Fid = Solace::uint32; 37 | 38 | /** A contigues sequence of string used by the protocol to encode a path */ 39 | using WalkPath = Solace::VariableSpan; 40 | 41 | /** String const for unknow version. */ 42 | extern const Solace::StringLiteral kUnknownProtocolVersion; 43 | 44 | /** Special value of a message tag representing 'no tag'. */ 45 | extern const Tag kNoTag; 46 | 47 | /** Special value of a message FID representing 'no Fid'. */ 48 | extern const Fid kNoFID; 49 | 50 | /** 51 | * Fixed size common message header that all messages start with 52 | */ 53 | struct MessageHeader { 54 | size_type messageSize{0}; //!< Size of the message including size of the header and size field itself 55 | Solace::byte type{0}; //!< Type of the message. @see MessageType. 56 | Tag tag{0}; //!< Message tag for concurrent messages. 57 | 58 | /** 59 | * @brief Get the estimated payload size in bytes. 60 | * @return Payload size in bytes. 61 | */ 62 | constexpr size_type payloadSize() const noexcept; 63 | }; 64 | 65 | 66 | /** 67 | * Get size in bytes of the mandatory protocol message header. 68 | * @see MessageHeader 69 | * @return Size in bytes of the mandatory protocol message header. 70 | */ 71 | inline 72 | constexpr size_type headerSize() noexcept { 73 | // Note: don't use sizeof(MessageHeader) due to possible padding 74 | return sizeof(MessageHeader::messageSize) + 75 | sizeof(MessageHeader::type) + 76 | sizeof(MessageHeader::tag); 77 | } 78 | 79 | inline 80 | constexpr size_type MessageHeader::payloadSize() const noexcept { 81 | // Do we care about negative sizes? 82 | return messageSize - headerSize(); 83 | } 84 | 85 | 86 | inline 87 | constexpr MessageHeader makeHeaderWithPayload(Solace::byte type, Tag tag, size_type payloadSize) noexcept { 88 | return { headerSize() + payloadSize, type, tag }; 89 | } 90 | 91 | 92 | 93 | /** 94 | * The qid represents the server's unique identification for the file being accessed: 95 | * two files on the same server hierarchy are the same if and only if their qids are the same. 96 | */ 97 | struct Qid { 98 | Solace::uint64 path; //!< Unique identifier of a file used by the server. 99 | Solace::uint32 version; //!< Version of the file if FS supports file versioning. 100 | Solace::byte type; //!< Type of the file this qid referse to. @see DirMode for details. 101 | }; 102 | 103 | 104 | inline 105 | bool operator== (Qid const& lhs, Qid const& rhs) noexcept { 106 | return (lhs.path == rhs.path && 107 | lhs.version == rhs.version && 108 | lhs.type == rhs.type); 109 | } 110 | 111 | inline 112 | bool operator!= (Qid const& lhs, Qid const& rhs) noexcept { 113 | return !(lhs == rhs); 114 | } 115 | 116 | /** 117 | * Helper function to convert message type to byte-code. 118 | */ 119 | template 120 | constexpr Solace::byte messageCodeOf() noexcept = delete; 121 | 122 | 123 | static_assert(sizeof(size_type) == 4, "Protocol size type is uint32"); 124 | static_assert(sizeof(Tag) == 2, "Tag is uint16"); 125 | static_assert(sizeof(Fid) == 4, "Fid is uint32"); 126 | static_assert(sizeof(var_datum_size_type) == 2, "Var datum size is uint16"); 127 | static_assert(sizeof(MessageHeader::type) == 1, "MessageType should be 1 byte"); 128 | static_assert(headerSize() == 7, "9P message header size is 7 bytes"); 129 | 130 | } // end of namespace styxe 131 | #endif // STYXE_9P_HPP 132 | -------------------------------------------------------------------------------- /configure: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DIR="$( cd "$( dirname "${0}" )" && pwd )" 4 | TMP_TARGET_FILE_NAME="$(mktemp Makefile.$(date +"%Y-%m-%d_%T_XXXXXX"))" 5 | TARGET_FILE_NAME=Makefile 6 | 7 | # Configurable options 8 | prefix=/usr/local 9 | debugsym=false 10 | sanitizer=false 11 | coverage=false 12 | profile=false 13 | pkgconfig=false 14 | generator=Ninja 15 | 16 | # Figure out project name: 17 | project_name=$(basename $DIR) 18 | if [ -f "PROJECT.name" ]; then 19 | project_name="$(cat 'PROJECT.name')" 20 | else 21 | echo "Warning: no file 'PROJECT.name' found, guessing project name: '${project_name}'" 22 | fi 23 | 24 | 25 | for arg in "$@"; do 26 | case "$arg" in 27 | --prefix=*) 28 | prefix=`echo $arg | sed 's/--prefix=//'` 29 | ;; 30 | 31 | --conan-profile=*) 32 | conan_profile=`echo $arg | sed 's/--conan-profile=//'` 33 | ;; 34 | --generator=*) 35 | generator=`echo $arg | sed 's/--generator=//'` 36 | ;; 37 | 38 | --enable-debug ) 39 | debugsym=true 40 | ;; 41 | --disable-debug ) 42 | debugsym=false 43 | ;; 44 | 45 | --enable-sanitizer ) 46 | sanitizer=true 47 | ;; 48 | --disable-sanitizer ) 49 | sanitizer=false 50 | ;; 51 | 52 | --enable-coverage ) 53 | coverage=true 54 | ;; 55 | --disable-coverage ) 56 | coverage=false 57 | ;; 58 | 59 | --enable-profile ) 60 | profile=true 61 | ;; 62 | --disable-profile ) 63 | profile=true 64 | ;; 65 | 66 | --enable-pkgconfig ) 67 | pkgconfig=true 68 | ;; 69 | --disable-pkgconfig ) 70 | pkgconfig=true 71 | ;; 72 | 73 | --help) 74 | echo 'usage: ./configure [options]' 75 | echo 'options:' 76 | echo ' --prefix=: installation prefix' 77 | echo ' --generator=: CMake generator' 78 | echo ' --conan-profile=: Use custom conan profil' 79 | echo ' --enable-debug include debug symbols' 80 | echo ' --disable-debug do not include debug symbols' 81 | echo ' --enable-sanitizer To enable -fsanitize compiler option' 82 | echo ' --disable-sanitizer To disable -fsanitize compiler option' 83 | echo ' --enable-coverage To enable compiler coverage option' 84 | echo ' --disable-coverage To disable compiler coverage option' 85 | echo ' --enable-profile To enable compiler profiler info generation' 86 | echo ' --disable-profile To disable compiler profiler info generation' 87 | echo ' --enable-pkgconfig To include generated .PC library descriptor when installing' 88 | echo ' --disable-pkgconfig To exclude generated .PC library descriptor when installing' 89 | echo '' 90 | echo 'all invalid options are silently ignored' 91 | exit 0 92 | ;; 93 | 94 | *) 95 | echo "Unexpected option '$arg'" 96 | exit 0 97 | ;; 98 | esac 99 | done 100 | 101 | echo "Generating Makefile for ${project_name}..." 102 | echo "# Generated Makefile for ${project_name}: $(date)" >> "${TMP_TARGET_FILE_NAME}" 103 | echo "PREFIX ?= ${prefix}" >> "${TMP_TARGET_FILE_NAME}" 104 | echo "PROJECT ?= ${project_name}" >> "${TMP_TARGET_FILE_NAME}" 105 | echo "GENERATOR ?= ${generator}" >> "${TMP_TARGET_FILE_NAME}" 106 | 107 | 108 | if $debugsym; then 109 | echo 'dbg = -g' >> "${TMP_TARGET_FILE_NAME}" 110 | fi 111 | 112 | if $sanitizer; then 113 | echo 'sanitize = address,undefined,leak' >> "${TMP_TARGET_FILE_NAME}" 114 | fi 115 | 116 | if $coverage; then 117 | echo 'coverage = -coverage' >> "${TMP_TARGET_FILE_NAME}" 118 | fi 119 | 120 | if $profile; then 121 | echo 'profile = -pg' >> "${TMP_TARGET_FILE_NAME}" 122 | fi 123 | 124 | if $pkgconfig; then 125 | echo 'PKG_CONFIG = ON' >> "${TMP_TARGET_FILE_NAME}" 126 | fi 127 | 128 | if [ ! -z "$conan_profile" ] ; then 129 | echo "CONAN_PROFILE ?= ${conan_profile}" >> "${TMP_TARGET_FILE_NAME}" 130 | fi 131 | 132 | 133 | if [ ! -z "${CXX}" ] || [ ! -z "${CC}" ] ; then 134 | echo "Using custom copiler: CXX=$CXX CC=$CC" 135 | printf "\n# Compiler config\n" >> "${TMP_TARGET_FILE_NAME}" 136 | 137 | if [ ! -z "${CXX}" ] ; then 138 | echo "CXX ?= ${CXX}" >> "${TMP_TARGET_FILE_NAME}" 139 | fi 140 | 141 | if [ ! -z "${CC}" ]; then 142 | echo "CC ?= ${CC}" >> "${TMP_TARGET_FILE_NAME}" 143 | fi 144 | fi 145 | 146 | echo '' >> "${TMP_TARGET_FILE_NAME}" 147 | cat Makefile.in >> "${TMP_TARGET_FILE_NAME}" 148 | 149 | # Atomic move if above was successful 150 | mv "${TMP_TARGET_FILE_NAME}" "${TARGET_FILE_NAME}" 151 | 152 | # Congrats 153 | echo "Configuration complete, type 'make' to build ${project_name}." 154 | -------------------------------------------------------------------------------- /.github/workflows/conan-publish.yml: -------------------------------------------------------------------------------- 1 | name: Build conan package 2 | 3 | on: 4 | push: 5 | tags: 6 | - '*' 7 | 8 | jobs: 9 | build: 10 | strategy: 11 | matrix: 12 | docker_image: 13 | - conanio/gcc8 14 | - conanio/gcc8-armv7 15 | - conanio/gcc8-armv7hf 16 | - conanio/gcc8-armv8 17 | - conanio/gcc9 18 | - conanio/gcc9-armv7 19 | - conanio/gcc9-armv7hf 20 | - conanio/gcc9-armv8 21 | - conanio/gcc10 22 | - conanio/gcc10-armv7 23 | - conanio/gcc10-armv7hf 24 | - conanio/clang7 25 | - conanio/clang8 26 | - conanio/clang9 27 | - conanio/clang10 28 | - conanio/clang11 29 | include: 30 | - docker_image: conanio/gcc8 31 | gcc_version: 8 32 | build_arch: x86_64 33 | - docker_image: conanio/gcc8-armv7 34 | gcc_version: 8 35 | build_arch: armv7 36 | - docker_image: conanio/gcc8-armv7hf 37 | gcc_version: 8 38 | build_arch: armv7hf 39 | - docker_image: conanio/gcc8-armv8 40 | gcc_version: 8 41 | build_arch: armv8 42 | 43 | - docker_image: conanio/gcc9 44 | gcc_version: 9 45 | build_arch: x86_64 46 | - docker_image: conanio/gcc9-armv7 47 | gcc_version: 9 48 | build_arch: armv7 49 | - docker_image: conanio/gcc9-armv7hf 50 | gcc_version: 9 51 | build_arch: armv7hf 52 | - docker_image: conanio/gcc9-armv8 53 | gcc_version: 9 54 | build_arch: armv8 55 | 56 | - docker_image: conanio/gcc10 57 | gcc_version: 10 58 | build_arch: x86_64 59 | - docker_image: conanio/gcc10-armv7 60 | gcc_version: 10 61 | build_arch: armv7 62 | - docker_image: conanio/gcc10-armv7hf 63 | gcc_version: 10 64 | build_arch: armv7hf 65 | 66 | - docker_image: conanio/clang7 67 | clang_version: "7.0" 68 | build_arch: x86_64 69 | - docker_image: conanio/clang8 70 | clang_version: 8 71 | build_arch: x86_64 72 | - docker_image: conanio/clang9 73 | clang_version: 9 74 | build_arch: x86_64 75 | - docker_image: conanio/clang10 76 | clang_version: 10 77 | build_arch: x86_64 78 | - docker_image: conanio/clang11 79 | clang_version: 11 80 | build_arch: x86_64 81 | 82 | runs-on: ubuntu-latest 83 | env: 84 | CONAN_USERNAME: "abbyssoul" 85 | CONAN_LOGIN_USERNAME: "abbyssoul" 86 | CONAN_PASSWORD: ${{ secrets.conan_key }} 87 | CONAN_CHANNEL: "stable" 88 | CONAN_UPLOAD: "https://api.bintray.com/conan/abbyssoul/public-conan" 89 | CONAN_BUILD_POLICY: outdated 90 | CONAN_GCC_VERSIONS: ${{ matrix.gcc_version }} 91 | CONAN_ARCHS: ${{matrix.build_arch}} 92 | CONAN_CLANG_VERSIONS: ${{matrix.clang_version}} 93 | CONAN_DOCKER_IMAGE: ${{ matrix.docker_image }} 94 | 95 | steps: 96 | - uses: actions/checkout@master 97 | - uses: actions/setup-python@master 98 | with: 99 | python-version: '3.7' 100 | - name: Generating conan user directory 101 | run: | 102 | python3 -m pip install conan conan_package_tools -U 103 | conan profile new default --detect 104 | conan remote add abyss "${CONAN_UPLOAD}" 105 | conan user 106 | - name: Deduce package version from tag 107 | run: | 108 | git fetch --depth=1 origin +$GITHUB_REF:$GITHUB_REF 109 | export CONAN_VERSION=$(git describe --tags) 110 | echo "CONAN_REFERENCE=libstyxe/${CONAN_VERSION}" >> $GITHUB_ENV 111 | 112 | - name: Building and publish the package 113 | run: python build-conan-package.py 114 | 115 | build-osx: 116 | runs-on: macOS-latest 117 | env: 118 | CONAN_USERNAME: "abbyssoul" 119 | CONAN_LOGIN_USERNAME: "abbyssoul" 120 | CONAN_PASSWORD: ${{ secrets.conan_key }} 121 | CONAN_CHANNEL: "stable" 122 | CONAN_UPLOAD: "https://api.bintray.com/conan/abbyssoul/public-conan" 123 | CONAN_APPLE_CLANG_VERSIONS: "12.0" 124 | CONAN_BUILD_POLICY: outdated 125 | 126 | steps: 127 | - uses: actions/checkout@master 128 | - uses: actions/setup-python@master 129 | with: 130 | python-version: '3.7' 131 | - name: Generating conan profile and user directory 132 | run: | 133 | python -m pip install conan conan_package_tools -U 134 | conan profile new default --detect 135 | conan remote add abyss "${CONAN_UPLOAD}" 136 | conan user 137 | - name: Deduce package version from tag 138 | run: | 139 | git fetch --depth=1 origin +$GITHUB_REF:$GITHUB_REF 140 | export CONAN_VERSION=$(git describe --tags) 141 | echo "CONAN_REFERENCE=libstyxe/${CONAN_VERSION}" >> $GITHUB_ENV 142 | 143 | - name: Building and publish the package 144 | run: python build-conan-package.py 145 | -------------------------------------------------------------------------------- /docs/9p2000.e.txt: -------------------------------------------------------------------------------- 1 | 9P2000 protocol Erlang extension v1.0 2 | ------------------------------------- 3 | 4 | 5 | Notation 6 | 7 | The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, 8 | “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, “OPTIONAL” in this 9 | document should be interpreted as described in [1]RFC2119 <#fn:1>. 10 | 11 | 12 | Introduction 13 | 14 | Erlang on Xen makes extensive use of 9p protocol for a multitude of 15 | tasks, including code loading, storage access, node monitoring, message 16 | passing, etc. In most cases, the standard semantics of the protocol is 17 | enough. However, in a few cases limitations of the protocol gets in the way. 18 | 19 | Dropped transport connections 20 | 9p connections are tightly coupled to the underlying transport (TCP) 21 | connections. The loss of TCP connection — a frequent occurence 22 | during instance migration — means that all Fids are lost. 23 | Simple operations too chatty 24 | A simple operation, such as writing “0” to a synthetic file, require 25 | multiple network roundtrips: walk to file, open Fid, write data, 26 | clunk Fid. This makes many administrative tasks noticably slow. 27 | 28 | The 9p protocol extension — 9P2000.e — is introduced to address these 29 | two issues. Erlang on Xen uses this protocol version for internode 30 | communications. 31 | 32 | 33 | Overview 34 | 35 | 9P2000.e is the extension of 9P2000 protocol [2]9P2000 <#fn:2>. It adds 36 | several new protocol operations as described below. Semantics of 37 | standard protocol operations are left unchanged. 38 | 39 | A new operation — session — reestablishes a session upon after 40 | reconnecting a transport. All Fids are preserved in the reestablished 41 | session. 42 | 43 | Also the protocol extension adds a few new operations that act as 44 | macro-operations of frequently used sequences. 45 | 46 | The server that implements 9P2000.e should fall back gracefully to use 47 | 9P2000 protocol by disabling the newly introduced operations. 48 | 49 | 50 | New messages 51 | 52 | size[4] Tsession tag[2] key[8] 53 | size[4] Rsession tag[2] 54 | 55 | size[4] Tsread tag[2] fid[4] nwname[2] nwname*(wname[s]) 56 | size[4] Rsread tag[2] count[4] data[count] 57 | 58 | size[4] Tswrite tag[2] fid[4] nwname[2] nwname*(wname[s]) count[4] data[count] 59 | size[4] Rswrite tag[2] count[4] 60 | 61 | 62 | The proposed numeric values for the new commands are as follows: 63 | 64 | Value Commmand 65 | 150 Tsession 66 | 151 Rsession 67 | 152 Tsread 68 | 153 Rsread 69 | 154 Tswrite 70 | 155 Rswrite 71 | 72 | 73 | New operations 74 | 75 | 76 | session - reestablish a session 77 | 78 | |size[4] Tsession tag[2] key[8] 79 | size[4] Rsession tag[2] 80 | | 81 | 82 | When a client performs authentication it may establish a session key. If 83 | transport connection is lost and reconnected the client may decide to 84 | use the session message to request reestablishing of the session. A 85 | successful reply means that the session is reestablished and all 86 | connection Fids are still valid. 87 | 88 | An error reply means that the session can not be reestablished. The 89 | client may decide to continue, treating the connection as completely new. 90 | 91 | The session message must be the first message must be the first message 92 | that follows a successful version negotiation. The tag of the session 93 | message must be set to NOTAG (~0). A rerror message is returned if the 94 | session can not be recovered. 95 | 96 | 9P2000.e protocol uses MUMBLE messages for authentication [3]mumble 97 | <#fn:3>. A MUMBLE message contains the session key for the new session. 98 | 99 | 100 | sread - read entire file contents 101 | 102 | |size[4] Tsread tag[2] fid[4] nwname[2] nwname*(wname[s]) 103 | size[4] Rsread tag[2] count[4] data[count] 104 | | 105 | 106 | The operation reads the entire contents of a file. A file is identifier 107 | by walking to a series of wnames starting with fid. The operation 108 | combines walking to a file, opening it, reading its content, and 109 | clunking a fid. The new operation minimizes network roundtrips when 110 | reading small files. 111 | 112 | 113 | swrite - overwrite file contents 114 | 115 | |size[4] Tswrite tag[2] fid[4] nwname[2] nwname*(wname[s]) count[4] data[count] 116 | size[4] Rswrite tag[2] count[4] 117 | | 118 | 119 | The operation overwrites the file contents with data. The file is 120 | created if it does not exist. 121 | 122 | 123 | Discussion 124 | 125 | The session key should be kept secret as knowing it may allow hijacking 126 | of a 9p connection. 127 | 128 | The Erlang extension does not change the semantics of the standard 129 | 9P2000 operations. This should facilitate a graceful fallback to plain 130 | 9P2000 protocol if the server does not support the extension. 131 | 132 | ------------------------------------------------------------------------ 133 | 134 | 1. RFC2119 135 | 136 | Bradner, S., “Key words for use in RFCs to Indicate Requirement 137 | Levels,” BCP 14, RFC 2119, March 1997. 138 | 139 | 2. 9P2000 140 | 141 | 9P2000 Protocol Specification, Plan 9 Manual Section 5 142 | (http://man.cat-v.org/plan_9/5/). 143 | 144 | 3. mumble 145 | 146 | MUMBLE authentication scheme, Cloudozer LLP, 2012. 147 | 148 | -------------------------------------------------------------------------------- /test/test_messageParser.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /******************************************************************************* 17 | * libstyxe Unit Test Suit 18 | * @file: test/test_messageParser.cpp 19 | * @author: soultaker 20 | * 21 | *******************************************************************************/ 22 | #include "styxe/messageParser.hpp" 23 | 24 | #include 25 | 26 | #include 27 | 28 | 29 | using namespace Solace; 30 | using namespace styxe; 31 | 32 | 33 | TEST(P9, testHeaderSize) { 34 | ASSERT_EQ(4u + 1u + 2u, headerSize()); 35 | } 36 | 37 | 38 | TEST(P9, canParsingValidMessageHeader) { 39 | // Form a normal message with no data: 40 | byte buffer[16]; 41 | auto byteStream = ByteWriter{wrapMemory(buffer)}; 42 | styxe::Encoder encoder{byteStream}; 43 | encoder << makeHeaderWithPayload(asByte(MessageType::TVersion), 1, 0); 44 | 45 | auto reader = ByteReader{byteStream.viewWritten()}; 46 | auto res = parseMessageHeader(reader); 47 | ASSERT_TRUE(res.isOk()); 48 | 49 | auto header = res.unwrap(); 50 | ASSERT_EQ(4u + 1u + 2u, header.messageSize); 51 | ASSERT_EQ(asByte(MessageType::TVersion), header.type); 52 | ASSERT_EQ(1, header.tag); 53 | } 54 | 55 | 56 | TEST(P9, parsingMessageHeaderWithUnknownMessageType) { 57 | // Form a normal message with no data: 58 | byte buffer[16]; 59 | auto byteStream = ByteWriter{wrapMemory(buffer)}; 60 | styxe::Encoder encoder{byteStream}; 61 | encoder << makeHeaderWithPayload(255, 1, 0); 62 | 63 | auto reader = ByteReader{byteStream.viewWritten()}; 64 | ASSERT_TRUE(parseMessageHeader(reader).isOk()); 65 | } 66 | 67 | 68 | TEST(P9, failParsingHeaderWithInsufficientData) { 69 | byte buffer[16]; 70 | auto byteStream = ByteWriter{wrapMemory(buffer)}; 71 | 72 | // Only write one header field. Should be not enough data to read a header. 73 | byteStream.writeLE(headerSize()); // type and tag are not written deliberately 74 | 75 | auto reader = ByteReader{byteStream.viewWritten()}; 76 | auto maybeHeader = parseMessageHeader(reader); 77 | ASSERT_TRUE(maybeHeader.isError()); 78 | } 79 | 80 | 81 | TEST(P9, testParsingIllformedMessageHeader) { 82 | byte buffer[16]; 83 | auto byteStream = ByteWriter{wrapMemory(buffer)}; 84 | // Set declared message size less then header size. 85 | byteStream.writeLE(byte{3}); 86 | byteStream.writeLE(asByte(MessageType::TVersion)); 87 | byteStream.writeLE(Tag{1}); 88 | 89 | auto reader = ByteReader{byteStream.viewWritten()}; 90 | ASSERT_TRUE(parseMessageHeader(reader).isError()); 91 | } 92 | 93 | 94 | TEST(P9, parsingIllFormedHeaderForMessagesLargerMTUShouldError) { 95 | byte buffer[16]; 96 | auto byteStream = ByteWriter{wrapMemory(buffer)}; 97 | 98 | styxe::Encoder encoder{byteStream}; 99 | // Set declared message size to be more then negotiated message size 100 | encoder << makeHeaderWithPayload(asByte(MessageType::TVersion), 1, 100); 101 | 102 | auto reader = ByteReader{byteStream.viewWritten()}; 103 | auto maybeHeader = parseMessageHeader(reader); 104 | ASSERT_TRUE(maybeHeader.isOk()); 105 | 106 | EXPECT_FALSE(validateHeader(*maybeHeader, 16, 128)); 107 | } 108 | 109 | 110 | 111 | TEST(P9, parseIncorrectlySizedSmallerResponse) { 112 | byte buffer[16]; 113 | auto byteStream = ByteWriter{wrapMemory(buffer)}; 114 | 115 | // Set declared message size to be more then negotiated message size 116 | styxe::Encoder encoder{byteStream}; 117 | encoder << makeHeaderWithPayload(asByte(MessageType::RVersion), 1, sizeof(int32)) 118 | << byte{3}; // The payload is just 1 byte and not 4 as declared by the header. 119 | 120 | auto reader = ByteReader{byteStream.viewWritten()}; 121 | auto header = parseMessageHeader(reader); 122 | ASSERT_TRUE(header.isOk()); 123 | 124 | auto message = parseVersionRequest(*header, reader, 12); 125 | ASSERT_TRUE(message.isError()); 126 | } 127 | 128 | 129 | TEST(P9, parseIncorrectlySizedLargerResponse) { 130 | byte buffer[16]; 131 | auto byteStream = ByteWriter{wrapMemory(buffer)}; 132 | 133 | // Set declared message size to be more then negotiated message size 134 | styxe::Encoder encoder{byteStream}; 135 | encoder << makeHeaderWithPayload(asByte(MessageType::RVersion), 1, sizeof(int32)) 136 | << uint64{999999}; // The payload is 8 bytes and not 4 as declared by the header. 137 | 138 | auto reader = ByteReader{byteStream.viewWritten()}; 139 | auto header = parseMessageHeader(reader); 140 | ASSERT_TRUE(header.isOk()); 141 | 142 | auto message = parseVersionRequest(*header, reader, 12); 143 | ASSERT_TRUE(message.isError()); 144 | } 145 | 146 | 147 | TEST(P9, creatingUnsupportedVersionParserShouldFail) { 148 | ASSERT_TRUE(createRequestParser("Fancy", 128).isError()); 149 | ASSERT_TRUE(createResponseParser("Style", 64).isError()); 150 | } 151 | -------------------------------------------------------------------------------- /test/ci/teamcity_messages.cpp: -------------------------------------------------------------------------------- 1 | /* Copyright 2011 JetBrains s.r.o. 2 | * 3 | * Licensed under the Apache License, Version 2.0 (the "License"); 4 | * you may not use this file except in compliance with the License. 5 | * You may obtain a copy of the License at 6 | * 7 | * http://www.apache.org/licenses/LICENSE-2.0 8 | * 9 | * Unless required by applicable law or agreed to in writing, software 10 | * distributed under the License is distributed on an "AS IS" BASIS, 11 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 | * See the License for the specific language governing permissions and 13 | * limitations under the License. 14 | * 15 | * $Revision: 88625 $ 16 | */ 17 | 18 | #include "teamcity_messages.h" 19 | 20 | #include 21 | #include 22 | 23 | namespace jetbrains { 24 | namespace teamcity { 25 | 26 | std::string getFlowIdFromEnvironment() { 27 | #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) 28 | char *flowId = nullptr; 29 | size_t sz = 0; 30 | std::string result; 31 | if(!_dupenv_s(&flowId, &sz,"TEAMCITY_PROCESS_FLOW_ID")) { 32 | result = flowId != nullptr ? flowId : ""; 33 | free(flowId); 34 | } 35 | 36 | return result; 37 | #else 38 | const char *flowId = getenv("TEAMCITY_PROCESS_FLOW_ID"); 39 | return flowId == nullptr ? "" : flowId; 40 | #endif 41 | } 42 | 43 | bool underTeamcity() { 44 | #if defined(WIN32) || defined(_WIN32) || defined(__WIN32) && !defined(__CYGWIN__) 45 | char *teamCityProjectName = 0; 46 | size_t sz = 0; 47 | bool result = false; 48 | if(!_dupenv_s(&teamCityProjectName, &sz, "TEAMCITY_PROJECT_NAME")) { 49 | result = teamCityProjectName != nullptr; 50 | free(teamCityProjectName); 51 | } 52 | 53 | return result; 54 | #else 55 | return getenv("TEAMCITY_PROJECT_NAME") != nullptr; 56 | #endif 57 | } 58 | 59 | TeamcityMessages::TeamcityMessages() 60 | : m_out(&std::cout) 61 | {} 62 | 63 | void TeamcityMessages::setOutput(std::ostream &out) { 64 | m_out = &out; 65 | } 66 | 67 | std::string TeamcityMessages::escape(std::string s) { 68 | std::string result; 69 | 70 | for (size_t i = 0; i < s.length(); i++) { 71 | char c = s[i]; 72 | 73 | switch (c) { 74 | case '\n': result.append("|n"); break; 75 | case '\r': result.append("|r"); break; 76 | case '\'': result.append("|'"); break; 77 | case '|': result.append("||"); break; 78 | case ']': result.append("|]"); break; 79 | default: result.append(&c, 1); 80 | } 81 | } 82 | 83 | return result; 84 | } 85 | 86 | void TeamcityMessages::openMsg(const std::string &name) { 87 | // endl for http://jetbrains.net/tracker/issue/TW-4412 88 | *m_out << std::endl << "##teamcity[" << name; 89 | } 90 | 91 | void TeamcityMessages::closeMsg() { 92 | *m_out << "]"; 93 | // endl for http://jetbrains.net/tracker/issue/TW-4412 94 | *m_out << std::endl; 95 | } 96 | 97 | void TeamcityMessages::writeProperty(std::string name, std::string value) { 98 | *m_out << " " << name << "='" << escape(value) << "'"; 99 | } 100 | 101 | void TeamcityMessages::suiteStarted(std::string name, std::string flowid) { 102 | openMsg("testSuiteStarted"); 103 | writeProperty("name", name); 104 | if(flowid.length() > 0) { 105 | writeProperty("flowId", flowid); 106 | } 107 | 108 | closeMsg(); 109 | } 110 | 111 | void TeamcityMessages::suiteFinished(std::string name, std::string flowid) { 112 | openMsg("testSuiteFinished"); 113 | writeProperty("name", name); 114 | if(flowid.length() > 0) { 115 | writeProperty("flowId", flowid); 116 | } 117 | 118 | closeMsg(); 119 | } 120 | 121 | void TeamcityMessages::testStarted(std::string name, std::string flowid, bool captureStandardOutput) { 122 | openMsg("testStarted"); 123 | writeProperty("name", name); 124 | if(flowid.length() > 0) { 125 | writeProperty("flowId", flowid); 126 | } 127 | 128 | if(captureStandardOutput) { 129 | writeProperty("captureStandardOutput", "true"); // false by default 130 | } 131 | 132 | closeMsg(); 133 | } 134 | 135 | void TeamcityMessages::testFinished(std::string name, int durationMs, std::string flowid) { 136 | openMsg("testFinished"); 137 | 138 | writeProperty("name", name); 139 | 140 | if(flowid.length() > 0) { 141 | writeProperty("flowId", flowid); 142 | } 143 | 144 | if(durationMs >= 0) { 145 | std::stringstream out(std::ios_base::out); 146 | out << durationMs; 147 | writeProperty("duration", out.str()); 148 | } 149 | 150 | closeMsg(); 151 | } 152 | 153 | void TeamcityMessages::testFailed(std::string name, std::string message, std::string details, std::string flowid) { 154 | openMsg("testFailed"); 155 | writeProperty("name", name); 156 | writeProperty("message", message); 157 | writeProperty("details", details); 158 | if(flowid.length() > 0) { 159 | writeProperty("flowId", flowid); 160 | } 161 | 162 | closeMsg(); 163 | } 164 | 165 | void TeamcityMessages::testIgnored(std::string name, std::string message, std::string flowid) { 166 | openMsg("testIgnored"); 167 | writeProperty("name", name); 168 | writeProperty("message", message); 169 | if(flowid.length() > 0) { 170 | writeProperty("flowId", flowid); 171 | } 172 | 173 | closeMsg(); 174 | } 175 | 176 | void TeamcityMessages::testOutput(std::string name, std::string output, std::string flowid, bool isStdError) { 177 | openMsg(isStdError ? "testStdErr" : "testStdOut"); 178 | writeProperty("name", name); 179 | writeProperty("out", output); 180 | if(flowid.length() > 0) { 181 | writeProperty("flowId", flowid); 182 | } 183 | 184 | closeMsg(); 185 | } 186 | 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /include/styxe/9p2000e.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | #ifndef STYXE_9P2000E_HPP 18 | #define STYXE_9P2000E_HPP 19 | 20 | #include "styxe/9p2000.hpp" 21 | 22 | 23 | namespace styxe { 24 | namespace _9P2000E { 25 | 26 | /// Protocol version literal 27 | extern const Solace::StringLiteral kProtocolVersion; 28 | 29 | 30 | enum class MessageType : Solace::byte { 31 | /** 32 | * 9P2000.e extension 33 | */ 34 | TSession = 150, 35 | RSession, 36 | TShortRead = 152, 37 | RShortRead, 38 | TShortWrite = 154, 39 | RShortWrite, 40 | }; 41 | 42 | 43 | /// 9P2000 protocol Erlang extension new messages 44 | struct Request : public styxe::Request { 45 | 46 | struct Partial { 47 | /// Partial Short read message. @see Request::ShortRead for details. 48 | struct ShortRead { 49 | Fid fid; //!< Fid of the root directory to walk the path from. 50 | }; 51 | 52 | /// Partial Short Write message. @see Request::ShortRead for details. 53 | struct ShortWrite { 54 | Fid fid; //!< Fid of the root directory to walk the path from. 55 | }; 56 | }; 57 | 58 | /// A request to re-establish a session. 59 | struct Session { 60 | Solace::byte key[8]; //!< A key of the previously established session. 61 | }; 62 | 63 | /// A request to read entire file contents. 64 | struct ShortRead: public Partial::ShortRead { 65 | WalkPath path; //!< A path to the file to be read. 66 | }; 67 | 68 | /// A request to overwrite file contents. 69 | struct ShortWrite: public Partial::ShortWrite { 70 | WalkPath path; //!< A path to the file to be read. 71 | Solace::MemoryView data; //!< A data to be written into the file. 72 | }; 73 | 74 | }; 75 | 76 | 77 | /// 9P2000 protocol Erlang extension new messages 78 | struct Response : public styxe::Response { 79 | /// Session re-establishment response 80 | struct Session {}; 81 | 82 | /// Read resopose 83 | struct ShortRead { 84 | /// View in to the response buffer where raw read data is. 85 | Solace::MemoryView data; 86 | }; 87 | 88 | /// Write response 89 | struct ShortWrite { 90 | size_type count; //!< Number of bytes written 91 | }; 92 | 93 | struct Partial { 94 | struct ShortRead {}; 95 | }; 96 | }; 97 | 98 | 99 | /** 100 | * Get a string representation of the message name given the op-code. 101 | * @param messageType Message op-code to convert to a string. 102 | * @return A string representation of a given message code. 103 | */ 104 | Solace::StringView 105 | messageTypeToString(Solace::byte type) noexcept; 106 | 107 | 108 | } // end of namespace _9P2000E 109 | 110 | 111 | inline constexpr Solace::byte asByte(_9P2000E::MessageType type) noexcept { 112 | return static_cast(type); 113 | } 114 | 115 | 116 | template <> 117 | constexpr Solace::byte messageCodeOf<_9P2000E::Request::Session>() noexcept 118 | { return asByte(_9P2000E::MessageType::TSession); } 119 | 120 | 121 | template <> 122 | constexpr Solace::byte messageCodeOf<_9P2000E::Request::ShortRead>() noexcept 123 | { return asByte(_9P2000E::MessageType::TShortRead); } 124 | 125 | template <> 126 | constexpr Solace::byte messageCodeOf<_9P2000E::Request::ShortWrite>() noexcept 127 | { return asByte(_9P2000E::MessageType::TShortWrite); } 128 | 129 | template <> 130 | constexpr Solace::byte messageCodeOf<_9P2000E::Response::Session>() noexcept 131 | { return asByte(_9P2000E::MessageType::RSession); } 132 | 133 | template <> 134 | constexpr Solace::byte messageCodeOf<_9P2000E::Response::ShortRead>() noexcept 135 | { return asByte(_9P2000E::MessageType::RShortRead); } 136 | 137 | template <> 138 | constexpr Solace::byte messageCodeOf<_9P2000E::Response::ShortWrite>() noexcept 139 | { return asByte(_9P2000E::MessageType::RShortWrite); } 140 | 141 | 142 | Solace::Result 143 | operator>> (Solace::ByteReader& data, _9P2000E::Request::Session& dest); 144 | 145 | Solace::Result 146 | operator>> (Solace::ByteReader& data, _9P2000E::Request::ShortRead& dest); 147 | 148 | Solace::Result 149 | operator>> (Solace::ByteReader& data, _9P2000E::Request::ShortWrite& dest); 150 | 151 | Solace::Result 152 | operator>> (Solace::ByteReader& data, _9P2000E::Response::Session& dest); 153 | 154 | Solace::Result 155 | operator>> (Solace::ByteReader& data, _9P2000E::Response::ShortRead& dest); 156 | 157 | Solace::Result 158 | operator>> (Solace::ByteReader& data, _9P2000E::Response::ShortWrite& dest); 159 | 160 | 161 | /** Create Session request. */ 162 | RequestWriter& operator<< (RequestWriter& writer, _9P2000E::Request::Session const& request); 163 | 164 | /** Create ShortRead request. */ 165 | RequestWriter& operator<< (RequestWriter& writer, _9P2000E::Request::ShortRead const& request); 166 | 167 | /** Create short Write request. */ 168 | RequestWriter& operator<< (RequestWriter& writer, _9P2000E::Request::ShortWrite const& request); 169 | 170 | /** Create Session restore response. */ 171 | ResponseWriter& operator<< (ResponseWriter& writer, _9P2000E::Response::Session const& response); 172 | 173 | /** Create ShortRead response. */ 174 | ResponseWriter& operator<< (ResponseWriter& writer, _9P2000E::Response::ShortRead const& response); 175 | 176 | /** Create ShortWriteresponse. */ 177 | ResponseWriter& operator<< (ResponseWriter& writer, _9P2000E::Response::ShortWrite const& response); 178 | 179 | 180 | PartialPathWriter operator<< (RequestWriter& writer, _9P2000E::Request::Partial::ShortRead const& request); 181 | PathDataWriter operator<< (RequestWriter& writer, _9P2000E::Request::Partial::ShortWrite const& request); 182 | 183 | 184 | } // end of namespace styxe 185 | #endif // STYXE_9P2000E_HPP 186 | -------------------------------------------------------------------------------- /cmake/compile_flags.cmake: -------------------------------------------------------------------------------- 1 | include(CheckCXXCompilerFlag) 2 | 3 | # Require at least C++17 4 | set(CMAKE_CXX_STANDARD 17) 5 | set(CMAKE_CXX_STANDARD_REQUIRED on) 6 | 7 | # Checks for optional extra compiler options 8 | check_cxx_compiler_flag("-fvtable-verify" WITH_VTABLE_VERIFY) 9 | check_cxx_compiler_flag("-fstack-protector" WITH_STACK_PROTECTOR) 10 | check_cxx_compiler_flag("-fno-omit-frame-pointer" WITH_FRAME_POINTER) 11 | check_cxx_compiler_flag("-fsanitize-address-use-after-scope" WITH_SANITIZE_ADDRESS_USE_AFTER_SCOPE) 12 | check_cxx_compiler_flag("-fsanitize=signed-integer-overflow" WITH_SANITIZE_SIGNED_INTEGER_OVERFLOW) 13 | check_cxx_compiler_flag("-fsanitize=unsigned-integer-overflow" WITH_SANITIZE_UNSIGNED_INTEGER_OVERFLOW) 14 | 15 | # Some warning are not universally supported 16 | check_cxx_compiler_flag("-Wlogical-op" WITH_WARN_LOGICAL_OP) 17 | check_cxx_compiler_flag("-Wstrict-null-sentinel" WITH_WARN_STRICT_NULL_SENTINEL) 18 | check_cxx_compiler_flag("-Wimplicit-fallthrough=2" WITH_IMPLICIT_FALLTHROUGH) 19 | check_cxx_compiler_flag("-Wnoexcept" WITH_WNOEXCEPT) 20 | 21 | 22 | # Set compiler flags: 23 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pipe -pedantic -pedantic-errors") 24 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-rtti") 25 | 26 | 27 | # TODO: Make this warning work too! 28 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wconversion") 29 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-conversion") 30 | #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wswitch-enum -Wswitch-default") 31 | 32 | # Enable all the warnings one can get 33 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wextra -Wshadow -Wmissing-include-dirs -Wpacked -Wredundant-decls") 34 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnon-virtual-dtor -Woverloaded-virtual") 35 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wold-style-cast") 36 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wfloat-equal") 37 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wpointer-arith -Wcast-align -Wcast-qual -Wwrite-strings") 38 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wunused -Wunused-function -Wunused-label -Wunused-value -Wunused-variable") 39 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Winit-self -Wdisabled-optimization") 40 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wsign-promo -Wstrict-overflow=5") 41 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wformat=2 -Wformat-security") 42 | 43 | # -Werror 44 | # -Wundef -Wmissing-declarations 45 | # Not compatible wit GTest: -Wctor-dtor-privacy 46 | # Not friendly to GTest: -Wmissing-format-attribute") 47 | # Too many lies: set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Weffc++") 48 | 49 | # New in gcc-7 50 | if (WITH_IMPLICIT_FALLTHROUGH) 51 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wimplicit-fallthrough=2") 52 | endif(WITH_IMPLICIT_FALLTHROUGH) 53 | 54 | # GCC specific 55 | if (WITH_WNOEXCEPT) 56 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wnoexcept") 57 | endif (WITH_WNOEXCEPT) 58 | if (WITH_WARN_LOGICAL_OP) 59 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wlogical-op") 60 | endif (WITH_WARN_LOGICAL_OP) 61 | if (WITH_WARN_STRICT_NULL_SENTINEL) 62 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wstrict-null-sentinel") 63 | endif (WITH_WARN_STRICT_NULL_SENTINEL) 64 | 65 | 66 | # It's better to use fortify_sources if compiler supports it 67 | if (UNIX AND NOT APPLE) 68 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_FORTIFY_SOURCE=2") 69 | 70 | # Some extra defins for libstdc++: 71 | # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_GLIBCXX_ASSERTIONS -D_GLIBCXX_DEBUG -D_GLIBCXX_SANITIZE_VECTOR") 72 | endif() 73 | 74 | 75 | if (WITH_STACK_PROTECTOR) 76 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fstack-protector-strong") 77 | endif (WITH_STACK_PROTECTOR) 78 | # --------------------------------- 79 | # Debug mode flags 80 | # --------------------------------- 81 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -ggdb3 -D _DEBUG -D DEBUG") 82 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fstack-protector-all") 83 | 84 | if (WITH_FRAME_POINTER) 85 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer") 86 | endif (WITH_FRAME_POINTER) 87 | 88 | if (WITH_VTABLE_VERIFY) 89 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fvtable-verify=std") 90 | endif (WITH_VTABLE_VERIFY) 91 | 92 | 93 | # --------------------------------- 94 | # Release Optimization flags 95 | # --------------------------------- 96 | set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Ofast -D NDEBUG") 97 | 98 | if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" AND NOT "MINGW") 99 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fuse-ld=gold") 100 | # Link time optimization: currently disabled as requires build system support. 101 | # set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -fuse-linker-plugin -flto") 102 | endif() 103 | 104 | 105 | # --------------------------------- 106 | # When sanitizers are ON 107 | # --------------------------------- 108 | if (SANITIZE) 109 | check_cxx_compiler_flag("-fsanitize=leak" WITH_SANITIZE_LEAK) 110 | if (WITH_SANITIZE_LEAK) 111 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined,leak") 112 | else() 113 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=address,undefined") 114 | message(STATUS, "Leak sanitizer not supported") 115 | endif(WITH_SANITIZE_LEAK) 116 | 117 | if (WITH_SANITIZE_SIGNED_INTEGER_OVERFLOW) 118 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=signed-integer-overflow") 119 | endif(WITH_SANITIZE_SIGNED_INTEGER_OVERFLOW) 120 | 121 | if (WITH_SANITIZE_UNSIGNED_INTEGER_OVERFLOW) 122 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fsanitize=unsigned-integer-overflow") 123 | endif(WITH_SANITIZE_UNSIGNED_INTEGER_OVERFLOW) 124 | 125 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -fsanitize=address") 126 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -fsanitize=address") 127 | # CMake 3.13 can replace with: add_link_options(-fsanitize=address) 128 | endif() 129 | 130 | 131 | # --------------------------------- 132 | # Debug build with test coverage 133 | # --------------------------------- 134 | if (COVERAGE) 135 | if (CMAKE_BUILD_TYPE STREQUAL "Debug") 136 | set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} --coverage") # enabling coverage 137 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} --coverage") 138 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --coverage") 139 | else() 140 | message(FATAL_ERROR "Coverage can only be enabled in Debug mode") 141 | endif() 142 | endif() 143 | 144 | 145 | # --------------------------------- 146 | # Include profile information 147 | # --------------------------------- 148 | if (PROFILE) 149 | if (CMAKE_BUILD_TYPE STREQUAL "Debug") 150 | set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -pg") 151 | set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -pg") 152 | set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -pg") 153 | else() 154 | message(FATAL_ERROR "Profiling requires non optimized build") 155 | endif() 156 | endif() 157 | -------------------------------------------------------------------------------- /Makefile.in: -------------------------------------------------------------------------------- 1 | # This is a template for project build automation with make. 2 | # The actual build system in use if CMake. 3 | # This file is not meant to be used directly. Please generate a working Makefile for your system via configure script: 4 | # ./configure [configuration options] 5 | 6 | # Project directory layout 7 | BUILD_DIR = build 8 | ANALYZE_DIR = build-analyze 9 | INCLUDE_DIR = include 10 | SRC_DIR = src 11 | TEST_DIR = test 12 | 13 | MODULE_HEADERS = ${INCLUDE_DIR}/* 14 | MODULE_SRC = ${SRC_DIR}/* 15 | MODULE_TESTS = ${TEST_DIR}/* 16 | 17 | DEP_INSTALL = $(BUILD_DIR)/conan.lock 18 | GENERATED_MAKE = $(BUILD_DIR)/CMakeFiles 19 | 20 | LIBNAME = lib$(PROJECT).a 21 | LIB_TAGRET = $(BUILD_DIR)/lib/$(LIBNAME) 22 | 23 | TESTNAME = test_$(PROJECT) 24 | TEST_TAGRET = $(BUILD_DIR)/bin/$(TESTNAME) 25 | 26 | DOC_DIR = docs 27 | DOC_TARGET_HTML = $(DOC_DIR)/html 28 | 29 | COVERAGE_REPORT = coverage.info 30 | CPPCHECK_VERSION = '2.3' 31 | 32 | ifdef sanitize 33 | SANITIZE = ON 34 | else 35 | SANITIZE = OFF 36 | endif 37 | 38 | ifdef dbg 39 | BUILD_TYPE = Debug 40 | else 41 | BUILD_TYPE = Release 42 | endif 43 | 44 | ifdef coverage 45 | COVERAGE = ON 46 | else 47 | COVERAGE = OFF 48 | endif 49 | 50 | ifdef profile 51 | ENABLE_PROFILE = ON 52 | else 53 | ENABLE_PROFILE = OFF 54 | endif 55 | 56 | ifdef pkgconfig 57 | PKG_CONFIG = ON 58 | else 59 | PKG_CONFIG = OFF 60 | endif 61 | 62 | ifdef CONAN_PROFILE 63 | CONAN_INSTALL_PROFILE = --profile ${CONAN_PROFILE} 64 | endif 65 | 66 | 67 | # First tagret that starts not with '.'' - is a default target to run 68 | all: lib 69 | 70 | 71 | #------------------------------------------------------------------------------- 72 | # CMake wrapper 73 | #------------------------------------------------------------------------------- 74 | $(DEP_INSTALL): 75 | mkdir -p $(BUILD_DIR) 76 | conan install -if $(BUILD_DIR) -s build_type=${BUILD_TYPE} . ${CONAN_INSTALL_PROFILE} --build missing 77 | 78 | $(GENERATED_MAKE): $(DEP_INSTALL) 79 | cd $(BUILD_DIR) && cmake -G ${GENERATOR} -DPROFILE=${ENABLE_PROFILE} -DCOVERAGE=${COVERAGE} -DSANITIZE=${SANITIZE} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DPKG_CONFIG=${PKG_CONFIG} .. 80 | 81 | #------------------------------------------------------------------------------- 82 | # Build the project 83 | #------------------------------------------------------------------------------- 84 | .PHONY: $(LIB_TAGRET) 85 | $(LIB_TAGRET): $(GENERATED_MAKE) $(MODULE_SRC) 86 | cd $(BUILD_DIR) && cmake --build . -j --target $(PROJECT) 87 | 88 | lib: $(LIB_TAGRET) 89 | 90 | 91 | #------------------------------------------------------------------------------- 92 | # Build unit tests 93 | #------------------------------------------------------------------------------- 94 | .PHONY: $(TEST_TAGRET) 95 | $(TEST_TAGRET): $(GENERATED_MAKE) $(MODULE_TESTS) 96 | cd $(BUILD_DIR) && cmake --build . -j --target $(TESTNAME) 97 | 98 | tests: $(LIB_TAGRET) $(TEST_TAGRET) 99 | 100 | 101 | .PHONY: test 102 | test: tests 103 | ./$(TEST_TAGRET) 104 | 105 | 106 | #------------------------------------------------------------------------------- 107 | # Build examples 108 | #------------------------------------------------------------------------------- 109 | .PHONY: examples 110 | examples: $(LIB_TAGRET) 111 | cd $(BUILD_DIR) && cmake --build . -j --target examples 112 | 113 | 114 | #------------------------------------------------------------------------------- 115 | # Build docxygen documentation 116 | #------------------------------------------------------------------------------- 117 | $(DOC_TARGET_HTML): 118 | doxygen $(DOC_DIR)/Doxyfile 119 | 120 | # Build project doxygen docs doxyfile.inc 121 | doc: $(MODULE_HEADERS) $(MODULE_SRC) $(DOC_TARGET_HTML) 122 | 123 | 124 | #------------------------------------------------------------------------------- 125 | # Code Quality Assurance 126 | #------------------------------------------------------------------------------- 127 | ANALYZE_MAKE: $(ANALYZE_DIR) 128 | mkdir -p $(ANALYZE_DIR) 129 | cd $(ANALYZE_DIR) && cmake -DCMAKE_C_COMPILER=$(shell which scan-build) .. 130 | 131 | tools/cppcheck: 132 | # Getting latest cppcheck 133 | # Fix cppcheck version to as master may be is borked sometimes 134 | git clone -b ${CPPCHECK_VERSION} --single-branch --depth 1 https://github.com/danmar/cppcheck.git tools/cppcheck 135 | 136 | tools/cppcheck/cppcheck: tools/cppcheck 137 | $(MAKE) -j2 -C tools/cppcheck cppcheck 138 | 139 | 140 | # TODO: Extra cppcheck options to consider: --enable=style,unusedFunction --inconclusive 141 | # note: don't use --enable=all, 'cause `style` is too verbose 142 | .PHONY: cppcheck 143 | cppcheck: $(MODULE_HEADERS) $(MODULE_SRC) tools/cppcheck/cppcheck 144 | tools/cppcheck/cppcheck --std=c++20 -D __linux__ -D __x86_64__ --inline-suppr -q --error-exitcode=2 \ 145 | --enable=warning,performance,portability,information,missingInclude \ 146 | --report-progress \ 147 | -I include -i test/ci ${SRC_DIR} ${TEST_DIR} examples 148 | 149 | 150 | .PHONY: cpplint 151 | cpplint: $(MODULE_HEADERS) $(MODULE_SRC) 152 | cpplint --recursive --exclude=${TEST_DIR}/ci/* ${INCLUDE_DIR} ${SRC_DIR} ${TEST_DIR} 153 | 154 | scan-build: ANALYZE_MAKE 155 | cd $(ANALYZE_DIR) && scan-build $(MAKE) 156 | 157 | tidy: 158 | clang-tidy -checks=llvm-*,modernize-*,clang-analyzer-*,-modernize-pass-by-value -header-filter=.* \ 159 | ${SRC_DIR}/*.cpp -- -I${INCLUDE_DIR} -std=c++17 160 | 161 | 162 | .PHONY: codecheck 163 | codecheck: cpplint cppcheck #scan-build 164 | 165 | 166 | 167 | #------------------------------------------------------------------------------- 168 | # Runtime Quality Control 169 | #------------------------------------------------------------------------------- 170 | 171 | valgrind-memcheck: $(TEST_TAGRET) 172 | valgrind --tool=memcheck --trace-children=yes --track-fds=yes --redzone-size=128 --error-exitcode=3 \ 173 | --leak-check=full --track-origins=yes --show-reachable=yes --show-leak-kinds=all --errors-for-leak-kinds=all --partial-loads-ok=no \ 174 | $(TEST_TAGRET) 175 | 176 | 177 | # Plugin more valgrind checks when they are available 178 | .PHONY: verify 179 | verify: valgrind-memcheck 180 | 181 | 182 | $(COVERAGE_REPORT): $(TEST_TAGRET) 183 | ifeq ($(COVERAGE), OFF) 184 | $(error Coverage info generation is not configured. Run ./configure --enable-coverage to enable) 185 | else 186 | ./$(TEST_TAGRET) 187 | 188 | # capture coverage info 189 | lcov --directory . --capture --output-file $@ 190 | # filter out system and test code 191 | lcov --remove coverage.info 'test/*' '/usr/*' 'external/*' --output-file $@ 192 | endif 193 | 194 | coverage: $(COVERAGE_REPORT) 195 | 196 | 197 | coverage_report: $(COVERAGE_REPORT) 198 | lcov --list $(COVERAGE_REPORT) 199 | 200 | 201 | #------------------------------------------------------------------------------- 202 | # Install 203 | #------------------------------------------------------------------------------- 204 | 205 | .PHONY: install 206 | install: $(LIB_TAGRET) 207 | cd $(BUILD_DIR) && cmake \ 208 | -DCMAKE_INSTALL_PREFIX=$(PREFIX) \ 209 | -DSANITIZE=${SANITIZE} \ 210 | .. 211 | #cd $(BUILD_DIR) && cmake --build . --target install --config ${BUILD_TYPE} 212 | $(MAKE) -C $(BUILD_DIR) install DESTDIR=$(DESTDIR) 213 | 214 | 215 | .PHONY: uninstall 216 | uninstall: 217 | $(RM) -f $(DESTDIR)$(PREFIX)/lib/$(LIBNAME) 218 | $(RM) -rf $(DESTDIR)$(PREFIX)/include/$(PROJECT) 219 | 220 | 221 | #------------------------------------------------------------------------------- 222 | # Packaging 223 | #------------------------------------------------------------------------------- 224 | 225 | .PHONY: debian 226 | debian: 227 | dpkg-buildpackage -d 228 | 229 | .PHONY: debian-clean 230 | debian-clean: 231 | dpkg-buildpackage -d -T clean 232 | 233 | #------------------------------------------------------------------------------- 234 | # Cleanup 235 | #------------------------------------------------------------------------------- 236 | 237 | .PHONY: clean 238 | clean: 239 | $(RM) -rf $(DOC_TARGET_HTML) $(BUILD_DIR) $(COVERAGE_REPORT) 240 | $(RM) tools/cppcheck/cppcheck 241 | -------------------------------------------------------------------------------- /src/9p2000.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "styxe/9p2000.hpp" 18 | #include "styxe/messageParser.hpp" 19 | #include "styxe/version.hpp" 20 | 21 | #include "parse_helper.hpp" 22 | 23 | #include 24 | #include // std::min 25 | 26 | 27 | using namespace Solace; 28 | using namespace styxe; 29 | 30 | static const Version kLibVersion{STYXE_VERSION_MAJOR, STYXE_VERSION_MINOR, STYXE_VERSION_BUILD}; 31 | 32 | const StringLiteral styxe::kUnknownProtocolVersion{"unknown"}; 33 | const StringLiteral styxe::kProtocolVersion{"9P2000"}; 34 | 35 | const Tag styxe::kNoTag = static_cast(~0); 36 | const Fid styxe::kNoFID = static_cast(~0); 37 | 38 | 39 | const byte OpenMode::READ; 40 | const byte OpenMode::WRITE; 41 | const byte OpenMode::RDWR; 42 | const byte OpenMode::EXEC; 43 | const byte OpenMode::TRUNC; 44 | const byte OpenMode::CEXEC; 45 | const byte OpenMode::RCLOSE; 46 | 47 | 48 | 49 | Version const& 50 | styxe::getVersion() noexcept { 51 | return kLibVersion; 52 | } 53 | 54 | 55 | styxe::Result 56 | styxe::operator>> (ByteReader& data, Response::Walk& dest) { 57 | Decoder decoder{data}; 58 | 59 | // FIXME: Response::Walk can't hold more then 16 qids! 60 | auto result = decoder >> dest.nqids; 61 | for (decltype(dest.nqids) i = 0; i < dest.nqids && result; ++i) { 62 | result = decoder >> dest.qids[i]; 63 | } 64 | 65 | if (!result) 66 | return result.moveError(); 67 | 68 | return styxe::Result{types::okTag, data}; 69 | } 70 | 71 | 72 | styxe::Result 73 | styxe::operator>> (ByteReader& data, Response::Version& dest) { 74 | return decode(data, dest.msize, dest.version); 75 | } 76 | 77 | styxe::Result 78 | styxe::operator>> (ByteReader& data, Response::Auth& dest) { 79 | return decode(data, dest.qid); 80 | } 81 | 82 | styxe::Result 83 | styxe::operator>> (ByteReader& data, Response::Attach& dest) { 84 | return decode(data, dest.qid); 85 | } 86 | 87 | styxe::Result 88 | styxe::operator>> (ByteReader& data, Response::Error& dest) { 89 | return decode(data, dest.ename); 90 | } 91 | 92 | 93 | styxe::Result 94 | styxe::operator>> (ByteReader& data, Response::Open& dest) { 95 | return decode(data, dest.qid, dest.iounit); 96 | } 97 | 98 | styxe::Result 99 | styxe::operator>> (ByteReader& data, Response::Create& dest) { 100 | return decode(data, dest.qid, dest.iounit); 101 | } 102 | 103 | styxe::Result 104 | styxe::operator>> (ByteReader& data, Response::Read& dest) { 105 | return decode(data, dest.data); 106 | } 107 | styxe::Result 108 | styxe::operator>> (ByteReader& data, Response::Write& dest) { 109 | return decode(data, dest.count); 110 | } 111 | 112 | styxe::Result 113 | styxe::operator>> (ByteReader& data, Response::Stat& dest) { 114 | return decode(data, dest.dummySize, dest.data); 115 | } 116 | 117 | styxe::Result 118 | styxe::operator>> (ByteReader& data, Response::Clunk& ) { return styxe::Result{types::okTag, data}; } 119 | 120 | styxe::Result 121 | styxe::operator>> (ByteReader& data, Response::Remove& ) { return styxe::Result{types::okTag, data}; } 122 | 123 | styxe::Result 124 | styxe::operator>> (ByteReader& data, Response::Flush& ) { return styxe::Result{types::okTag, data}; } 125 | 126 | styxe::Result 127 | styxe::operator>> (ByteReader& data, Response::WStat& ) { return styxe::Result{types::okTag, data}; } 128 | 129 | 130 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 131 | /// Request parser 132 | //////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// 133 | styxe::Result 134 | styxe::operator>> (ByteReader& data, Request::Version& dest) { 135 | return decode(data, dest.msize, dest.version); 136 | } 137 | 138 | styxe::Result 139 | styxe::operator>> (ByteReader& data, Request::Auth& dest) { 140 | return decode(data, dest.afid, dest.uname, dest.aname); 141 | } 142 | 143 | styxe::Result 144 | styxe::operator>> (ByteReader& data, Request::Attach& dest) { 145 | return decode(data, dest.fid, dest.afid, dest.uname, dest.aname); 146 | } 147 | 148 | styxe::Result 149 | styxe::operator>> (ByteReader& data, Request::Flush& dest) { 150 | return decode(data, dest.oldtag); 151 | } 152 | 153 | 154 | styxe::Result 155 | styxe::operator>> (ByteReader& data, Request::Walk& dest) { 156 | return decode(data, dest.fid, dest.newfid, dest.path); 157 | } 158 | 159 | styxe::Result 160 | styxe::operator>> (ByteReader& data, Request::Open& dest) { 161 | return decode(data, dest.fid, dest.mode.mode); 162 | } 163 | 164 | styxe::Result 165 | styxe::operator>> (ByteReader& data, Request::Create& dest) { 166 | return decode(data, dest.fid, dest.name, dest.perm, dest.mode.mode); 167 | } 168 | 169 | styxe::Result 170 | styxe::operator>> (ByteReader& data, Request::Read& dest) { 171 | return decode(data, dest.fid, dest.offset, dest.count); 172 | } 173 | 174 | styxe::Result 175 | styxe::operator>> (ByteReader& data, Request::Write& dest) { 176 | return decode(data, dest.fid, dest.offset, dest.data); 177 | } 178 | 179 | styxe::Result 180 | styxe::operator>> (ByteReader& data, Request::Clunk& dest) { 181 | return decode(data, dest.fid); 182 | } 183 | styxe::Result 184 | styxe::operator>> (ByteReader& data, Request::Remove& dest) { 185 | return decode(data, dest.fid); 186 | } 187 | 188 | styxe::Result 189 | styxe::operator>> (ByteReader& data, Request::Stat& dest) { 190 | return decode(data, dest.fid); 191 | } 192 | 193 | styxe::Result 194 | styxe::operator>> (ByteReader& data, Request::WStat& dest) { 195 | return decode(data, dest.fid, dest.stat); 196 | } 197 | 198 | 199 | StringView 200 | styxe::messageTypeToString(byte type) noexcept { 201 | auto mType = static_cast(type); 202 | switch (mType) { 203 | case MessageType::TVersion: return "TVersion"; 204 | case MessageType::RVersion: return "RVersion"; 205 | case MessageType::TAuth: return "TAuth"; 206 | case MessageType::RAuth: return "RAuth"; 207 | case MessageType::TAttach: return "TAttach"; 208 | case MessageType::RAttach: return "RAttach"; 209 | case MessageType::TError: return "TError"; 210 | case MessageType::RError: return "RError"; 211 | case MessageType::TFlush: return "TFlush"; 212 | case MessageType::RFlush: return "RFlush"; 213 | case MessageType::TWalk: return "TWalk"; 214 | case MessageType::RWalk: return "RWalk"; 215 | case MessageType::TOpen: return "TOpen"; 216 | case MessageType::ROpen: return "ROpen"; 217 | case MessageType::TCreate: return "TCreate"; 218 | case MessageType::RCreate: return "RCreate"; 219 | case MessageType::TRead: return "TRead"; 220 | case MessageType::RRead: return "RRead"; 221 | case MessageType::TWrite: return "TWrite"; 222 | case MessageType::RWrite: return "RWrite"; 223 | case MessageType::TClunk: return "TClunk"; 224 | case MessageType::RClunk: return "RClunk"; 225 | case MessageType::TRemove: return "TRemove"; 226 | case MessageType::RRemove: return "RRemove"; 227 | case MessageType::TStat: return "TStat"; 228 | case MessageType::RStat: return "RStat"; 229 | case MessageType::TWStat: return "TWStat"; 230 | case MessageType::RWStat: return "RWStat"; 231 | } 232 | 233 | return "Unsupported"; 234 | } 235 | -------------------------------------------------------------------------------- /include/styxe/9p2000u.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | #ifndef STYXE_9P2000U_HPP 18 | #define STYXE_9P2000U_HPP 19 | 20 | #include "styxe/9p2000.hpp" 21 | 22 | 23 | namespace styxe { 24 | namespace _9P2000U { 25 | 26 | /// Protocol version literal 27 | extern const Solace::StringLiteral kProtocolVersion; 28 | 29 | /// "Magic value" to indicate no valid uid. 30 | extern const Solace::uint32 kNonUid; 31 | 32 | 33 | enum class MessageType : Solace::byte { 34 | /// 9p2000.u adds no new messages but extand existing 35 | }; 36 | 37 | /** 38 | * 9p2000.U extented version of Stat struct. 39 | * @see Stat. 40 | */ 41 | struct StatEx: public Stat { 42 | /* 9p2000.u extensions */ 43 | Solace::StringView extension; //!< UNIX extension to store data about special files (links, devices, pipes, etc.) 44 | Solace::uint32 n_uid; //!< numeric id of the user who owns the file 45 | Solace::uint32 n_gid; //!< numeric id of the group associated with the file 46 | Solace::uint32 n_muid; //!< numeric id of the user who last modified the file 47 | }; 48 | 49 | 50 | /** Encode a file stats into the output stream. 51 | * @param encoder Encoder used to encode the value. 52 | * @param value Value to encode. 53 | * @return Ref to the encoder for fluency. 54 | */ 55 | inline 56 | Encoder& operator<< (Encoder& encoder, StatEx const& value) { 57 | return encoder << static_cast(value) 58 | << value.extension 59 | << value.n_uid 60 | << value.n_gid 61 | << value.n_muid; 62 | } 63 | 64 | 65 | /** Decode a Stat struct from the stream. 66 | * @param decoder A data stream to read a value from. 67 | * @param dest An address where to store decoded value. 68 | * @return Ref to the decoder or Error if operation has failed. 69 | */ 70 | inline 71 | Solace::Result operator>> (Decoder& decoder, StatEx& dest) { 72 | return decoder >> static_cast(dest) 73 | >> dest.extension 74 | >> dest.n_uid 75 | >> dest.n_gid 76 | >> dest.n_muid; 77 | } 78 | 79 | 80 | /// 9P2000.u protocol messages 81 | struct Request { 82 | 83 | /// Messages to establish a connection. 84 | struct Auth : public ::styxe::Request::Auth { 85 | Solace::uint32 n_uname; //!< A numeric uname to map a string to a numeric id. 86 | }; 87 | 88 | /// A fresh introduction from a user on the client machine to the server. 89 | struct Attach : public ::styxe::Request::Attach { 90 | Solace::uint32 n_uname; //!< A numeric uname to map a string to a numeric id. 91 | }; 92 | 93 | /** 94 | * The create request asks the file server to create a new file with the name supplied, 95 | * in the directory (dir) represented by fid, and requires write permission in the directory. 96 | * The owner of the file is the implied user id of the request. 97 | */ 98 | struct Create : public ::styxe::Request::Create { 99 | Solace::StringView extension; //!< extension 100 | }; 101 | 102 | /** 103 | * A request to update file stat fields. 104 | */ 105 | struct WStat { 106 | Fid fid; //!< Fid of the file to update stats on. 107 | StatEx stat; //!< New stats to update file info to. 108 | }; 109 | 110 | }; 111 | 112 | 113 | /// 9P2000.u messages 114 | struct Response { 115 | 116 | /// Error resoponse from a server 117 | struct Error: public ::styxe::Response::Error { 118 | Solace::uint32 errcode; //!< Error code 119 | }; 120 | 121 | /// Stat response 122 | struct Stat { 123 | var_datum_size_type dummySize; //!< Dummy variable size of data. 124 | StatEx data; //!< File stat data 125 | }; 126 | }; 127 | 128 | 129 | /** 130 | * Get a string representation of the message name given the op-code. 131 | * @param messageType Message op-code to convert to a string. 132 | * @return A string representation of a given message code. 133 | */ 134 | inline 135 | Solace::StringView 136 | messageTypeToString(Solace::byte type) noexcept { 137 | // Note: 9p2000u does not introduce new messages but extend existing 138 | return ::styxe::messageTypeToString(type); 139 | } 140 | 141 | } // end of namespace _9P2000U 142 | 143 | 144 | inline 145 | size_type protocolSize(_9P2000U::StatEx const& value) noexcept { 146 | return protocolSize(static_cast(value)) 147 | + protocolSize(value.extension) 148 | + protocolSize(value.n_uid) 149 | + protocolSize(value.n_gid) 150 | + protocolSize(value.n_muid); 151 | } 152 | 153 | 154 | /** Serialize Auth request message into a request writer stream. 155 | * @param writer Bytestream to write the message to. 156 | * @param message A message to serilalize. 157 | * @return Ref to writer for fluent interface. 158 | */ 159 | RequestWriter& operator<< (RequestWriter& writer, _9P2000U::Request::Auth const& message); 160 | 161 | /** Serialize Attach request message into a request writer stream. 162 | * @param writer Bytestream to write the message to. 163 | * @param message A message to serilalize. 164 | * @return Ref to writer for fluent interface. 165 | */ 166 | RequestWriter& operator<< (RequestWriter& writer, _9P2000U::Request::Attach const& message); 167 | 168 | /** Serialize Create request message into a request writer stream. 169 | * @param writer Bytestream to write the message to. 170 | * @param message A message to serilalize. 171 | * @return Ref to writer for fluent interface. 172 | */ 173 | RequestWriter& operator<< (RequestWriter& writer, _9P2000U::Request::Create const& message); 174 | 175 | /** Serialize WStat request message into a request writer stream. 176 | * @param writer Bytestream to write the message to. 177 | * @param message A message to serilalize. 178 | * @return Ref to writer for fluent interface. 179 | */ 180 | RequestWriter& operator<< (RequestWriter& writer, _9P2000U::Request::WStat const& message); 181 | 182 | /** Serialize Error response message into a request writer stream. 183 | * @param writer Bytestream to write the message to. 184 | * @param message A message to serilalize. 185 | * @return Ref to writer for fluent interface. 186 | */ 187 | ResponseWriter& operator<< (ResponseWriter& writer, _9P2000U::Response::Error const& message); 188 | 189 | /** Serialize Stat response message into a request writer stream. 190 | * @param writer Bytestream to write the message to. 191 | * @param message A message to serilalize. 192 | * @return Ref to writer for fluent interface. 193 | */ 194 | ResponseWriter& operator<< (ResponseWriter& writer, _9P2000U::Response::Stat const& message); 195 | 196 | 197 | Solace::Result 198 | operator>> (Solace::ByteReader& data, _9P2000U::Request::Auth& dest); 199 | Solace::Result 200 | operator>> (Solace::ByteReader& data, _9P2000U::Request::Attach& dest); 201 | Solace::Result 202 | operator>> (Solace::ByteReader& data, _9P2000U::Request::Create& dest); 203 | Solace::Result 204 | operator>> (Solace::ByteReader& data, _9P2000U::Request::WStat& dest); 205 | 206 | Solace::Result 207 | operator>> (Solace::ByteReader& data, _9P2000U::Response::Error& dest); 208 | Solace::Result 209 | operator>> (Solace::ByteReader& data, _9P2000U::Response::Stat& dest); 210 | 211 | 212 | template <> 213 | constexpr Solace::byte messageCodeOf<_9P2000U::Request::Auth>() noexcept { return asByte(MessageType::TAuth); } 214 | template <> 215 | constexpr Solace::byte messageCodeOf<_9P2000U::Request::Attach>() noexcept { return asByte(MessageType::TAttach); } 216 | template <> 217 | constexpr Solace::byte messageCodeOf<_9P2000U::Request::Create>() noexcept { return asByte(MessageType::TCreate); } 218 | template <> 219 | constexpr Solace::byte messageCodeOf<_9P2000U::Request::WStat>() noexcept { return asByte(MessageType::TWStat); } 220 | 221 | template <> 222 | constexpr Solace::byte messageCodeOf<_9P2000U::Response::Stat>() noexcept { return asByte(MessageType::RStat); } 223 | template <> 224 | constexpr Solace::byte messageCodeOf<_9P2000U::Response::Error>() noexcept { return asByte(MessageType::RError); } 225 | 226 | 227 | } // end of namespace styxe 228 | #endif // STYXE_9P2000U_HPP 229 | -------------------------------------------------------------------------------- /test/test_9P2000u.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /******************************************************************************* 17 | * libstyxe Unit Test Suit 18 | * @file: test/test_9P2000u.cpp 19 | * Specific test 9P2000.e 20 | *******************************************************************************/ 21 | #include "styxe/messageWriter.hpp" 22 | #include "styxe/messageParser.hpp" 23 | 24 | #include "testHarnes.hpp" 25 | #include 26 | 27 | using namespace Solace; 28 | using namespace styxe; 29 | 30 | 31 | auto const kTestedVestion = _9P2000U::kProtocolVersion; 32 | 33 | 34 | 35 | _9P2000U::StatEx randomStat() { 36 | std::random_device rd; 37 | std::default_random_engine randomGen{rd()}; 38 | 39 | 40 | _9P2000U::StatEx stat; 41 | stat.atime = randomGen(); 42 | stat.dev = randomGen(); 43 | stat.gid = "Other user"; 44 | stat.length = randomGen(); 45 | stat.mode = 111; 46 | stat.mtime = randomGen(); 47 | stat.name = "la-la McFile"; 48 | stat.qid.path = 61; 49 | stat.qid.type = 15; 50 | stat.qid.version = 404; 51 | stat.type = 1; 52 | stat.uid = "Userface McUse"; 53 | stat.n_uid = randomGen(); 54 | stat.n_gid = randomGen(); 55 | stat.n_muid = randomGen(); 56 | 57 | stat.size = DirListingWriter::sizeStat(stat); 58 | 59 | return stat; 60 | } 61 | 62 | 63 | namespace { 64 | 65 | struct P92000u_Responses : public TestHarnes { 66 | 67 | template 68 | styxe::Result 69 | getResponseOrFail() { 70 | ByteReader reader{_writer.viewWritten()}; 71 | 72 | auto maybeParser = createResponseParser(kTestedVestion, kMaxMessageSize); 73 | if (!maybeParser) { 74 | logFailure(maybeParser.getError()); 75 | return maybeParser.moveError(); 76 | } 77 | auto& parser = maybeParser.unwrap(); 78 | 79 | auto constexpr expectType = messageCodeOf(); 80 | return parseMessageHeader(reader) 81 | .then([](MessageHeader&& header) { 82 | return (header.type != expectType) 83 | ? styxe::Result{types::errTag, getCannedError(CannedError::UnsupportedMessageType)} 84 | : styxe::Result{types::okTag, std::move(header)}; 85 | }) 86 | .then([&parser, &reader](MessageHeader&& header) { 87 | return parser.parseResponse(header, reader); 88 | }) 89 | .then([](ResponseMessage&& msg) -> styxe::Result { 90 | bool const isType = std::holds_alternative(msg); 91 | 92 | if (!isType) { 93 | []() { FAIL() << "Parsed request is on unexpected type"; } (); 94 | return getCannedError(CannedError::UnsupportedMessageType); 95 | } 96 | 97 | return Ok(std::get(std::move(msg))); 98 | }) 99 | .mapError([this](Error&& e) { 100 | logFailure(e); 101 | 102 | return e; 103 | }); 104 | } 105 | 106 | protected: 107 | ResponseWriter _responseWriter{_writer}; 108 | }; 109 | 110 | 111 | 112 | struct P92000u_Requests : public TestHarnes { 113 | 114 | template 115 | styxe::Result 116 | getRequestOrFail() { 117 | ByteReader reader{_writer.viewWritten()}; 118 | 119 | auto maybeParser = createRequestParser(kTestedVestion, kMaxMessageSize); 120 | if (!maybeParser) { 121 | logFailure(maybeParser.getError()); 122 | return maybeParser.moveError(); 123 | } 124 | auto& parser = maybeParser.unwrap(); 125 | 126 | auto constexpr expectType = messageCodeOf(); 127 | return parseMessageHeader(reader) 128 | .then([](MessageHeader&& header) { 129 | return (header.type != expectType) 130 | ? styxe::Result{getCannedError(CannedError::UnsupportedMessageType)} 131 | : styxe::Result{types::okTag, std::move(header)}; 132 | }) 133 | .then([&parser, &reader](MessageHeader&& header) { 134 | return parser.parseRequest(header, reader); 135 | }) 136 | .then([](RequestMessage&& msg) -> styxe::Result { 137 | bool const isType = std::holds_alternative(msg); 138 | 139 | if (!isType) { 140 | []() { FAIL() << "Parsed request is on unexpected type"; } (); 141 | return getCannedError(CannedError::UnsupportedMessageType); 142 | } 143 | 144 | return Ok(std::get(std::move(msg))); 145 | }) 146 | .mapError([this](Error&& e) -> Error { 147 | logFailure(e); 148 | 149 | return e; 150 | }); 151 | } 152 | 153 | protected: 154 | 155 | RequestWriter _requestWriter{_writer}; 156 | }; 157 | 158 | } // namespace 159 | 160 | 161 | TEST_F(P92000u_Requests, createSessionAuth) { 162 | RequestWriter writer{_writer}; 163 | writer << _9P2000U::Request::Auth{312, "User mcUsers", "Somewhere near", 7762}; 164 | 165 | getRequestOrFail<_9P2000U::Request::Auth>() 166 | .then([](_9P2000U::Request::Auth&& request) { 167 | ASSERT_EQ(312U, request.afid); 168 | ASSERT_EQ("User mcUsers", request.uname); 169 | ASSERT_EQ("Somewhere near", request.aname); 170 | ASSERT_EQ(7762U, request.n_uname); 171 | }); 172 | } 173 | 174 | TEST_F(P92000u_Requests, createAttachRequest) { 175 | RequestWriter writer{_writer}; 176 | writer << _9P2000U::Request::Attach{3310, 1841, "McFace", "close to u", 6277}; 177 | 178 | getRequestOrFail<_9P2000U::Request::Attach>() 179 | .then([](_9P2000U::Request::Attach&& request) { 180 | ASSERT_EQ(3310U, request.fid); 181 | ASSERT_EQ(1841U, request.afid); 182 | ASSERT_EQ("McFace", request.uname); 183 | ASSERT_EQ("close to u", request.aname); 184 | ASSERT_EQ(6277U, request.n_uname); 185 | }); 186 | } 187 | 188 | 189 | TEST_F(P92000u_Requests, createCreateRequest) { 190 | RequestWriter writer{_writer}; 191 | writer << _9P2000U::Request::Create{1734, "mcFance", 11, OpenMode::EXEC, "Extra ext"}; 192 | 193 | getRequestOrFail<_9P2000U::Request::Create>() 194 | .then([](_9P2000U::Request::Create&& request) { 195 | ASSERT_EQ(1734U, request.fid); 196 | ASSERT_EQ("mcFance", request.name); 197 | ASSERT_EQ(11U, request.perm); 198 | ASSERT_EQ(OpenMode::EXEC, request.mode); 199 | ASSERT_EQ("Extra ext", request.extension); 200 | }); 201 | } 202 | 203 | 204 | TEST_F(P92000u_Requests, createWStatRequest) { 205 | _9P2000U::StatEx stat = randomStat(); 206 | 207 | RequestWriter writer{_writer}; 208 | writer << _9P2000U::Request::WStat{8193, stat}; 209 | 210 | getRequestOrFail<_9P2000U::Request::WStat>() 211 | .then([stat](_9P2000U::Request::WStat&& request) { 212 | ASSERT_EQ(8193U, request.fid); 213 | ASSERT_EQ(stat, request.stat); 214 | }); 215 | } 216 | 217 | 218 | TEST_F(P92000u_Responses, createErrorResponse) { 219 | auto const testError = StringLiteral{"Something went right :)"}; 220 | ResponseWriter writer{_writer, 3}; 221 | writer << _9P2000U::Response::Error{testError, 9912}; 222 | 223 | getResponseOrFail<_9P2000U::Response::Error>() 224 | .then([testError](_9P2000U::Response::Error&& response) { 225 | ASSERT_EQ(testError, response.ename); 226 | ASSERT_EQ(9912U, response.errcode); 227 | }); 228 | } 229 | 230 | TEST_F(P92000u_Responses, parseErrorResponse) { 231 | auto const expectedErrorMessage = StringLiteral{"All good!"}; 232 | 233 | styxe::Encoder encoder{_writer}; 234 | encoder << makeHeaderWithPayload(asByte(MessageType::RError), 1, styxe::protocolSize(expectedErrorMessage) + 4) 235 | << static_cast(expectedErrorMessage) 236 | << uint32{9913}; 237 | 238 | getResponseOrFail<_9P2000U::Response::Error>() 239 | .then([expectedErrorMessage](_9P2000U::Response::Error&& response) { 240 | EXPECT_EQ(expectedErrorMessage, response.ename); 241 | EXPECT_EQ(9913U, response.errcode); 242 | }); 243 | } 244 | 245 | 246 | TEST_F(P92000u_Responses, createStatResponse) { 247 | _9P2000U::StatEx stat = randomStat(); 248 | 249 | ResponseWriter writer{_writer, 1}; 250 | writer << _9P2000U::Response::Stat{stat.size, stat}; 251 | 252 | getResponseOrFail<_9P2000U::Response::Stat>() 253 | .then([stat](_9P2000U::Response::Stat&& response) { 254 | ASSERT_EQ(stat, response.data); 255 | }); 256 | } 257 | 258 | TEST_F(P92000u_Responses, parseStatResponse) { 259 | _9P2000U::Response::Stat statResponse; 260 | statResponse.data = randomStat(); 261 | statResponse.dummySize = protocolSize(statResponse.data); 262 | 263 | styxe::Encoder encoder{_writer}; 264 | encoder << makeHeaderWithPayload(asByte(MessageType::RStat), 1, 265 | sizeof(statResponse.dummySize) + protocolSize(statResponse.data)) 266 | << statResponse.dummySize 267 | << statResponse.data; 268 | 269 | getResponseOrFail<_9P2000U::Response::Stat>() 270 | .then([statResponse](_9P2000U::Response::Stat&& response) { 271 | ASSERT_EQ(statResponse.dummySize, response.dummySize); 272 | ASSERT_EQ(statResponse.data, response.data); 273 | }); 274 | } 275 | -------------------------------------------------------------------------------- /src/9p2000_writer.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | #include "styxe/9p2000.hpp" 18 | #include "styxe/encoder.hpp" 19 | 20 | #include "write_helper.hpp" 21 | 22 | 23 | using namespace Solace; 24 | using namespace styxe; 25 | 26 | 27 | ResponseWriter& 28 | styxe::operator<< (ResponseWriter& writer, Response::Version const& message) { 29 | return encode(writer, message, message.msize, message.version); 30 | } 31 | 32 | ResponseWriter& 33 | styxe::operator<< (ResponseWriter& writer, Response::Auth const& message) { 34 | return encode(writer, message, message.qid); 35 | } 36 | 37 | 38 | ResponseWriter& 39 | styxe::operator<< (ResponseWriter& writer, Response::Error const& message) { 40 | return encode(writer, message, message.ename); 41 | } 42 | 43 | ResponseWriter& 44 | styxe::operator<< (ResponseWriter& writer, Response::Flush const& message) { 45 | return encode(writer, message); 46 | } 47 | 48 | ResponseWriter& 49 | styxe::operator<< (ResponseWriter& writer, Response::Attach const& message) { 50 | return encode(writer, message, message.qid); 51 | } 52 | 53 | ResponseWriter& 54 | styxe::operator<< (ResponseWriter& writer, Response::Walk const& response) { 55 | auto& e = writer.messageTypeOf>(); 56 | e << response.nqids; 57 | 58 | for (decltype(response.nqids) i = 0; i < response.nqids; ++i) { 59 | e << response.qids[i]; 60 | } 61 | writer.updateMessageSize(); 62 | 63 | return writer; 64 | } 65 | 66 | ResponseWriter& 67 | styxe::operator<< (ResponseWriter& writer, Response::Open const& message) { 68 | return encode(writer, message, message.qid, message.iounit); 69 | } 70 | 71 | 72 | ResponseWriter& 73 | styxe::operator<< (ResponseWriter& writer, Response::Create const& message) { 74 | return encode(writer, message, message.qid, message.iounit); 75 | } 76 | 77 | 78 | ResponseWriter& 79 | styxe::operator<< (ResponseWriter& writer, Response::Read const& message) { 80 | return encode(writer, message, message.data); 81 | } 82 | 83 | 84 | ResponseWriter& 85 | styxe::operator<< (ResponseWriter& writer, Response::Write const& message) { 86 | return encode(writer, message, message.count); 87 | } 88 | 89 | 90 | ResponseWriter& 91 | styxe::operator<< (ResponseWriter& writer, Response::Clunk const& message) { 92 | return encode(writer, message); 93 | } 94 | 95 | 96 | ResponseWriter& 97 | styxe::operator<< (ResponseWriter& writer, Response::Remove const& message) { 98 | return encode(writer, message); 99 | } 100 | 101 | 102 | ResponseWriter& 103 | styxe::operator<< (ResponseWriter& writer, Response::Stat const& message) { 104 | return encode(writer, message, message.dummySize, message.data); 105 | } 106 | 107 | 108 | ResponseWriter& 109 | styxe::operator<< (ResponseWriter& writer, Response::WStat const& message) { 110 | return encode(writer, message); 111 | } 112 | 113 | 114 | 115 | RequestWriter& 116 | styxe::operator<< (RequestWriter& writer, Request::Version const& message) { 117 | return encode(writer, message, message.msize, message.version); 118 | } 119 | 120 | 121 | RequestWriter& 122 | styxe::operator<< (RequestWriter& writer, Request::Auth const& message) { 123 | return encode(writer, message, message.afid, message.uname, message.aname); 124 | } 125 | 126 | RequestWriter& 127 | styxe::operator<< (RequestWriter& writer, Request::Flush const& message) { 128 | return encode(writer, message, message.oldtag); 129 | } 130 | 131 | 132 | RequestWriter& 133 | styxe::operator<< (RequestWriter& writer, Request::Attach const& message) { 134 | return encode(writer, message, message.fid, message.afid, message.uname, message.aname); 135 | } 136 | 137 | 138 | RequestWriter& 139 | styxe::operator<< (RequestWriter& writer, Request::Walk const& message) { 140 | return encode(writer, message, message.fid, message.newfid, message.path); 141 | } 142 | 143 | 144 | RequestWriter& 145 | styxe::operator<< (RequestWriter& writer, Request::Open const& message) { 146 | return encode(writer, message, message.fid, message.mode.mode); 147 | } 148 | 149 | 150 | RequestWriter& 151 | styxe::operator<< (RequestWriter& writer, Request::Create const& message) { 152 | return encode(writer, message, message.fid, message.name, message.perm, message.mode.mode); 153 | } 154 | 155 | 156 | RequestWriter& 157 | styxe::operator<< (RequestWriter& writer, Request::Read const& message) { 158 | return encode(writer, message, message.fid, message.offset, message.count); 159 | } 160 | 161 | 162 | RequestWriter& 163 | styxe::operator<< (RequestWriter& writer, Request::Write const& message) { 164 | return encode(writer, message, message.fid, message.offset, message.data); 165 | } 166 | 167 | 168 | RequestWriter& 169 | styxe::operator<< (RequestWriter& writer, Request::Clunk const& message) { 170 | return encode(writer, message, message.fid); 171 | } 172 | 173 | 174 | RequestWriter& 175 | styxe::operator<< (RequestWriter& writer, Request::Remove const& message) { 176 | return encode(writer, message, message.fid); 177 | } 178 | 179 | 180 | RequestWriter& 181 | styxe::operator<< (RequestWriter& writer, Request::Stat const& message) { 182 | return encode(writer, message, message.fid); 183 | } 184 | 185 | 186 | RequestWriter& 187 | styxe::operator<< (RequestWriter& writer, Request::WStat const& message) { 188 | return encode(writer, message, message.fid, message.stat); 189 | } 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | PartialPathWriter 199 | styxe::operator<< (RequestWriter& writer, Request::Partial::Walk const& response) { 200 | writer.messageTypeOf() 201 | << response.fid 202 | << response.newfid; 203 | 204 | return PartialPathWriter{writer}; 205 | } 206 | 207 | PartialDataWriter 208 | styxe::operator<< (RequestWriter& writer, Request::Partial::Write const& message) { 209 | writer.messageTypeOf() 210 | << message.fid 211 | << message.offset; 212 | 213 | return PartialDataWriter{writer}; 214 | } 215 | 216 | 217 | PartialDataWriter 218 | styxe::operator<< (ResponseWriter& writer, Response::Partial::Read const&) { 219 | writer.messageTypeOf(); 220 | 221 | return PartialDataWriter{writer}; 222 | } 223 | 224 | 225 | PartialStringWriter 226 | styxe::operator<< (ResponseWriter& writer, Response::Partial::Error const&) { 227 | writer.messageTypeOf(); 228 | 229 | return PartialStringWriter{writer}; 230 | } 231 | 232 | 233 | 234 | size_type 235 | styxe::protocolSize(Qid const&) noexcept { 236 | static constexpr size_type kQidSize = sizeof(Qid::type) + sizeof(Qid::version) + sizeof(Qid::path); 237 | 238 | // Qid has a fixed size of 13 bytes, lets keep it that way 239 | static_assert(kQidSize == 13, "Incorrect Qid struct size"); 240 | 241 | return kQidSize; 242 | } 243 | 244 | 245 | size_type 246 | styxe::protocolSize(styxe::Stat const& stat) noexcept { 247 | return protocolSize(stat.size) + 248 | protocolSize(stat.type) + 249 | protocolSize(stat.dev) + 250 | protocolSize(stat.qid) + 251 | protocolSize(stat.mode) + 252 | protocolSize(stat.atime) + 253 | protocolSize(stat.mtime) + 254 | protocolSize(stat.length) + 255 | protocolSize(stat.name) + 256 | protocolSize(stat.uid) + 257 | protocolSize(stat.gid) + 258 | protocolSize(stat.muid); 259 | } 260 | 261 | 262 | 263 | 264 | DirListingWriter::DirListingWriter(ResponseWriter& writer, Solace::uint32 maxBytes, Solace::uint64 offset) noexcept 265 | : _offset{offset} 266 | , _maxBytes{maxBytes} 267 | , _writer{writer} 268 | { 269 | auto& encoder = _writer.messageType(asByte(MessageType::RRead)); 270 | _dataPosition = encoder.buffer().position(); 271 | encoder << MemoryView{}; // Prime writer with 0 size read response 272 | _writer.updateMessageSize(); 273 | } 274 | 275 | 276 | void DirListingWriter::updateDataSize() { 277 | auto& buffer = _writer.encoder().buffer(); 278 | 279 | auto const finalPos = buffer.position(); 280 | auto const dataSize = narrow_cast(finalPos - _dataPosition - protocolSize(size_type{})); 281 | buffer.position(_dataPosition); // Reset output stream to the start position 282 | _writer.encoder() << dataSize; 283 | buffer.position(finalPos); // Reset output stream to the final position 284 | _writer.updateMessageSize(); 285 | } 286 | 287 | 288 | bool DirListingWriter::encode(Stat const& stat) { 289 | auto const protoSize = ::protocolSize(stat); 290 | // Keep count of how many data we have traversed. 291 | _bytesTraversed += protoSize; 292 | if (_bytesTraversed <= _offset) { // Client is only interested in data pass the offset. 293 | return true; 294 | } 295 | 296 | // Keep track of much data will end up in a buffer to prevent overflow. 297 | _bytesEncoded += protoSize; 298 | if (_bytesEncoded > _maxBytes) { 299 | return false; 300 | } 301 | 302 | // Only encode the data if we have some room left, as specified by 'count' arg. 303 | _writer.encoder() << stat; 304 | 305 | updateDataSize(); 306 | return true; 307 | } 308 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # libstyxe [![C++ standard][c++-standard-shield]][c++-standard-link] [![License][license-shield]][license-link] 2 | --- 3 | [![TravisCI][travis-shield]][travis-link] 4 | [![Codecov][codecov-shield]][codecov-link] 5 | [![Coverity][coverity-shield]][coverity-link] 6 | [![Coverage Status][coveralls-shield]][coveralls-link] 7 | [![LGTM][LGTM-shield]][LGTM-link] 8 | 9 | 10 | [c++-standard-shield]: https://img.shields.io/badge/c%2B%2B-17%2F20-blue 11 | [c++-standard-link]: https://en.wikipedia.org/wiki/C%2B%2B#Standardization 12 | [license-shield]: https://img.shields.io/badge/License-Apache%202.0-blue.svg 13 | [license-link]: https://opensource.org/licenses/Apache-2.0 14 | [travis-shield]: https://travis-ci.org/abbyssoul/libstyxe.png?branch=master 15 | [travis-link]: https://travis-ci.org/abbyssoul/libstyxe 16 | [codecov-shield]: https://codecov.io/gh/abbyssoul/libstyxe/branch/master/graph/badge.svg 17 | [codecov-link]: https://codecov.io/gh/abbyssoul/libstyxe 18 | [coverity-shield]: https://scan.coverity.com/projects/18800/badge.svg 19 | [coverity-link]: https://scan.coverity.com/projects/abbyssoul-libstyxe 20 | [coveralls-shield]: https://coveralls.io/repos/github/abbyssoul/libstyxe/badge.svg?branch=master 21 | [coveralls-link]: https://coveralls.io/github/abbyssoul/libstyxe?branch=master 22 | [LGTM-shield]: https://img.shields.io/lgtm/grade/cpp/github/abbyssoul/libstyxe.svg 23 | [LGTM-link]: https://lgtm.com/projects/g/abbyssoul/libstyxe/alerts/ 24 | 25 | 26 | A _library_ for parsing 9P2000 protocol messages. 27 | > library: a collection of types, functions, classes, etc. implementing a set of facilities (abstractions) meant to be potentially used as part of more that one program. From [Cpp Code guidelines gloassay](http://isocpp.github.io/CppCoreGuidelines/CppCoreGuidelines#glossary) 28 | 29 | [9P](http://man.cat-v.org/plan_9/5/intro) is a distributed resource sharing protocol developed as part of the Plan 9 research operating system at AT&T Bell Laboratories (now a part of Lucent Technologies) by the Computer Science Research Center. It can be used to distributed file systems, devices, and application services. It was designed as an interface to both local and remote resources, making the transition from local to cluster to grid resources transparent. 30 | [From RFC 9P2000](https://ericvh.github.io/9p-rfc/rfc9p2000.u.html) 31 | 32 | This library contains C++17 implementation of 9P message parser and writer. It is not a complete 9P server as no IO capabilities is included. 33 | For an example implementation of a 9P server using ASIO please check hello world of 9p servers - serving json over 9P: [mjstyxfs](https://github.com/abbyssoul/mjstyxfs). 34 | For ASIO base IO and async event look please consider using [libapsio](https://github.com/abbyssoul/libapsio) library that take care of networking. 35 | 36 | 37 | Parser implementation is based on [9P documentation](http://man.cat-v.org/plan_9/5/intro). 38 | Also following extensions are supported 39 | - [9P2000.u](https://ericvh.github.io/9p-rfc/rfc9p2000.u.html) - Unix extension. 40 | - [9P2000.L](https://github.com/chaos/diod/blob/master/protocol.md) - Linux extension. 41 | - [9P2000.e](http://erlangonxen.org/more/9p2000e) extension. 42 | 43 | 44 | # Using this library 45 | 46 | ### To create 9P message: 47 | The library is using `Solace::ByteWriter` / `Solace::ByteReader` to read/write byte streams. 48 | Note that this adaptors do not allocate memory. So the user is responsible for creating a 49 | buffer of appropriate size to write the resulting message to. 50 | Note that the size of the target buffer should be no more then negotiated message size for the current session. 51 | 52 | 53 | ```C++ 54 | #include 55 | 56 | ... 57 | Solace::ByteWriter byteWriter{...}; 58 | 59 | // Write TVersion request message into the output buffer 60 | styxe::RequestWriter requestWriter{byteWriter, 1}; 61 | requestWriter << Request::Version{parser.maxMessageSize(), _9P2000U::kProtocolVersion} 62 | ... 63 | 64 | // Write TOpen request into the given destination buffer 65 | styxe::RequestWriter requestWriter{buffer, 1}; 66 | requestWriter << Open{42, styxe::OpenMode::READ)}; 67 | ``` 68 | 69 | ### Parsing 9P message from a byte buffer: 70 | Parsing of 9P protocol messages differ slightly depending on if you are implementing server - expecting request type messages - or a client - parsing server responses. 71 | 72 | ### Parsing requests (server side): 73 | ```C++ 74 | styxe::Parser parser{...}; 75 | ... 76 | Solace::ByteReader byteReader{...}; 77 | auto maybeHeader = parser.parseMessageHeader{byteReader}; 78 | if (!maybeHeader) { 79 | LOG() << "Failed to parse message header"; 80 | return maybeHeader.getError(); 81 | } 82 | 83 | auto maybeMessage = parser.parseRequest(*maybeHeader, byteReader); 84 | if (!maybeMessage) { 85 | LOG() << "Failed to parse message"; 86 | return maybeMessage.getError(); 87 | } 88 | 89 | handleRequest(*maybeMessage); 90 | ... 91 | ``` 92 | 93 | Alternatively you can prefer fluent interface: 94 | ```c++ 95 | styxe::Parser parser{...}; 96 | ... 97 | Solace::ByteReader byteReader{...}; 98 | parser.parseMessageHeader(byteReader) 99 | .then([&](styxe::MessageHeader header) { 100 | return parser.parseRequest(header, byteReader) 101 | .then(handleRequest); 102 | }) 103 | .orElse([](Error&& err) { 104 | std::cerr << "Error parsing request: " << err << std::endl; 105 | }); 106 | ``` 107 | 108 | ### Client side: parsing responses from a server 109 | ```c++ 110 | styxe::Parser parser{...}; 111 | ... 112 | parser.parseMessageHeader(byteReader) 113 | .then([&](styxe::MessageHeader header) { 114 | return parser.parseResponse(header, byteReader) 115 | .then(handleRequest); 116 | }) 117 | .orElse([](Error&& err) { 118 | std::cerr << "Error parsing response: " << err << std::endl; 119 | }); 120 | ``` 121 | 122 | See [examples](docs/examples.md) for other example usage of this library. 123 | 124 | # Using the library from your project. 125 | This library needs to be installed on your system in order to be used. There are a few ways this can be done: 126 | - You can install the pre-built version via [Conan](https://conan.io/) package manager. (Recommended) 127 | - You can build it from sources and install it locally. 128 | - You can install a pre-built version via your system package manager such as deb/apt if it is available in your system repository. 129 | 130 | ## Consuming library with Conan 131 | The library is available via [Conan](https://conan.io/) package manager. Add this to your project `conanfile.txt`: 132 | ``` 133 | [requires] 134 | libstyxe/0.6 135 | ``` 136 | 137 | Please check the latest available [binary version][conan-central-latest]. 138 | 139 | 140 | ## Dependencies 141 | This library depends on [libsolace](https://github.com/abbyssoul/libsolace) for low level data manipulation primitives 142 | such as ByteReader/ByteWriter and Result<> type. 143 | Since it is only a 9P protocol parser - there is dependency on the IO. It is library users responsibility to provide data stream. 144 | 145 | ### GTest 146 | Note test framework used is *gtest* and it is managed via git modules. 147 | Don't forget to do `git submodule update --init --recursive` on a new checkout to pull sub-module dependencies. 148 | 149 | 150 | 151 | # Building 152 | 153 | ### Build tool dependencies 154 | In order to build this project following tools must be present in the system: 155 | * git (to check out project and it’s external modules, see dependencies section) 156 | * cmake - user for build script generation 157 | * ninja (opional, used by default) 158 | * doxygen (opional, for documentation generation) 159 | * cppcheck (opional, but recommended for static code analysis, latest version from git is used as part of the 'codecheck' step) 160 | * cpplint (opional, for static code analysis in addition to cppcheck) 161 | * valgrind (opional, for runtime code quality verification) 162 | 163 | This project is using C++17 features extensively. The minimal tested/required version of gcc is gcc-7. 164 | [CI](https://travis-ci.org/abbyssoul/libstyxe) is using clang-6 and gcc-7. 165 | To install build tools on Debian based Linux distribution: 166 | ```shell 167 | sudo apt-get update -qq 168 | sudo apt-get install git doxygen python-pip valgrind ggcov 169 | sudo pip install cpplint 170 | ``` 171 | 172 | The library has one external dependency: [libsolace](https://github.com/abbyssoul/libsolace) which is managed via conan. 173 | Please make sure [conan is installed](https://docs.conan.io/en/latest/installation.html) on your system if you want to build this project. 174 | 175 | ## Building the project 176 | ```shell 177 | # In the project check-out directory: 178 | # To build debug version with sanitizer enabled (recommended for development) 179 | ./configure --enable-debug --enable-sanitizer 180 | 181 | # To build the library it self 182 | make 183 | 184 | # To build and run unit tests: 185 | make test 186 | 187 | # To run valgrind on test suit: 188 | # Note: `valgrind` doesn’t work with ./configure --enable-sanitize option 189 | make verify 190 | 191 | # To build API documentation using doxygen: 192 | make doc 193 | ``` 194 | 195 | To install locally for testing: 196 | ```shell 197 | make --prefix=/user/home//test/lib install 198 | ``` 199 | To install system wide (as root): 200 | ```shell 201 | make install 202 | ``` 203 | To run code quality check before submission of a patch: 204 | ```shell 205 | # Verify code quality before submission 206 | make codecheck 207 | ``` 208 | 209 | 210 | ## Contributing changes 211 | The framework is work in progress and contributions are very welcomed. 212 | Please see [`CONTRIBUTING.md`](CONTRIBUTING.md) for details on how to contribute to 213 | this project. 214 | 215 | Please note that in order to maintain code quality a set of static code analysis tools is used as part of the build process. 216 | Thus all contributions must be verified by this tools before PR can be accepted. 217 | 218 | 219 | ## License 220 | The library available under Apache License 2.0 221 | Please see [`LICENSE`](LICENSE) for details. 222 | 223 | 224 | ## Authors 225 | Please see [`AUTHORS`](AUTHORS) file for the list of contributors. 226 | -------------------------------------------------------------------------------- /include/styxe/messageWriter.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | #ifndef STYXE_MESSAGEWRITER_HPP 18 | #define STYXE_MESSAGEWRITER_HPP 19 | 20 | #include "encoder.hpp" 21 | #include "9p.hpp" 22 | 23 | 24 | namespace styxe { 25 | 26 | /** Encode a message header into the output stream. 27 | * @param encoder Encoder used to encode the value. 28 | * @param value Value to encode. 29 | * @return Ref to the encoder for fluency. 30 | */ 31 | inline 32 | Encoder& operator<< (Encoder& encoder, MessageHeader value) { 33 | return encoder << value.messageSize 34 | << value.type 35 | << value.tag; 36 | 37 | } 38 | 39 | 40 | /** 41 | * Base class for 9p message writers. 42 | */ 43 | struct MessageWriterBase { 44 | 45 | /** 46 | * @brief Construct a new ResponseWriter. 47 | * @param dest A byte writer stream where data to be written. 48 | * @param messageTag Tag of the message being created. 49 | */ 50 | constexpr MessageWriterBase(Solace::ByteWriter& dest, Tag messageTag) noexcept 51 | : _encoder{dest} 52 | , _pos{dest.position()} 53 | , _header{headerSize(), 0, messageTag} 54 | {} 55 | 56 | 57 | /// Update message header with the actual number of bytes written so far. 58 | void updateMessageSize(); 59 | 60 | 61 | /** Get underlying data encoder 62 | * @return Encoder 63 | */ 64 | constexpr Encoder& encoder() noexcept { return _encoder; } 65 | 66 | /** 67 | * Get formed message header 68 | * @return Copy of the message header. 69 | */ 70 | constexpr MessageHeader header() const noexcept { return _header; } 71 | 72 | /** 73 | * Set message type and write newly formed message header to the output stream. 74 | * @param typeCode Message type byte-code. @see MessageHeader::type 75 | * @return styxe::Encoder to write payload data to. 76 | */ 77 | Encoder& messageType(Solace::byte typeCode) { 78 | return messageType(typeCode, _header.tag); 79 | } 80 | 81 | template 82 | Encoder& messageTypeOf() { 83 | return messageType(messageCodeOf()); 84 | } 85 | 86 | /** 87 | * Set message type and write newly formed message header to the output stream. 88 | * @param type Message type byte-code. @see MessageHeader::type 89 | * @param tag Message tag. @see MessageHeader::tag 90 | * @return styxe::Encoder to write payload data to. 91 | */ 92 | Encoder& messageType(Solace::byte type, Tag tag) { 93 | _header.type = type; 94 | 95 | return _encoder << MessageHeader{_header.messageSize, _header.type, tag}; 96 | } 97 | 98 | private: 99 | /** Finalize the message build. 100 | * @return ByteWriter stream 101 | */ 102 | Solace::ByteWriter& build(); 103 | 104 | /// Data encoder used to write data out 105 | Encoder _encoder; 106 | 107 | /// Current position in the output stream where the message header starts 108 | Solace::ByteWriter::size_type _pos; 109 | 110 | /// Message header 111 | MessageHeader _header; 112 | }; 113 | 114 | /** 115 | * Helper type used to represent a message being built. 116 | */ 117 | template 118 | struct MessageWriter : public MessageWriterBase { 119 | 120 | /** 121 | * @brief Construct a new MessageWriter. 122 | * @param dest A byte writer stream where data to be written. 123 | * @param messageTag Tag of the message being created. 124 | */ 125 | constexpr MessageWriter(Solace::ByteWriter& dest, Tag messageTag = kNoTag) noexcept 126 | : MessageWriterBase{dest, messageTag} 127 | {} 128 | 129 | /** 130 | * IO manipulator helper function. Accept io manipulator in a function form and apply it. 131 | * @return Resulting reference to a writer after application of the manipulator passed in. 132 | */ 133 | MessageWriter& operator<< (MessageWriter& (*pf)(MessageWriter&)) { 134 | return pf(*this); 135 | } 136 | 137 | /** 138 | * IO manipulator helper function. Accept io manipulator in a function form and apply it. 139 | */ 140 | void operator<< (void (*pf)(MessageWriter&)) { 141 | pf(*this); 142 | } 143 | 144 | }; 145 | 146 | 147 | struct ResponseTag {}; 148 | struct RequestTag {}; 149 | 150 | using RequestWriter = MessageWriter; 151 | using ResponseWriter = MessageWriter; 152 | 153 | 154 | 155 | /// Message writer partial for messages that include trailing data segment.s 156 | struct PartialDataWriter { 157 | 158 | /** 159 | * Construct a new DataWriter. 160 | * @param writer A byte stream to write the resulting message to. 161 | */ 162 | explicit PartialDataWriter(MessageWriterBase& writer) noexcept 163 | : _writer{writer} // Store a writer object 164 | , _segmentsPos{writer.encoder().buffer().position()} // Save postion in the output stream 165 | { 166 | _writer.encoder() << Solace::MemoryView{}; // Write empty data segment (to be overwritten later) 167 | _writer.updateMessageSize(); 168 | } 169 | 170 | MessageWriterBase& update(size_type dataSize); 171 | 172 | /** 173 | * Write data field to the output writer. 174 | * @param value Data buffer to write 175 | * @return Ref to request original request writer. 176 | */ 177 | MessageWriterBase& data(Solace::MemoryView value); 178 | 179 | Solace::MutableMemoryView viewRemainder(); 180 | 181 | private: 182 | MessageWriterBase& _writer; 183 | Solace::ByteWriter::size_type const _segmentsPos; //!< A position in the output stream where path segments start. 184 | size_type _dataSize{0}; //!< Total size of data written so far 185 | }; 186 | 187 | 188 | 189 | inline 190 | PartialDataWriter&& operator<< (PartialDataWriter&& writer, Solace::MemoryView segment) { 191 | writer.data(segment); 192 | return Solace::mv(writer); 193 | } 194 | 195 | inline 196 | PartialDataWriter& operator<< (PartialDataWriter& writer, Solace::MemoryView segment) { 197 | writer.data(segment); 198 | return writer; 199 | } 200 | 201 | 202 | /** 203 | * Message writer specialization for messages that include repeated path segments. 204 | */ 205 | struct PartialPathWriter { 206 | /** 207 | * Construct a new PathWriter. 208 | * @param writer A byte stream to write the resulting message to. 209 | */ 210 | explicit PartialPathWriter(RequestWriter& writer) noexcept 211 | : _writer{writer} 212 | , _segmentsPos{writer.encoder().buffer().position()} 213 | { 214 | _writer.encoder() << _nSegments; 215 | _writer.updateMessageSize(); 216 | } 217 | 218 | /** 219 | * Get a reference to the underlying writer object 220 | * @return reference to the underlying writer object 221 | */ 222 | constexpr RequestWriter& writer() noexcept { return _writer; } 223 | 224 | /** 225 | * Write path segment of a path. 226 | * @param value A string representation of a path segment to be written. 227 | */ 228 | void segment(Solace::StringView value); 229 | 230 | protected: 231 | RequestWriter& _writer; //!< Ref to the underlying writer object the data written to. 232 | 233 | private: 234 | Solace::ByteWriter::size_type const _segmentsPos; //!< A position in the output stream where path segments start. 235 | WalkPath::size_type _nSegments{0}; //!< Number of path segments written 236 | }; 237 | 238 | 239 | PartialPathWriter&& operator<< (PartialPathWriter&& writer, Solace::StringView segment); 240 | 241 | /** 242 | * Message writer specialization to create partial messages that include repeated path segments followed by data. 243 | */ 244 | struct PathDataWriter final : public PartialPathWriter { 245 | /** 246 | * Construct a new PathDataWriter. 247 | * @param writer A byte stream to write the resulting message to. 248 | */ 249 | explicit PathDataWriter(RequestWriter& writer) noexcept 250 | : PartialPathWriter{writer} 251 | {} 252 | 253 | /** 254 | * Write data field to the output writer. 255 | * @param value Data buffer to write 256 | * @return Ref to request original request writer. 257 | */ 258 | RequestWriter& data(Solace::MemoryView value); 259 | }; 260 | 261 | 262 | inline 263 | PathDataWriter&& operator<< (PathDataWriter&& writer, Solace::StringView segment) { 264 | writer.segment(segment); 265 | return Solace::mv(writer); 266 | } 267 | 268 | inline 269 | PathDataWriter& operator<< (PathDataWriter& writer, Solace::StringView segment) { 270 | writer.segment(segment); 271 | return writer; 272 | } 273 | 274 | inline 275 | RequestWriter& operator<< (PathDataWriter&& writer, Solace::MemoryView segment) { 276 | return writer.data(segment); 277 | } 278 | 279 | 280 | 281 | /// Message writer partial for messages that include trailing string segments 282 | struct PartialStringWriter { 283 | 284 | /** 285 | * Construct a new DataWriter. 286 | * @param writer A byte stream to write the resulting message to. 287 | */ 288 | explicit PartialStringWriter(MessageWriterBase& writer) noexcept 289 | : _writer{writer} // Store a writer object 290 | , _segmentsPos{writer.encoder().buffer().position()} // Save postion in the output stream 291 | { 292 | _writer.encoder() << Solace::StringView{}; // Write empty data segment (to be overwritten later) 293 | _writer.updateMessageSize(); 294 | } 295 | 296 | /** 297 | * Write data field to the output writer. 298 | * @param value Data buffer to write 299 | * @return Ref to request original request writer. 300 | */ 301 | MessageWriterBase& string(Solace::StringView value); 302 | 303 | private: 304 | MessageWriterBase& _writer; 305 | Solace::ByteWriter::size_type const _segmentsPos; //!< A position in the output stream where path segments start. 306 | Solace::StringView::size_type _dataSize{0}; //!< A position in the output stream where path segments start. 307 | }; 308 | 309 | inline 310 | PartialStringWriter&& operator<< (PartialStringWriter&& writer, Solace::StringView segment) { 311 | writer.string(segment); 312 | return Solace::mv(writer); 313 | } 314 | 315 | inline 316 | PartialStringWriter& operator<< (PartialStringWriter& writer, Solace::StringView segment) { 317 | writer.string(segment); 318 | return writer; 319 | } 320 | 321 | 322 | } // end of namespace styxe 323 | #endif // STYXE_MESSAGEWRITER_HPP 324 | -------------------------------------------------------------------------------- /test/test_9P2000e.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | /******************************************************************************* 17 | * libstyxe Unit Test Suit 18 | * @file: test/test_9P2000e.cpp 19 | * Specific test 9P2000.e 20 | *******************************************************************************/ 21 | #include "styxe/messageWriter.hpp" 22 | #include "styxe/messageParser.hpp" 23 | 24 | #include "testHarnes.hpp" 25 | 26 | 27 | using namespace Solace; 28 | using namespace styxe; 29 | 30 | 31 | auto const kTestedVestion = _9P2000E::kProtocolVersion; 32 | 33 | namespace { 34 | 35 | struct P92000e_Responses : public TestHarnes { 36 | 37 | template 38 | styxe::Result 39 | getResponseOrFail() { 40 | ByteReader reader{_writer.viewWritten()}; 41 | 42 | auto maybeParser = createResponseParser(kTestedVestion, kMaxMessageSize); 43 | if (!maybeParser) { 44 | logFailure(maybeParser.getError()); 45 | return maybeParser.moveError(); 46 | } 47 | auto& parser = maybeParser.unwrap(); 48 | 49 | auto constexpr expectType = messageCodeOf(); 50 | return parseMessageHeader(reader) 51 | .then([](MessageHeader&& header) { 52 | return (header.type != expectType) 53 | ? styxe::Result{types::errTag, getCannedError(CannedError::UnsupportedMessageType)} 54 | : styxe::Result{types::okTag, std::move(header)}; 55 | }) 56 | .then([&parser, &reader](MessageHeader&& header) { 57 | return parser.parseResponse(header, reader); 58 | }) 59 | .then([](ResponseMessage&& msg) -> styxe::Result { 60 | bool const isType = std::holds_alternative(msg); 61 | 62 | if (!isType) { 63 | []() { FAIL() << "Parsed request is on unexpected type"; } (); 64 | return getCannedError(CannedError::UnsupportedMessageType); 65 | } 66 | 67 | return Ok(std::get(std::move(msg))); 68 | }) 69 | .mapError([this](Error&& e) { 70 | logFailure(e); 71 | 72 | return e; 73 | }); 74 | } 75 | 76 | protected: 77 | 78 | ResponseWriter _responseWriter{_writer}; 79 | }; 80 | 81 | 82 | 83 | struct P92000e_Requests : public TestHarnes { 84 | 85 | template 86 | styxe::Result 87 | getRequestOrFail() { 88 | ByteReader reader{_writer.viewWritten()}; 89 | 90 | auto maybeParser = createRequestParser(kTestedVestion, kMaxMessageSize); 91 | if (!maybeParser) { 92 | logFailure(maybeParser.getError()); 93 | return maybeParser.moveError(); 94 | } 95 | auto& parser = maybeParser.unwrap(); 96 | 97 | auto constexpr expectType = messageCodeOf(); 98 | return parseMessageHeader(reader) 99 | .then([](MessageHeader&& header) { 100 | return (header.type != expectType) 101 | ? styxe::Result{getCannedError(CannedError::UnsupportedMessageType)} 102 | : styxe::Result{types::okTag, std::move(header)}; 103 | }) 104 | .then([&parser, &reader](MessageHeader&& header) { 105 | return parser.parseRequest(header, reader); 106 | }) 107 | .then([](RequestMessage&& msg) -> styxe::Result { 108 | bool const isType = std::holds_alternative(msg); 109 | 110 | if (!isType) { 111 | []() { FAIL() << "Parsed request is on unexpected type"; } (); 112 | return getCannedError(CannedError::UnsupportedMessageType); 113 | } 114 | 115 | return Ok(std::get(std::move(msg))); 116 | }) 117 | .mapError([this](Error&& e) -> Error { 118 | logFailure(e); 119 | 120 | return e; 121 | }); 122 | } 123 | 124 | protected: 125 | 126 | RequestWriter _requestWriter{_writer}; 127 | }; 128 | 129 | } // namespace 130 | 131 | 132 | TEST_F(P92000e_Requests, createSessionRequest) { 133 | _requestWriter << _9P2000E::Request::Session{{8, 7, 6, 5, 4, 3, 2, 1}}; 134 | 135 | getRequestOrFail<_9P2000E::Request::Session>() 136 | .then([](_9P2000E::Request::Session&& request) { 137 | ASSERT_EQ(8, request.key[0]); 138 | ASSERT_EQ(4, request.key[4]); 139 | ASSERT_EQ(1, request.key[7]); 140 | }); 141 | } 142 | 143 | 144 | TEST_F(P92000e_Requests, parseSessionRequest_NotEnoughData) { 145 | byte const sessionKey[5] = {8, 7, 6, 5, 4}; 146 | auto keyData = wrapMemory(sessionKey); 147 | 148 | // Set declared message size to be more then negotiated message size 149 | _requestWriter.encoder() << makeHeaderWithPayload(messageCodeOf<_9P2000E::Response::Session>(), 1, keyData.size()); 150 | _writer.write(keyData); 151 | 152 | ByteReader reader{_writer.viewWritten()}; 153 | auto headerResult = parseMessageHeader(reader); 154 | ASSERT_TRUE(headerResult.isOk()); 155 | 156 | auto header = headerResult.unwrap(); 157 | ASSERT_EQ(messageCodeOf<_9P2000E::Response::Session>(), header.type); 158 | 159 | // Make sure we can parse the message back. 160 | auto message = parseVersionRequest(header, reader, kMaxMessageSize); 161 | ASSERT_TRUE(message.isError()); 162 | } 163 | 164 | 165 | TEST_F(P92000e_Responses, createSessionResponse) { 166 | _responseWriter << _9P2000E::Response::Session{}; 167 | 168 | getResponseOrFail<_9P2000E::Response::Session>(); 169 | } 170 | 171 | 172 | TEST_F(P92000e_Responses, parseSessionResponse) { 173 | // Set declared message size to be more then negotiated message size 174 | _responseWriter.encoder() << makeHeaderWithPayload(messageCodeOf<_9P2000E::Response::Session>(), 1, 0); 175 | 176 | getResponseOrFail<_9P2000E::Response::Session>(); 177 | } 178 | 179 | 180 | 181 | TEST_F(P92000e_Requests, createShortReadRequest) { 182 | byte buffer[15 + 2*3]; 183 | ByteWriter pathWriter{wrapMemory(buffer)}; 184 | styxe::Encoder encoder{pathWriter}; 185 | encoder << StringView{"some"} 186 | << StringView{"wierd"} 187 | << StringView{"place"}; 188 | 189 | _requestWriter << _9P2000E::Request::ShortRead{32, WalkPath{3, wrapMemory(buffer)}}; 190 | 191 | getRequestOrFail<_9P2000E::Request::ShortRead>() 192 | .then([](_9P2000E::Request::ShortRead&& request) { 193 | ASSERT_EQ(32U, request.fid); 194 | ASSERT_EQ(3U, request.path.size()); 195 | ASSERT_EQ("some", *request.path.begin()); 196 | }); 197 | } 198 | 199 | 200 | 201 | TEST_F(P92000e_Requests, createPartialShortReadRequest) { 202 | _requestWriter << _9P2000E::Request::Partial::ShortRead{32} 203 | << StringView{"some"} 204 | << StringView{"wierd"} 205 | << StringView{"place"}; 206 | 207 | getRequestOrFail<_9P2000E::Request::ShortRead>() 208 | .then([](_9P2000E::Request::ShortRead&& request) { 209 | ASSERT_EQ(32U, request.fid); 210 | ASSERT_EQ(3U, request.path.size()); 211 | ASSERT_EQ("some", *request.path.begin()); 212 | }); 213 | } 214 | 215 | 216 | TEST_F(P92000e_Responses, createShortReadResponse) { 217 | char const messageData[] = "This was somewhat important data d^_-b"; 218 | auto data = wrapMemory(messageData); 219 | _responseWriter << _9P2000E::Response::ShortRead{data}; 220 | 221 | getResponseOrFail<_9P2000E::Response::ShortRead>() 222 | .then([data](_9P2000E::Response::ShortRead&& response) { 223 | EXPECT_EQ(data, response.data); 224 | }); 225 | } 226 | 227 | 228 | TEST_F(P92000e_Responses, parseShortReadResponse) { 229 | auto const messageData = StringLiteral{"This is a very important data d-_^b"}; 230 | auto const dataView = messageData.view(); 231 | 232 | 233 | _responseWriter.encoder() << makeHeaderWithPayload(messageCodeOf<_9P2000E::Response::ShortRead>(), 234 | 1, 235 | narrow_cast(sizeof(size_type) + dataView.size())) 236 | << dataView; 237 | 238 | getResponseOrFail<_9P2000E::Response::ShortRead>() 239 | .then([messageData](_9P2000E::Response::ShortRead&& response) { 240 | EXPECT_EQ(messageData.size(), response.data.size()); 241 | EXPECT_EQ(messageData.view(), response.data); 242 | }); 243 | } 244 | 245 | 246 | TEST_F(P92000e_Requests, createShortWriteRequest) { 247 | char const messageData[] = "This is a very important data d-_^b"; 248 | auto data = wrapMemory(messageData); 249 | 250 | byte buffer[15 + 2*3]; 251 | ByteWriter pathWriter{wrapMemory(buffer)}; 252 | styxe::Encoder encoder{pathWriter}; 253 | encoder << StringView{"some"} 254 | << StringView{"wierd"} 255 | << StringView{"place"}; 256 | 257 | _requestWriter << _9P2000E::Request::ShortWrite{32, WalkPath{3, wrapMemory(buffer)}, data}; 258 | getRequestOrFail<_9P2000E::Request::ShortWrite>() 259 | .then([data](_9P2000E::Request::ShortWrite&& request) { 260 | ASSERT_EQ(32U, request.fid); 261 | ASSERT_EQ(data, request.data); 262 | ASSERT_EQ(3U, request.path.size()); 263 | ASSERT_EQ("some", *request.path.begin()); 264 | }); 265 | } 266 | 267 | 268 | TEST_F(P92000e_Requests, createPartialShortWriteRequest) { 269 | char const messageData[] = "This is a very important data d-_^b"; 270 | auto data = wrapMemory(messageData); 271 | 272 | _requestWriter << _9P2000E::Request::Partial::ShortWrite{32} 273 | << StringView{"some"} 274 | << StringView{"wierd"} 275 | << StringView{"place"} 276 | << data; 277 | 278 | 279 | getRequestOrFail<_9P2000E::Request::ShortWrite>() 280 | .then([data](_9P2000E::Request::ShortWrite&& request) { 281 | ASSERT_EQ(32U, request.fid); 282 | ASSERT_EQ(data, request.data); 283 | ASSERT_EQ(3U, request.path.size()); 284 | ASSERT_EQ("some", *request.path.begin()); 285 | }); 286 | } 287 | 288 | 289 | 290 | TEST_F(P92000e_Responses, createShortWriteResponse) { 291 | _responseWriter << _9P2000E::Response::ShortWrite{100500}; 292 | 293 | getResponseOrFail<_9P2000E::Response::ShortWrite>() 294 | .then([](_9P2000E::Response::ShortWrite&& response) { 295 | EXPECT_EQ(100500U, response.count); 296 | }); 297 | } 298 | 299 | 300 | TEST_F(P92000e_Responses, parseShortWriteResponse) { 301 | _responseWriter.encoder() << makeHeaderWithPayload(messageCodeOf<_9P2000E::Response::ShortWrite>(), 1, sizeof(uint32)) 302 | << (static_cast(81177)); 303 | 304 | getResponseOrFail<_9P2000E::Response::ShortWrite>() 305 | .then([](_9P2000E::Response::ShortWrite&& response) { 306 | EXPECT_EQ(81177U, response.count); 307 | }); 308 | } 309 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright {yyyy} {name of copyright owner} 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /include/styxe/messageParser.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2018 Ivan Ryabov 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | #pragma once 17 | #ifndef STYXE_MESSAGEPARSER_HPP 18 | #define STYXE_MESSAGEPARSER_HPP 19 | 20 | #include "styxe/9p2000.hpp" 21 | #include "styxe/9p2000e.hpp" 22 | #include "styxe/9p2000u.hpp" 23 | #include "styxe/9p2000L.hpp" 24 | 25 | 26 | namespace styxe { 27 | 28 | /// Type representing a request message 29 | using RequestMessage = std::variant< 30 | Request::Version, 31 | Request::Auth, 32 | Request::Flush, 33 | Request::Attach, 34 | Request::Walk, 35 | Request::Open, 36 | Request::Create, 37 | Request::Read, 38 | Request::Write, 39 | Request::Clunk, 40 | Request::Remove, 41 | Request::Stat, 42 | Request::WStat, 43 | // 9P2000.u extended messages 44 | _9P2000U::Request::Auth, 45 | _9P2000U::Request::Attach, 46 | _9P2000U::Request::Create, 47 | _9P2000U::Request::WStat, 48 | // 9p2000.e exrta messages 49 | _9P2000E::Request::Session, 50 | _9P2000E::Request::ShortRead, 51 | _9P2000E::Request::ShortWrite, 52 | // 9P2000.L extra messages 53 | _9P2000L::Request::StatFS, 54 | _9P2000L::Request::LOpen, 55 | _9P2000L::Request::LCreate, 56 | _9P2000L::Request::Symlink, 57 | _9P2000L::Request::MkNode, 58 | _9P2000L::Request::Rename, 59 | _9P2000L::Request::ReadLink, 60 | _9P2000L::Request::GetAttr, 61 | _9P2000L::Request::SetAttr, 62 | _9P2000L::Request::XAttrWalk, 63 | _9P2000L::Request::XAttrCreate, 64 | _9P2000L::Request::ReadDir, 65 | _9P2000L::Request::FSync, 66 | _9P2000L::Request::Lock, 67 | _9P2000L::Request::GetLock, 68 | _9P2000L::Request::Link, 69 | _9P2000L::Request::MkDir, 70 | _9P2000L::Request::RenameAt, 71 | _9P2000L::Request::UnlinkAt 72 | >; 73 | 74 | /// Type representing a response message 75 | using ResponseMessage = std::variant< 76 | Response::Version, 77 | Response::Auth, 78 | Response::Attach, 79 | Response::Error, 80 | Response::Flush, 81 | Response::Walk, 82 | Response::Open, 83 | Response::Create, 84 | Response::Read, 85 | Response::Write, 86 | Response::Clunk, 87 | Response::Remove, 88 | Response::Stat, 89 | Response::WStat, 90 | // 9P2000.u extended messages 91 | _9P2000U::Response::Error, 92 | _9P2000U::Response::Stat, 93 | // 9p2000.e exrta messages 94 | _9P2000E::Response::Session, 95 | _9P2000E::Response::ShortRead, 96 | _9P2000E::Response::ShortWrite, 97 | // 9P2000.L extra messages 98 | _9P2000L::Response::LError, 99 | _9P2000L::Response::StatFS, 100 | _9P2000L::Response::LOpen, 101 | _9P2000L::Response::LCreate, 102 | _9P2000L::Response::Symlink, 103 | _9P2000L::Response::MkNode, 104 | _9P2000L::Response::Rename, 105 | _9P2000L::Response::ReadLink, 106 | _9P2000L::Response::GetAttr, 107 | _9P2000L::Response::SetAttr, 108 | _9P2000L::Response::XAttrWalk, 109 | _9P2000L::Response::XAttrCreate, 110 | _9P2000L::Response::ReadDir, 111 | _9P2000L::Response::FSync, 112 | _9P2000L::Response::Lock, 113 | _9P2000L::Response::GetLock, 114 | _9P2000L::Response::Link, 115 | _9P2000L::Response::MkDir, 116 | _9P2000L::Response::RenameAt, 117 | _9P2000L::Response::UnlinkAt 118 | >; 119 | 120 | using RequestParseFunc = Solace::Result (*)(Solace::ByteReader& ); 121 | using ResponseParseFunc = Solace::Result (*)(Solace::ByteReader& ); 122 | 123 | using RequestParseTable = std::array; 124 | using ResponseParseTable = std::array; 125 | 126 | /// Type alias for message code -> StringView mapping. 127 | using VersionedNameMapper = Solace::StringView (*)(Solace::byte) noexcept; 128 | 129 | 130 | /** 131 | * Check that message heared is valid for given parser configuration 132 | * @param header Header to check 133 | * @param dataAvailible number of bytes avaliable in the message buffer. 134 | * @param maxMessageSize Maximun message size 135 | * @return Error if header is not valid or none. 136 | */ 137 | Result 138 | validateHeader(MessageHeader header, Solace::ByteReader::size_type dataAvailible, size_type maxMessageSize) noexcept; 139 | 140 | 141 | /** 142 | * Parse 9P message header from a byte buffer. 143 | * @param byteStream Byte buffer to read message header from. 144 | * @return Resulting message header if parsed successfully or an error otherwise. 145 | */ 146 | Result 147 | parseMessageHeader(Solace::ByteReader& byteStream); 148 | 149 | /** 150 | * Parse a version request message from a byte stream. 151 | * @param header Fixed-size message header. 152 | * @param byteStream A byte stream to parse a request from. 153 | * @return Either a parsed request message or an error. 154 | */ 155 | Result 156 | parseVersionRequest(MessageHeader header, Solace::ByteReader& byteStream, size_type maxMessageSize); 157 | 158 | 159 | /** 160 | * Base class for 9p message parsers. 161 | * This class incapsulate common parts between request and response parser. 162 | */ 163 | struct ParserBase { 164 | 165 | /** 166 | * Construct a new ParserBase 167 | * @param payloadSize Maximum negoriated payload size in bytes. Note: message size is payload + header.. 168 | * @param nameMapper A function to convert message code to strings. It depends on selected protocol version. 169 | */ 170 | constexpr ParserBase(size_type payloadSize, VersionedNameMapper nameMapper) noexcept 171 | : _maxPayloadSize{payloadSize} 172 | , _nameMapper{nameMapper} 173 | {} 174 | 175 | /** 176 | * Get a string representation of the message name given the op-code. 177 | * @param messageType Message op-code to convert to a string. 178 | * @return A string representation of a given message code. 179 | */ 180 | Solace::StringView messageName(Solace::byte messageType) const noexcept; 181 | 182 | /** 183 | * Get maximum message size in bytes. That includes size of a message header and the payload. 184 | * @return Negotiated maximum size of a message in bytes. 185 | */ 186 | size_type maxMessageSize() const noexcept { return headerSize() + _maxPayloadSize; } 187 | 188 | private: 189 | size_type _maxPayloadSize; /// Initial value of the maximum message payload size in bytes. 190 | VersionedNameMapper _nameMapper; 191 | 192 | }; 193 | 194 | 195 | /** 196 | * An implementation of 9p response message parser. 197 | * 198 | * The protocol is state-full as version, supported extentions and messages size are negotiated. 199 | * Thus this info must be preserved during communication. Instance of this class serves this purpose as well as 200 | * helps with message parsing. 201 | * 202 | * @note The implementation of the protocol does not allocate memory for any operation. 203 | * Message parser acts on an instance of the user provided Solace::ByteReader and any message data such as 204 | * name string or data read from a file is actually a pointer to the underlying ReadBuffer storage. 205 | * Thus it is user's responsibility to manage lifetime of that buffer. 206 | * (That is not technically correct as current implementation does allocate memory when dealing with Solace::Path 207 | * objects as there is no currently availiable PathView version) 208 | * 209 | * In order to create 9P2000 messages please @see MessageWriter. 210 | */ 211 | struct ResponseParser final : 212 | public ParserBase { 213 | 214 | /** 215 | * Construct a new instance of the parser. 216 | * @param maxPayloadSize Maximum message paylaod size in bytes. 217 | * @param nameMapper A pointer to a map-function to convert message op-codes to message name string. 218 | * @param parserTable A table of version specific opcode parser methods. 219 | */ 220 | ResponseParser(size_type maxPayloadSize, VersionedNameMapper nameMapper, ResponseParseTable parserTable) noexcept 221 | : ParserBase{maxPayloadSize, nameMapper} 222 | , _versionedResponseParser{Solace::mv(parserTable)} 223 | {} 224 | 225 | /** 226 | * Parse 9P Response type message from a byte buffer. 227 | * This is the primiry method used by a client to parse response from the server. 228 | * 229 | * @param header Message header. 230 | * @param data Byte buffer to read message content from. 231 | * @return Resulting message if parsed successfully or an error otherwise. 232 | */ 233 | Result 234 | parseResponse(MessageHeader header, Solace::ByteReader& data) const; 235 | 236 | private: 237 | ResponseParseTable _versionedResponseParser; /// Parser V-table. 238 | }; 239 | 240 | 241 | 242 | /** 243 | * An implementation of 9p request message parser. 244 | * 245 | * The protocol is state-full as version, supported extentions and messages size are negotiated. 246 | * Thus this info must be preserved during communication. Instance of this class serves this purpose as well as 247 | * helps with message parsing. 248 | * 249 | * @note The implementation of the protocol does not allocate memory for any operation. 250 | * Message parser acts on an instance of the user provided Solace::ByteReader and any message data such as 251 | * name string or data read from a file is actually a pointer to the underlying ReadBuffer storage. 252 | * Thus it is user's responsibility to manage lifetime of that buffer. 253 | * (That is not technically correct as current implementation does allocate memory when dealing with Solace::Path 254 | * objects as there is no currently availiable PathView version) 255 | * 256 | * In order to create 9P2000 messages please @see RequestWriter. 257 | */ 258 | struct RequestParser final : 259 | public ParserBase { 260 | 261 | /** 262 | * Construct a new instance of the parser. 263 | * @param maxPayloadSize Maximum message paylaod size in bytes. 264 | * @param nameMapper A pointer to a map-function to convert message op-codes to message name string. 265 | * @param parserTable A table of version specific opcode parser methods. 266 | */ 267 | RequestParser(size_type maxPayloadSize, VersionedNameMapper nameMapper, RequestParseTable parserTable) noexcept 268 | : ParserBase{maxPayloadSize, nameMapper} 269 | , _versionedRequestParser{Solace::mv(parserTable)} 270 | {} 271 | 272 | /** 273 | * Parse 9P Request type message from a byte buffer. 274 | * This is the primiry method used by a server implementation to parse requests from a client. 275 | 276 | * @param header Message header. 277 | * @param data Byte buffer to read message content from. 278 | * @return Resulting message if parsed successfully or an error otherwise. 279 | */ 280 | Result 281 | parseRequest(MessageHeader header, Solace::ByteReader& data) const; 282 | 283 | private: 284 | RequestParseTable _versionedRequestParser; /// Parser 'V-table'. 285 | }; 286 | 287 | 288 | 289 | /** 290 | * Create a new parser for a given protocol version and max message size. 291 | * 292 | * @param version Version of the protocol to use. 293 | * @param maxPayloadSize Maximum size of a message in bytes as negotioated with Request::Version 294 | * @return Either a valid parser or an error if invalid version of maxMessageSize are requested. 295 | */ 296 | Result 297 | createResponseParser(Solace::StringView version, size_type maxPayloadSize) noexcept; 298 | 299 | 300 | /** 301 | * Create a new request message parser for a given protocol version. 302 | * 303 | * @param version Version of the protocol to use. 304 | * @param maxPayloadSize Maximum size of a message in bytes as negotioated with Request::Version 305 | * @return Either a valid parser or an error if invalid version of maxMessageSize are requested. 306 | */ 307 | Result 308 | createRequestParser(Solace::StringView version, size_type maxPayloadSize) noexcept; 309 | 310 | 311 | // Type constraints 312 | static_assert(std::is_move_assignable_v, "ParserBase should be movable"); 313 | static_assert(std::is_move_assignable_v, "ResponseParser should be movable"); 314 | static_assert(std::is_move_assignable_v, "RequestParser should be movable"); 315 | 316 | static_assert(std::is_move_assignable_v, "RequestMessage should be movable"); 317 | static_assert(std::is_move_assignable_v, "ResponseMessage should be movable"); 318 | 319 | 320 | } // end of namespace styxe 321 | #endif // STYXE_MESSAGEPARSER_HPP 322 | --------------------------------------------------------------------------------