├── tmp └── .keep ├── contrib └── ruby │ ├── ext │ └── trilogy-ruby │ │ ├── inc │ │ ├── src │ │ ├── extconf.rb │ │ └── trilogy-ruby.h │ ├── lib │ ├── trilogy │ │ ├── version.rb │ │ ├── result.rb │ │ ├── encoding.rb │ │ └── error.rb │ └── trilogy.rb │ ├── .gitignore │ ├── script │ ├── build │ ├── test │ ├── bootstrap │ ├── cibuild │ └── benchmark │ ├── Gemfile │ ├── bin │ └── console │ ├── Gemfile.lock │ ├── Rakefile │ ├── trilogy.gemspec │ ├── LICENSE │ ├── README.md │ └── test │ ├── auth_test.rb │ └── test_helper.rb ├── .dockerignore ├── script ├── clang-format ├── test └── cibuild ├── test ├── mysql │ ├── docker-entrypoint-initdb.d │ │ ├── x509_user.sql │ │ ├── native_password_user.sql │ │ ├── caching_sha2_password_user.sql │ │ ├── cleartext_user.sql │ │ └── generate_keys.sh │ ├── Dockerfile │ └── conf.d │ │ ├── 8.0 │ │ └── build.cnf │ │ └── 8.4 │ │ └── build.cnf ├── helpers.c ├── test.h ├── protocol │ ├── building │ │ ├── ping_packet_test.c │ │ ├── quit_packet_test.c │ │ ├── stmt_close_packet_test.c │ │ ├── stmt_reset_packet_test.c │ │ ├── set_option_packet_test.c │ │ ├── change_db_packet_test.c │ │ ├── stmt_prepare_packet_test.c │ │ ├── query_packet_test.c │ │ ├── stmt_bind_data_packet_test.c │ │ └── stmt_execute_packet_test.c │ └── parsing │ │ ├── result_packet_test.c │ │ ├── eof_packet_test.c │ │ ├── ok_packet_test.c │ │ ├── row_packet_test.c │ │ ├── column_packet_test.c │ │ ├── error_packet_test.c │ │ └── handshake_test.c ├── error_test.c ├── client │ ├── escape_test.c │ ├── connect_test.c │ ├── ping_test.c │ ├── set_option_test.c │ ├── change_db_test.c │ ├── stmt_prepare_test.c │ ├── stmt_close_test.c │ ├── auth_test.c │ └── stmt_reset_test.c ├── socket_test.c ├── buffer_test.c ├── fuzz.c ├── packet_parser_test.c └── runner.c ├── .github ├── dependabot.yml └── workflows │ ├── ci.yml │ └── macos.yml ├── inc ├── trilogy.h └── trilogy │ ├── packet_parser.h │ ├── vendor │ ├── curl_hostcheck.h │ └── openssl_hostname_validation.h │ ├── buffer.h │ ├── error.h │ ├── socket.h │ └── builder.h ├── .clang-format ├── SECURITY.md ├── .gitignore ├── src ├── error.c ├── buffer.c ├── packet_parser.c ├── reader.c └── vendor │ ├── openssl_hostname_validation.c │ └── curl_hostcheck.c ├── LICENSE ├── Dockerfile ├── docker-compose.yml ├── Makefile ├── CONTRIBUTING.md ├── README.md ├── CODE_OF_CONDUCT.md ├── CHANGELOG.md └── example └── trilogy_query.c /tmp/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /contrib/ruby/ext/trilogy-ruby/inc: -------------------------------------------------------------------------------- 1 | ../../../../inc -------------------------------------------------------------------------------- /contrib/ruby/ext/trilogy-ruby/src: -------------------------------------------------------------------------------- 1 | ../../../../src -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .gitignore 3 | .env* 4 | Dockerfile 5 | -------------------------------------------------------------------------------- /script/clang-format: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | clang-format -style=file -i **/*.c **/*.h -------------------------------------------------------------------------------- /contrib/ruby/lib/trilogy/version.rb: -------------------------------------------------------------------------------- 1 | class Trilogy 2 | VERSION = "2.9.0" 3 | end 4 | -------------------------------------------------------------------------------- /contrib/ruby/.gitignore: -------------------------------------------------------------------------------- 1 | *.bundle 2 | *.so 3 | /ext/trilogy-ruby/Makefile 4 | /tmp 5 | /vendor/gems 6 | /pkg 7 | -------------------------------------------------------------------------------- /contrib/ruby/script/build: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | cd "$(dirname "$0")/.." 4 | 5 | bundle exec rake clean compile 6 | -------------------------------------------------------------------------------- /contrib/ruby/script/test: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | cd "$(dirname "$0")/.." 4 | 5 | # run entire test suite 6 | bundle exec rake test 7 | -------------------------------------------------------------------------------- /test/mysql/docker-entrypoint-initdb.d/x509_user.sql: -------------------------------------------------------------------------------- 1 | CREATE USER 'x509'@'%' REQUIRE X509; 2 | GRANT ALL PRIVILEGES ON test.* TO 'x509'@'%'; 3 | -------------------------------------------------------------------------------- /contrib/ruby/Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gemspec 4 | 5 | group :benchmark do 6 | gem "mysql2" 7 | gem "benchmark-ips" 8 | end 9 | -------------------------------------------------------------------------------- /contrib/ruby/bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "trilogy" 5 | 6 | require "irb" 7 | IRB.start(__FILE__) 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: "/" 6 | schedule: 7 | interval: daily 8 | -------------------------------------------------------------------------------- /inc/trilogy.h: -------------------------------------------------------------------------------- 1 | #ifndef TRILOGY_H 2 | #define TRILOGY_H 3 | 4 | #include "trilogy/blocking.h" 5 | #include "trilogy/client.h" 6 | #include "trilogy/error.h" 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /.clang-format: -------------------------------------------------------------------------------- 1 | BasedOnStyle: LLVM 2 | IndentWidth: 4 3 | UseTab: Never 4 | BreakBeforeBraces: Linux 5 | AllowShortIfStatementsOnASingleLine: false 6 | IndentCaseLabels: false 7 | ColumnLimit: 120 8 | 9 | -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | If you discover a security issue in this repo, please submit it through the [GitHub Security Bug Bounty](https://hackerone.com/github) 2 | 3 | Thanks for helping make Trilogy safe for everyone 4 | -------------------------------------------------------------------------------- /test/mysql/docker-entrypoint-initdb.d/native_password_user.sql: -------------------------------------------------------------------------------- 1 | CREATE USER 'native'@'%'; 2 | GRANT ALL PRIVILEGES ON test.* TO 'native'@'%'; 3 | ALTER USER 'native'@'%' IDENTIFIED WITH mysql_native_password BY 'password'; 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.dSYM 2 | *.o 3 | /libtrilogy.a 4 | /trilogy 5 | /test/test 6 | /example/trilogy_query 7 | /contrib/ruby/trilogy-*.gem 8 | 9 | # Ignore everything in the tmp directory except for the keep file. 10 | /tmp/**/* 11 | !/tmp/.keep 12 | -------------------------------------------------------------------------------- /test/mysql/docker-entrypoint-initdb.d/caching_sha2_password_user.sql: -------------------------------------------------------------------------------- 1 | CREATE USER 'caching_sha2'@'%'; 2 | GRANT ALL PRIVILEGES ON test.* TO 'caching_sha2'@'%'; 3 | ALTER USER 'caching_sha2'@'%' IDENTIFIED /*!80000 WITH caching_sha2_password */ BY 'password'; 4 | -------------------------------------------------------------------------------- /contrib/ruby/script/bootstrap: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # Bootstrap the application's environment. 3 | set -e 4 | cd "$(dirname $0)"/.. 5 | 6 | bundle config set --local path vendor/gems 7 | bundle check 2>&1 > /dev/null || { 8 | bundle install "$@" 9 | bundle clean 10 | } 11 | 12 | script/build 13 | -------------------------------------------------------------------------------- /test/mysql/docker-entrypoint-initdb.d/cleartext_user.sql: -------------------------------------------------------------------------------- 1 | INSTALL PLUGIN cleartext_plugin_server SONAME 'auth_test_plugin.so'; 2 | CREATE USER 'cleartext_user'@'%'; 3 | GRANT ALL PRIVILEGES ON test.* TO 'cleartext_user'@'%'; 4 | ALTER USER 'cleartext_user'@'%' IDENTIFIED WITH cleartext_plugin_server BY 'password'; 5 | -------------------------------------------------------------------------------- /contrib/ruby/script/cibuild: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | set -e 3 | cd "$(dirname "$0")/.." 4 | 5 | if [ -d /usr/share/rbenv/shims ]; then 6 | export PATH=/usr/share/rbenv/shims:$PATH 7 | export RBENV_VERSION="2.6.5" 8 | fi 9 | 10 | ruby -v 11 | bundle -v 12 | 13 | script/bootstrap 14 | 15 | MYSQL_SOCK=$(mysql_config --socket) script/test 16 | -------------------------------------------------------------------------------- /test/helpers.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "test.h" 8 | 9 | int wait_readable(trilogy_conn_t *conn) { return trilogy_sock_wait_read(conn->socket); } 10 | 11 | int wait_writable(trilogy_conn_t *conn) { return trilogy_sock_wait_write(conn->socket); } 12 | 13 | void close_socket(trilogy_conn_t *conn) { close(trilogy_sock_fd(conn->socket)); } 14 | -------------------------------------------------------------------------------- /src/error.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "trilogy/error.h" 4 | 5 | const char *trilogy_error(int error) 6 | { 7 | switch (error) { 8 | #define XX(name, code) \ 9 | case code: \ 10 | return #name; 11 | TRILOGY_ERROR_CODES(XX) 12 | #undef XX 13 | 14 | default: 15 | return NULL; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /contrib/ruby/Gemfile.lock: -------------------------------------------------------------------------------- 1 | PATH 2 | remote: . 3 | specs: 4 | trilogy (2.9.0) 5 | bigdecimal 6 | 7 | GEM 8 | remote: https://rubygems.org/ 9 | specs: 10 | benchmark-ips (2.7.2) 11 | bigdecimal (3.1.8) 12 | minitest (5.25.4) 13 | mysql2 (0.5.6) 14 | rake (13.3.1) 15 | rake-compiler (1.0.7) 16 | rake 17 | 18 | PLATFORMS 19 | ruby 20 | 21 | DEPENDENCIES 22 | benchmark-ips 23 | minitest (~> 5.5) 24 | mysql2 25 | rake-compiler (~> 1.0) 26 | trilogy! 27 | 28 | BUNDLED WITH 29 | 2.5.23 30 | -------------------------------------------------------------------------------- /test/test.h: -------------------------------------------------------------------------------- 1 | #ifndef TEST_H 2 | #define TEST_H 3 | 4 | #include "greatest.h" 5 | #include "trilogy.h" 6 | #include 7 | #include 8 | 9 | #define ASSERT_ERR(EXP, GOT) ASSERT_ENUM_EQ((EXP), (GOT), trilogy_error) 10 | #define ASSERT_OK(GOT) ASSERT_ERR(TRILOGY_OK, (GOT)) 11 | #define ASSERT_EOF(GOT) ASSERT_ERR(TRILOGY_EOF, (GOT)) 12 | 13 | /* Helpers */ 14 | 15 | const trilogy_sockopt_t *get_connopt(); 16 | int wait_readable(trilogy_conn_t *conn); 17 | int wait_writable(trilogy_conn_t *conn); 18 | void close_socket(trilogy_conn_t *conn); 19 | 20 | #endif 21 | -------------------------------------------------------------------------------- /contrib/ruby/lib/trilogy/result.rb: -------------------------------------------------------------------------------- 1 | class Trilogy 2 | class Result 3 | attr_reader :fields, :rows, :query_time, :affected_rows, :last_insert_id 4 | 5 | def count 6 | rows.count 7 | end 8 | 9 | alias_method :size, :count 10 | 11 | def each_hash 12 | return enum_for(:each_hash) unless block_given? 13 | 14 | rows.each do |row| 15 | this_row = {} 16 | 17 | idx = 0 18 | row.each do |col| 19 | this_row[fields[idx]] = col 20 | idx += 1 21 | end 22 | 23 | yield this_row 24 | end 25 | 26 | self 27 | end 28 | 29 | def each(&bk) 30 | rows.each(&bk) 31 | end 32 | 33 | include Enumerable 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /contrib/ruby/ext/trilogy-ruby/extconf.rb: -------------------------------------------------------------------------------- 1 | require "mkmf" 2 | 3 | # concatenate trilogy library sources to allow the compiler to optimise across 4 | # source files 5 | 6 | trilogy_src_dir = File.realpath("src", __dir__) 7 | File.binwrite("trilogy.c", 8 | Dir["#{trilogy_src_dir}/**/*.c"].map { |src| 9 | %{#line 1 "#{src}"\n} + File.binread(src) 10 | }.join) 11 | 12 | $objs = %w[trilogy.o cast.o cext.o] 13 | append_cflags(["-I #{__dir__}/inc", "-std=gnu99", "-fvisibility=hidden"]) 14 | 15 | dir_config("openssl") 16 | 17 | have_library("crypto", "CRYPTO_malloc") 18 | have_library("ssl", "SSL_new") 19 | have_func("rb_interned_str", "ruby.h") 20 | have_func("rb_ractor_local_storage_value_newkey", "ruby.h") 21 | create_makefile "trilogy/cext" 22 | -------------------------------------------------------------------------------- /test/mysql/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG MYSQL_VERSION=8.0 2 | FROM mysql:8.0-debian 3 | 4 | # Make all apt-get commands non-interactive. Setting this as an ARG will apply to the entire 5 | # build phase, but not leak into the final image and run phase. 6 | ARG DEBIAN_FRONTEND=noninteractive 7 | 8 | # Install the MySQL test package so that we can get test plugins. 9 | # https://github.com/docker-library/mysql/issues/1040 10 | RUN set -eux \ 11 | && apt-get update \ 12 | && apt-get install --yes mysql-community-test 13 | 14 | # This is the final stage in which we copy the plugins from the test stage. Doing it this way allows 15 | # us to not have to install the test package in the final image since we only need the plugins. 16 | FROM mysql:${MYSQL_VERSION} 17 | COPY --from=0 /usr/lib/mysql/plugin/ /usr/lib64/mysql/plugin/ 18 | -------------------------------------------------------------------------------- /contrib/ruby/Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/extensiontask" 3 | require "rake/testtask" 4 | 5 | Rake::ExtensionTask.new do |ext| 6 | ext.name = "cext" 7 | ext.ext_dir = "ext/trilogy-ruby" 8 | ext.lib_dir = "lib/trilogy" 9 | end 10 | 11 | # When any of the parent library's files change, we need to re-run extconf 12 | vendored_c_lib = FileList["ext/trilogy-ruby/src/**/*.c", "ext/trilogy-ruby/inc/**/*.h"] 13 | if extconf_task = Rake.application.tasks.find { |t| t.name =~ /Makefile/ } 14 | task extconf_task => vendored_c_lib 15 | end 16 | 17 | Rake::TestTask.new do |t| 18 | t.libs << "test" 19 | t.test_files = FileList['test/*_test.rb'] 20 | t.verbose = true 21 | end 22 | task :test => :compile 23 | 24 | task :default => :test 25 | 26 | task :console => :compile do 27 | sh "ruby -I lib -r trilogy -S irb" 28 | end 29 | -------------------------------------------------------------------------------- /test/mysql/conf.d/8.0/build.cnf: -------------------------------------------------------------------------------- 1 | # This MySQL configuration file is mounted into the Database container (/etc/mysql/conf.d) 2 | # at boot and is picked up automatically. 3 | 4 | [mysqld] 5 | 6 | sql_mode = NO_ENGINE_SUBSTITUTION 7 | server_id = 1 8 | gtid_mode = ON 9 | enforce_gtid_consistency = ON 10 | log_bin = mysql-bin.log 11 | 12 | # Since we generate our own certificates for testing purposes, we need to instruct MySQL 13 | # on where to find them. The certifcates are generated as an entrypoint script located at: 14 | # mysql/docker-entrypoint-initdb.d/generate_keys.sh 15 | # The /mysql-certs directory is mounted into both the database container and the app 16 | # container so that they both can have access to the generated certificates. 17 | # -- 18 | ssl_ca = /mysql-certs/ca.pem 19 | ssl_cert = /mysql-certs/server-cert.pem 20 | ssl_key = /mysql-certs/server-key.pem 21 | -------------------------------------------------------------------------------- /test/protocol/building/ping_packet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | TEST test_build_ping_packet() 11 | { 12 | trilogy_builder_t builder; 13 | trilogy_buffer_t buff; 14 | 15 | int err = trilogy_buffer_init(&buff, 1); 16 | ASSERT_OK(err); 17 | 18 | err = trilogy_builder_init(&builder, &buff, 0); 19 | ASSERT_OK(err); 20 | 21 | err = trilogy_build_ping_packet(&builder); 22 | ASSERT_OK(err); 23 | 24 | static const uint8_t expected[] = {0x01, 0x00, 0x00, 0x00, 0x0e}; 25 | 26 | ASSERT_MEM_EQ(buff.buff, expected, buff.len); 27 | 28 | trilogy_buffer_free(&buff); 29 | PASS(); 30 | } 31 | 32 | int build_ping_packet_test() 33 | { 34 | RUN_TEST(test_build_ping_packet); 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /test/protocol/building/quit_packet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | TEST test_build_quit_packet() 11 | { 12 | trilogy_builder_t builder; 13 | trilogy_buffer_t buff; 14 | 15 | int err = trilogy_buffer_init(&buff, 1); 16 | ASSERT_OK(err); 17 | 18 | err = trilogy_builder_init(&builder, &buff, 0); 19 | ASSERT_OK(err); 20 | 21 | err = trilogy_build_quit_packet(&builder); 22 | ASSERT_OK(err); 23 | 24 | static const uint8_t expected[] = {0x01, 0x00, 0x00, 0x00, 0x01}; 25 | 26 | ASSERT_MEM_EQ(buff.buff, expected, buff.len); 27 | 28 | trilogy_buffer_free(&buff); 29 | PASS(); 30 | } 31 | 32 | int build_quit_packet_test() 33 | { 34 | RUN_TEST(test_build_quit_packet); 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /test/mysql/conf.d/8.4/build.cnf: -------------------------------------------------------------------------------- 1 | # This MySQL configuration file is mounted into the Database container (/etc/mysql/conf.d) 2 | # at boot and is picked up automatically. 3 | 4 | [mysqld] 5 | 6 | sql_mode = NO_ENGINE_SUBSTITUTION 7 | server_id = 1 8 | gtid_mode = ON 9 | enforce_gtid_consistency = ON 10 | log_bin = mysql-bin.log 11 | mysql_native_password=ON 12 | 13 | # Since we generate our own certificates for testing purposes, we need to instruct MySQL 14 | # on where to find them. The certifcates are generated as an entrypoint script located at: 15 | # mysql/docker-entrypoint-initdb.d/generate_keys.sh 16 | # The /mysql-certs directory is mounted into both the database container and the app 17 | # container so that they both can have access to the generated certificates. 18 | # -- 19 | ssl_ca = /mysql-certs/ca.pem 20 | ssl_cert = /mysql-certs/server-cert.pem 21 | ssl_key = /mysql-certs/server-key.pem 22 | -------------------------------------------------------------------------------- /test/protocol/building/stmt_close_packet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | TEST test_stmt_close_packet() 11 | { 12 | trilogy_builder_t builder; 13 | trilogy_buffer_t buff; 14 | 15 | int err = trilogy_buffer_init(&buff, 1); 16 | ASSERT_OK(err); 17 | 18 | err = trilogy_builder_init(&builder, &buff, 0); 19 | ASSERT_OK(err); 20 | 21 | uint32_t stmt_id = 1; 22 | 23 | err = trilogy_build_stmt_close_packet(&builder, stmt_id); 24 | ASSERT_OK(err); 25 | 26 | static const uint8_t expected[] = {0x05, 0x00, 0x00, 0x00, 0x19, 0x01, 0x00, 0x00, 0x00}; 27 | 28 | ASSERT_MEM_EQ(buff.buff, expected, buff.len); 29 | 30 | trilogy_buffer_free(&buff); 31 | PASS(); 32 | } 33 | 34 | int stmt_close_packet_test() 35 | { 36 | RUN_TEST(test_stmt_close_packet); 37 | 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /test/protocol/building/stmt_reset_packet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | TEST test_stmt_reset_packet() 11 | { 12 | trilogy_builder_t builder; 13 | trilogy_buffer_t buff; 14 | 15 | int err = trilogy_buffer_init(&buff, 1); 16 | ASSERT_OK(err); 17 | 18 | err = trilogy_builder_init(&builder, &buff, 0); 19 | ASSERT_OK(err); 20 | 21 | uint32_t stmt_id = 1; 22 | 23 | err = trilogy_build_stmt_reset_packet(&builder, stmt_id); 24 | ASSERT_OK(err); 25 | 26 | static const uint8_t expected[] = {0x05, 0x00, 0x00, 0x00, 0x1a, 0x01, 0x00, 0x00, 0x00}; 27 | 28 | ASSERT_MEM_EQ(buff.buff, expected, buff.len); 29 | 30 | trilogy_buffer_free(&buff); 31 | PASS(); 32 | } 33 | 34 | int stmt_reset_packet_test() 35 | { 36 | RUN_TEST(test_stmt_reset_packet); 37 | 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /test/protocol/building/set_option_packet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | TEST test_build_set_option_packet() 11 | { 12 | trilogy_builder_t builder; 13 | trilogy_buffer_t buff; 14 | 15 | int err = trilogy_buffer_init(&buff, 1); 16 | ASSERT_OK(err); 17 | 18 | err = trilogy_builder_init(&builder, &buff, 0); 19 | ASSERT_OK(err); 20 | 21 | const uint16_t option = 1; 22 | 23 | err = trilogy_build_set_option_packet(&builder, option); 24 | ASSERT_OK(err); 25 | 26 | static const uint8_t expected[] = {0x03, 0x00, 0x00, 0x00, 0x1b, 0x01, 0x00}; 27 | 28 | ASSERT_MEM_EQ(buff.buff, expected, buff.len); 29 | 30 | trilogy_buffer_free(&buff); 31 | PASS(); 32 | } 33 | 34 | int build_set_option_packet_test() 35 | { 36 | RUN_TEST(test_build_set_option_packet); 37 | 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /test/protocol/building/change_db_packet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | static const char db[] = "test"; 11 | 12 | TEST test_build_change_db_packet() 13 | { 14 | trilogy_builder_t builder; 15 | trilogy_buffer_t buff; 16 | 17 | int err = trilogy_buffer_init(&buff, 1); 18 | ASSERT_OK(err); 19 | 20 | err = trilogy_builder_init(&builder, &buff, 0); 21 | ASSERT_OK(err); 22 | 23 | err = trilogy_build_change_db_packet(&builder, db, strlen(db)); 24 | ASSERT_OK(err); 25 | 26 | static const uint8_t expected[] = {0x05, 0x00, 0x00, 0x00, 0x02, 0x74, 0x65, 0x73, 0x74}; 27 | 28 | ASSERT_MEM_EQ(buff.buff, expected, buff.len); 29 | 30 | trilogy_buffer_free(&buff); 31 | PASS(); 32 | } 33 | 34 | int build_change_db_packet_test() 35 | { 36 | RUN_TEST(test_build_change_db_packet); 37 | 38 | return 0; 39 | } 40 | -------------------------------------------------------------------------------- /test/protocol/parsing/result_packet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | static uint8_t result_packet[] = {0x01}; 11 | 12 | TEST test_parse_result_packet() 13 | { 14 | trilogy_result_packet_t packet; 15 | 16 | int err = trilogy_parse_result_packet(result_packet, sizeof(result_packet), &packet); 17 | ASSERT_OK(err); 18 | 19 | ASSERT_EQ(1, packet.column_count); 20 | 21 | PASS(); 22 | } 23 | 24 | TEST test_parse_result_packet_truncated() 25 | { 26 | trilogy_result_packet_t packet; 27 | 28 | int err = trilogy_parse_result_packet(result_packet, sizeof(result_packet) - 1, &packet); 29 | ASSERT_ERR(TRILOGY_TRUNCATED_PACKET, err); 30 | 31 | PASS(); 32 | } 33 | 34 | int parse_result_packet_test() 35 | { 36 | RUN_TEST(test_parse_result_packet); 37 | RUN_TEST(test_parse_result_packet_truncated); 38 | 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /test/error_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "test.h" 6 | 7 | #include "trilogy/error.h" 8 | 9 | TEST test_trilogy_error_strings() 10 | { 11 | #define XX(name, code) \ 12 | const char *name##_expected_str = #name; \ 13 | const char *name##_ret = trilogy_error(name); \ 14 | ASSERT_STRN_EQ(name##_ret, name##_expected_str, strlen(name##_expected_str)); 15 | 16 | TRILOGY_ERROR_CODES(XX) 17 | #undef XX 18 | 19 | PASS(); 20 | } 21 | 22 | TEST test_null_for_undefined_errors() 23 | { 24 | ASSERT_EQ(NULL, trilogy_error(1)); 25 | 26 | PASS(); 27 | } 28 | 29 | int error_test() 30 | { 31 | RUN_TEST(test_trilogy_error_strings); 32 | RUN_TEST(test_null_for_undefined_errors); 33 | 34 | return 0; 35 | } 36 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | permissions: 4 | contents: read 5 | 6 | on: 7 | push: 8 | branches: 9 | - main 10 | tags: 11 | - v* 12 | pull_request: 13 | 14 | jobs: 15 | build: 16 | name: ${{ format('Build ({0}, {1}, {2})', matrix.mysql, matrix.distribution, matrix.ruby) }} 17 | runs-on: ubuntu-latest 18 | strategy: 19 | fail-fast: false 20 | matrix: 21 | mysql: ["8.0", "8.4"] 22 | distribution: ["debian:bookworm", "ubuntu:noble", "ubuntu:jammy", "ubuntu:focal"] 23 | ruby: ["3.3", "3.4"] 24 | steps: 25 | - uses: actions/checkout@v6 26 | - name: docker login 27 | run: echo $GITHUB_TOKEN | docker login ghcr.io --username trilogy --password-stdin 28 | env: 29 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 30 | - name: Run tests 31 | env: 32 | MYSQL_VERSION: ${{ matrix.mysql }} 33 | DISTRIBUTION: ${{ matrix.distribution }} 34 | RUBY_VERSION: ${{ matrix.ruby }} 35 | run: script/cibuild 36 | -------------------------------------------------------------------------------- /test/protocol/building/stmt_prepare_packet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | TEST test_stmt_prepare_packet() 11 | { 12 | trilogy_builder_t builder; 13 | trilogy_buffer_t buff; 14 | 15 | int err = trilogy_buffer_init(&buff, 1); 16 | ASSERT_OK(err); 17 | 18 | err = trilogy_builder_init(&builder, &buff, 0); 19 | ASSERT_OK(err); 20 | 21 | const char *sql = "SELECT ?"; 22 | size_t sql_len = strlen(sql); 23 | 24 | err = trilogy_build_stmt_prepare_packet(&builder, sql, sql_len); 25 | ASSERT_OK(err); 26 | 27 | static const uint8_t expected[] = {0x09, 0x00, 0x00, 0x00, 0x16, 'S', 'E', 'L', 'E', 'C', 'T', ' ', '?'}; 28 | 29 | ASSERT_MEM_EQ(buff.buff, expected, buff.len); 30 | 31 | trilogy_buffer_free(&buff); 32 | PASS(); 33 | } 34 | 35 | int stmt_prepare_packet_test() 36 | { 37 | RUN_TEST(test_stmt_prepare_packet); 38 | 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /contrib/ruby/ext/trilogy-ruby/trilogy-ruby.h: -------------------------------------------------------------------------------- 1 | #ifndef TRILOGY_RUBY_H 2 | #define TRILOGY_RUBY_H 3 | 4 | #include 5 | 6 | #include 7 | 8 | #define TRILOGY_FLAGS_CAST 1 9 | #define TRILOGY_FLAGS_CAST_BOOLEANS 2 10 | #define TRILOGY_FLAGS_LOCAL_TIMEZONE 4 11 | #define TRILOGY_FLAGS_FLATTEN_ROWS 8 12 | #define TRILOGY_FLAGS_CAST_ALL_DECIMALS_TO_BIGDECIMALS 16 13 | #define TRILOGY_FLAGS_DEFAULT (TRILOGY_FLAGS_CAST) 14 | 15 | struct rb_trilogy_cast_options { 16 | bool cast; 17 | bool cast_booleans; 18 | bool database_local_time; 19 | bool flatten_rows; 20 | bool cast_decimals_to_bigdecimals; 21 | }; 22 | 23 | struct column_info { 24 | TRILOGY_TYPE_t type; 25 | TRILOGY_CHARSET_t charset; 26 | uint32_t len; 27 | uint16_t flags; 28 | uint8_t decimals; 29 | }; 30 | 31 | extern VALUE Trilogy_CastError; 32 | 33 | VALUE 34 | rb_trilogy_cast_value(const trilogy_value_t *value, const struct column_info *column, 35 | const struct rb_trilogy_cast_options *options); 36 | 37 | void rb_trilogy_cast_init(void); 38 | 39 | #endif 40 | -------------------------------------------------------------------------------- /inc/trilogy/packet_parser.h: -------------------------------------------------------------------------------- 1 | #ifndef TRILOGY_PACKET_PARSER_H 2 | #define TRILOGY_PACKET_PARSER_H 3 | 4 | #include 5 | #include 6 | 7 | // Data between client and server is exchanged in packets of max 16MByte size. 8 | #define TRILOGY_MAX_PACKET_LEN 0xffffff 9 | 10 | typedef struct { 11 | int (*on_packet_begin)(void *); 12 | int (*on_packet_data)(void *, const uint8_t *, size_t); 13 | int (*on_packet_end)(void *); 14 | } trilogy_packet_parser_callbacks_t; 15 | 16 | typedef struct { 17 | void *user_data; 18 | const trilogy_packet_parser_callbacks_t *callbacks; 19 | 20 | uint8_t sequence_number; 21 | 22 | // private: 23 | unsigned bytes_remaining : 24; 24 | 25 | unsigned state : 3; 26 | unsigned fragment : 1; 27 | unsigned deferred_end_callback : 1; 28 | } trilogy_packet_parser_t; 29 | 30 | void trilogy_packet_parser_init(trilogy_packet_parser_t *parser, const trilogy_packet_parser_callbacks_t *callbacks); 31 | 32 | size_t trilogy_packet_parser_execute(trilogy_packet_parser_t *parser, const uint8_t *buf, size_t len, int *error); 33 | 34 | #endif 35 | -------------------------------------------------------------------------------- /contrib/ruby/trilogy.gemspec: -------------------------------------------------------------------------------- 1 | require File.expand_path("../lib/trilogy/version", __FILE__) 2 | 3 | Gem::Specification.new do |s| 4 | s.name = "trilogy" 5 | s.version = Trilogy::VERSION 6 | s.authors = ["GitHub Engineering"] 7 | s.email = "opensource+trilogy@github.com" 8 | s.license = "MIT" 9 | s.homepage = "https://github.com/trilogy-libraries/trilogy" 10 | s.summary = "A friendly MySQL-compatible library for Ruby, binding to libtrilogy" 11 | 12 | s.extensions = "ext/trilogy-ruby/extconf.rb" 13 | 14 | gem_files = %w[LICENSE README.md Rakefile trilogy.gemspec] 15 | gem_files += Dir.glob("lib/**/*.rb") 16 | gem_files += Dir.glob("ext/trilogy-ruby/*.c") 17 | gem_files += Dir.glob("ext/trilogy-ruby/*.h") 18 | gem_files += Dir.glob("ext/trilogy-ruby/src/**/*.c") 19 | gem_files += Dir.glob("ext/trilogy-ruby/inc/**/*.h") 20 | 21 | s.files = gem_files 22 | 23 | s.require_paths = ["lib"] 24 | 25 | s.required_ruby_version = ">= 3.0" 26 | 27 | s.add_dependency "bigdecimal" 28 | 29 | s.add_development_dependency "rake-compiler", "~> 1.0" 30 | s.add_development_dependency "minitest", "~> 5.5" 31 | end 32 | -------------------------------------------------------------------------------- /test/protocol/building/query_packet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | static const char query[] = "SELECT * FROM users"; 11 | 12 | TEST test_build_query_packet() 13 | { 14 | trilogy_builder_t builder; 15 | trilogy_buffer_t buff; 16 | 17 | int err = trilogy_buffer_init(&buff, 1); 18 | ASSERT_OK(err); 19 | 20 | err = trilogy_builder_init(&builder, &buff, 0); 21 | ASSERT_OK(err); 22 | 23 | err = trilogy_build_query_packet(&builder, query, strlen(query)); 24 | ASSERT_OK(err); 25 | 26 | static const uint8_t expected[] = {0x14, 0x00, 0x00, 0x00, 0x03, 0x53, 0x45, 0x4c, 0x45, 0x43, 0x54, 0x20, 27 | 0x2a, 0x20, 0x46, 0x52, 0x4f, 0x4d, 0x20, 0x75, 0x73, 0x65, 0x72, 0x73}; 28 | 29 | ASSERT_MEM_EQ(buff.buff, expected, buff.len); 30 | 31 | trilogy_buffer_free(&buff); 32 | PASS(); 33 | } 34 | 35 | int build_query_packet_test() 36 | { 37 | RUN_TEST(test_build_query_packet); 38 | 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2021 GitHub, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /contrib/ruby/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2021 GitHub, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person 4 | obtaining a copy of this software and associated documentation 5 | files (the "Software"), to deal in the Software without 6 | restriction, including without limitation the rights to use, 7 | copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the 9 | Software is furnished to do so, subject to the following 10 | conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /test/protocol/parsing/eof_packet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | static uint8_t eof_packet[] = {0xfe, 0x00, 0x00, 0x02, 0x00}; 11 | 12 | TEST test_parse_eof_packet() 13 | { 14 | trilogy_eof_packet_t packet; 15 | 16 | uint32_t flags = TRILOGY_CAPABILITIES_PROTOCOL_41; 17 | 18 | int err = trilogy_parse_eof_packet(eof_packet, sizeof(eof_packet), flags, &packet); 19 | ASSERT_OK(err); 20 | 21 | ASSERT_EQ(0, packet.warning_count); 22 | ASSERT_EQ(0x02, packet.status_flags); 23 | 24 | PASS(); 25 | } 26 | 27 | TEST test_parse_eof_packet_truncated() 28 | { 29 | trilogy_eof_packet_t packet; 30 | 31 | uint32_t flags = TRILOGY_CAPABILITIES_PROTOCOL_41; 32 | 33 | int err = trilogy_parse_eof_packet(eof_packet, sizeof(eof_packet) - 3, flags, &packet); 34 | ASSERT_ERR(TRILOGY_TRUNCATED_PACKET, err); 35 | 36 | PASS(); 37 | } 38 | 39 | int parse_eof_packet_test() 40 | { 41 | RUN_TEST(test_parse_eof_packet); 42 | RUN_TEST(test_parse_eof_packet_truncated); 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /test/protocol/building/stmt_bind_data_packet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | TEST test_stmt_bind_data_packet() 11 | { 12 | trilogy_builder_t builder; 13 | trilogy_buffer_t buff; 14 | 15 | int err = trilogy_buffer_init(&buff, 1); 16 | ASSERT_OK(err); 17 | 18 | err = trilogy_builder_init(&builder, &buff, 0); 19 | ASSERT_OK(err); 20 | 21 | uint32_t stmt_id = 1; 22 | uint32_t param_id = 2; 23 | uint8_t data[] = {'d', 'a', 't', 'a'}; 24 | size_t data_len = sizeof(data); 25 | 26 | err = trilogy_build_stmt_bind_data_packet(&builder, stmt_id, param_id, data, data_len); 27 | ASSERT_OK(err); 28 | 29 | static const uint8_t expected[] = {0x0b, 0x00, 0x00, 0x00, 0x18, 0x01, 0x00, 0x00, 0x00, 0x02, 0x00, 'd', 'a', 30 | 't', 'a'}; 31 | 32 | ASSERT_MEM_EQ(buff.buff, expected, buff.len); 33 | 34 | trilogy_buffer_free(&buff); 35 | PASS(); 36 | } 37 | 38 | int stmt_bind_data_packet_test() 39 | { 40 | RUN_TEST(test_stmt_bind_data_packet); 41 | 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /test/protocol/parsing/ok_packet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | static uint8_t ok_packet[] = {0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00}; 11 | 12 | TEST test_parse_ok_packet() 13 | { 14 | trilogy_ok_packet_t packet; 15 | 16 | uint32_t flags = TRILOGY_CAPABILITIES_PROTOCOL_41; 17 | 18 | int err = trilogy_parse_ok_packet(ok_packet, sizeof(ok_packet), flags, &packet); 19 | ASSERT_OK(err); 20 | 21 | ASSERT_EQ(0, packet.affected_rows); 22 | ASSERT_EQ(0, packet.last_insert_id); 23 | ASSERT_EQ(0x02, packet.status_flags); 24 | ASSERT_EQ(0, packet.warning_count); 25 | 26 | PASS(); 27 | } 28 | 29 | TEST test_parse_ok_packet_truncated() 30 | { 31 | trilogy_ok_packet_t packet; 32 | 33 | uint32_t flags = TRILOGY_CAPABILITIES_PROTOCOL_41; 34 | 35 | int err = trilogy_parse_ok_packet(ok_packet, sizeof(ok_packet) - 2, flags, &packet); 36 | ASSERT_ERR(TRILOGY_TRUNCATED_PACKET, err); 37 | 38 | PASS(); 39 | } 40 | 41 | int parse_ok_packet_test() 42 | { 43 | RUN_TEST(test_parse_ok_packet); 44 | RUN_TEST(test_parse_ok_packet_truncated); 45 | 46 | return 0; 47 | } 48 | -------------------------------------------------------------------------------- /test/protocol/building/stmt_execute_packet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | TEST test_stmt_execute_packet() 11 | { 12 | trilogy_builder_t builder; 13 | trilogy_buffer_t buff; 14 | 15 | int err = trilogy_buffer_init(&buff, 1); 16 | ASSERT_OK(err); 17 | 18 | err = trilogy_builder_init(&builder, &buff, 0); 19 | ASSERT_OK(err); 20 | 21 | uint32_t stmt_id = 1; 22 | uint8_t flags = 1; 23 | trilogy_binary_value_t binds[] = {{.is_null = false, .type = TRILOGY_TYPE_LONG, .as.uint32 = 15}}; 24 | uint16_t num_binds = 1; 25 | 26 | err = trilogy_build_stmt_execute_packet(&builder, stmt_id, flags, binds, num_binds); 27 | ASSERT_OK(err); 28 | 29 | static const uint8_t expected[] = {0x12, 0x00, 0x00, 0x00, 0x17, 0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 30 | 0x00, 0x00, 0x01, 0x03, 0x00, 0x0f, 0x00, 0x00, 0x00}; 31 | 32 | ASSERT_MEM_EQ(buff.buff, expected, buff.len); 33 | 34 | trilogy_buffer_free(&buff); 35 | PASS(); 36 | } 37 | 38 | int stmt_execute_packet_test() 39 | { 40 | RUN_TEST(test_stmt_execute_packet); 41 | 42 | return 0; 43 | } 44 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | ARG DISTRIBUTION=debian:bookworm 2 | FROM ${DISTRIBUTION} 3 | LABEL maintainer="github@github.com" 4 | 5 | RUN apt-get update -qq && DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y build-essential ca-certificates wget libssl-dev default-libmysqlclient-dev clang clang-tools llvm valgrind netcat-traditional 6 | 7 | # Install libclang-rt-14-dev for Debian Bookworm so that Trilogy builds. 8 | ARG DISTRIBUTION=debian:bookworm 9 | RUN if [ "${DISTRIBUTION}" = "debian:bookworm" ]; then \ 10 | DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y libclang-rt-14-dev; \ 11 | fi 12 | 13 | # Install libclang-rt-18-dev for Ubuntu Noble so that Trilogy builds. 14 | RUN if [ "${DISTRIBUTION}" = "ubuntu:noble" ]; then \ 15 | DEBIAN_FRONTEND=noninteractive apt-get install --no-install-recommends -y libclang-rt-18-dev; \ 16 | fi 17 | 18 | RUN update-ca-certificates 19 | 20 | RUN wget https://github.com/postmodern/ruby-install/releases/download/v0.10.1/ruby-install-0.10.1.tar.gz && \ 21 | tar -xzvf ruby-install-0.10.1.tar.gz && \ 22 | cd ruby-install-0.10.1/ && \ 23 | make install 24 | 25 | ARG RUBY_VERSION=3.4 26 | RUN ruby-install --system ruby ${RUBY_VERSION} 27 | RUN ruby --version 28 | 29 | WORKDIR /app 30 | COPY . . 31 | 32 | CMD ["script/test"] 33 | -------------------------------------------------------------------------------- /inc/trilogy/vendor/curl_hostcheck.h: -------------------------------------------------------------------------------- 1 | #ifndef HEADER_CURL_HOSTCHECK_H 2 | #define HEADER_CURL_HOSTCHECK_H 3 | /*************************************************************************** 4 | * _ _ ____ _ 5 | * Project ___| | | | _ \| | 6 | * / __| | | | |_) | | 7 | * | (__| |_| | _ <| |___ 8 | * \___|\___/|_| \_\_____| 9 | * 10 | * Copyright (C) 1998 - 2012, Daniel Stenberg, , et al. 11 | * 12 | * This software is licensed as described in the file COPYING, which 13 | * you should have received as part of this distribution. The terms 14 | * are also available at http://curl.haxx.se/docs/copyright.html. 15 | * 16 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell 17 | * copies of the Software, and permit persons to whom the Software is 18 | * furnished to do so, under the terms of the COPYING file. 19 | * 20 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 21 | * KIND, either express or implied. 22 | * 23 | ***************************************************************************/ 24 | 25 | #define CURL_HOST_NOMATCH 0 26 | #define CURL_HOST_MATCH 1 27 | int Curl_cert_hostcheck(const char *match_pattern, const char *hostname); 28 | 29 | #endif /* HEADER_CURL_HOSTCHECK_H */ 30 | -------------------------------------------------------------------------------- /contrib/ruby/lib/trilogy.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require "trilogy/version" 4 | require "trilogy/error" 5 | require "trilogy/result" 6 | require "trilogy/cext" 7 | require "trilogy/encoding" 8 | 9 | class Trilogy 10 | def initialize(options = {}) 11 | options[:port] = options[:port].to_i if options[:port] 12 | mysql_encoding = options[:encoding] || "utf8mb4" 13 | encoding = Trilogy::Encoding.find(mysql_encoding) 14 | charset = Trilogy::Encoding.charset(mysql_encoding) 15 | @connection_options = options 16 | @connected_host = nil 17 | 18 | _connect(encoding, charset, options) 19 | end 20 | 21 | def connection_options 22 | @connection_options.dup.freeze 23 | end 24 | 25 | def in_transaction? 26 | (server_status & SERVER_STATUS_IN_TRANS) != 0 27 | end 28 | 29 | def server_info 30 | version_str = server_version 31 | 32 | if /\A(\d+)\.(\d+)\.(\d+)/ =~ version_str 33 | version_num = ($1.to_i * 10000) + ($2.to_i * 100) + $3.to_i 34 | end 35 | 36 | { :version => version_str, :id => version_num } 37 | end 38 | 39 | def connected_host 40 | @connected_host ||= query_with_flags("select @@hostname", query_flags | QUERY_FLAGS_FLATTEN_ROWS).rows.first 41 | end 42 | 43 | def query_with_flags(sql, flags) 44 | old_flags = query_flags 45 | self.query_flags = flags 46 | 47 | query(sql) 48 | ensure 49 | self.query_flags = old_flags 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | db: 3 | platform: linux/x86_64 4 | image: "ghcr.io/trilogy-libraries/trilogy/ci-mysql:${MYSQL_VERSION}-debian" 5 | build: 6 | context: . 7 | dockerfile: test/mysql/Dockerfile 8 | args: 9 | - MYSQL_VERSION=${MYSQL_VERSION} 10 | cache_from: 11 | - ghcr.io/trilogy-libraries/trilogy/ci-mysql:${MYSQL_VERSION}-debian 12 | environment: 13 | MYSQL_ALLOW_EMPTY_PASSWORD: 1 14 | MYSQL_DATABASE: test 15 | MYSQL_HOST: db.local 16 | volumes: 17 | - ./tmp/mysql-certs:/mysql-certs 18 | - ./test/mysql/conf.d/${MYSQL_VERSION}:/etc/mysql/conf.d 19 | - ./test/mysql/docker-entrypoint-initdb.d:/docker-entrypoint-initdb.d 20 | app: 21 | image: ghcr.io/trilogy-libraries/trilogy/ci-app:distro-${DISTRIBUTION_SLUG}-ruby-${RUBY_VERSION}-mysql-${MYSQL_VERSION} 22 | privileged: true 23 | build: 24 | context: . 25 | args: 26 | - BUILDKIT_INLINE_CACHE=1 27 | - DISTRIBUTION=${DISTRIBUTION} 28 | - RUBY_VERSION=${RUBY_VERSION} 29 | cache_from: 30 | - ghcr.io/trilogy-libraries/trilogy/ci-app:distro-${DISTRIBUTION_SLUG}-ruby-${RUBY_VERSION}-mysql-${MYSQL_VERSION} 31 | environment: 32 | MYSQL_HOST: db.local 33 | TRILOGY_TEST_CERTS: "/mysql-certs" 34 | depends_on: 35 | - db 36 | links: 37 | - "db:db.local" 38 | - "db:wildcard.db.local" 39 | volumes: 40 | - "./tmp/mysql-certs:/mysql-certs" 41 | -------------------------------------------------------------------------------- /test/client/escape_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "../test.h" 7 | 8 | #include "trilogy/client.h" 9 | #include "trilogy/error.h" 10 | 11 | TEST test_escape() 12 | { 13 | trilogy_conn_t conn; 14 | 15 | int err = trilogy_init(&conn); 16 | ASSERT_OK(err); 17 | 18 | const char *escaped; 19 | size_t escaped_len; 20 | const char escape_me[] = "\"\0\'\\\n\r\x1Ahello"; 21 | err = trilogy_escape(&conn, escape_me, sizeof(escape_me) - 1, &escaped, &escaped_len); 22 | ASSERT_OK(err); 23 | ASSERT_MEM_EQ("\\\"\\0\\'\\\\\\n\\r\\Zhello", escaped, escaped_len); 24 | 25 | trilogy_free(&conn); 26 | PASS(); 27 | } 28 | 29 | TEST test_escape_no_backslashes() 30 | { 31 | trilogy_conn_t conn; 32 | 33 | int err = trilogy_init(&conn); 34 | ASSERT_OK(err); 35 | 36 | uint16_t old_server_status = conn.server_status; 37 | conn.server_status = TRILOGY_SERVER_STATUS_NO_BACKSLASH_ESCAPES; 38 | 39 | const char *escaped; 40 | size_t escaped_len; 41 | const char escape_me[] = "hello ' world"; 42 | err = trilogy_escape(&conn, escape_me, sizeof(escape_me) - 1, &escaped, &escaped_len); 43 | ASSERT_OK(err); 44 | ASSERT_MEM_EQ("hello '' world", escaped, escaped_len); 45 | 46 | conn.server_status = old_server_status; 47 | 48 | trilogy_free(&conn); 49 | PASS(); 50 | } 51 | 52 | int client_escape_test() 53 | { 54 | RUN_TEST(test_escape); 55 | RUN_TEST(test_escape_no_backslashes); 56 | 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /test/protocol/parsing/row_packet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | static uint8_t row_packet[] = {0x08, 0x48, 0x6f, 0x6d, 0x65, 0x62, 0x72, 0x65, 0x77}; 11 | 12 | static uint8_t row_packet_with_null[] = {0xfb}; 13 | 14 | TEST test_parse_row_packet() 15 | { 16 | trilogy_value_t packet; 17 | 18 | uint64_t column_count = 1; 19 | 20 | int err = trilogy_parse_row_packet(row_packet, sizeof(row_packet), column_count, &packet); 21 | ASSERT_OK(err); 22 | ASSERT_EQ(false, packet.is_null); 23 | ASSERT_MEM_EQ(packet.data, "Homebrew", packet.data_len); 24 | 25 | PASS(); 26 | } 27 | 28 | TEST test_parse_row_packet_truncated() 29 | { 30 | trilogy_value_t packet; 31 | 32 | uint64_t column_count = 1; 33 | 34 | int err = trilogy_parse_row_packet(row_packet, sizeof(row_packet) - 3, column_count, &packet); 35 | ASSERT_ERR(TRILOGY_TRUNCATED_PACKET, err); 36 | 37 | PASS(); 38 | } 39 | 40 | TEST test_parse_row_packet_with_null() 41 | { 42 | trilogy_value_t packet; 43 | 44 | uint64_t column_count = 1; 45 | 46 | int err = trilogy_parse_row_packet(row_packet_with_null, sizeof(row_packet_with_null), column_count, &packet); 47 | ASSERT_OK(err); 48 | ASSERT_EQ(true, packet.is_null); 49 | ASSERT_EQ(0, packet.data_len); 50 | 51 | PASS(); 52 | } 53 | 54 | int parse_row_packet_test() 55 | { 56 | RUN_TEST(test_parse_row_packet); 57 | RUN_TEST(test_parse_row_packet_truncated); 58 | RUN_TEST(test_parse_row_packet_with_null); 59 | 60 | return 0; 61 | } 62 | -------------------------------------------------------------------------------- /test/mysql/docker-entrypoint-initdb.d/generate_keys.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | set -euo pipefail 4 | 5 | cd /mysql-certs 6 | 7 | # Generate a CA to test with 8 | 9 | openssl req -new -newkey rsa:2048 -days 365 -nodes -sha256 -x509 -keyout ca-key.pem -out ca.pem -config <( 10 | cat <<-EOF 11 | [req] 12 | distinguished_name = req_distinguished_name 13 | x509_extensions = v3_req 14 | prompt = no 15 | 16 | [req_distinguished_name] 17 | CN=MySQL Test CA 18 | O=GitHub 19 | 20 | [v3_req] 21 | subjectKeyIdentifier = hash 22 | authorityKeyIdentifier = keyid,issuer 23 | basicConstraints = CA:TRUE 24 | keyUsage = critical, keyCertSign 25 | EOF 26 | ) 27 | 28 | # Generate a server certificate 29 | 30 | domain=${MYSQL_HOST:-localhost} 31 | 32 | openssl req -new -newkey rsa:2048 -nodes -sha256 -subj "/CN=$domain" -keyout server-key.pem -out server-csr.pem 33 | openssl x509 -req -sha256 -CA ca.pem -CAkey ca-key.pem -set_serial 1 \ 34 | -extensions a \ 35 | -extfile <(echo "[a] 36 | basicConstraints = CA:FALSE 37 | subjectAltName=DNS:$domain,DNS:*.$domain 38 | extendedKeyUsage=serverAuth 39 | ") \ 40 | -days 365 \ 41 | -in server-csr.pem \ 42 | -out server-cert.pem 43 | 44 | # Generate a client certificate 45 | 46 | openssl req -new -newkey rsa:2048 -nodes -sha256 -subj "/CN=MySQL Test Client Certificate" -keyout client-key.pem -out client-csr.pem 47 | openssl x509 -req -sha256 -CA ca.pem -CAkey ca-key.pem -set_serial 2 \ 48 | -extensions a \ 49 | -extfile <(echo "[a] 50 | basicConstraints = CA:FALSE 51 | extendedKeyUsage=clientAuth 52 | ") \ 53 | -days 365 \ 54 | -in client-csr.pem \ 55 | -out client-cert.pem 56 | -------------------------------------------------------------------------------- /script/test: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | 3 | set -ex 4 | 5 | MYSQL_HOST=${MYSQL_HOST:=127.0.0.1} 6 | 7 | wait_for_mysql() { 8 | echo "Waiting for MySQL to boot" 9 | for i in {1..30}; do 10 | nc -z "$MYSQL_HOST" 3306 && break 11 | sleep 1 12 | done 13 | } 14 | 15 | wait_for_mysql 16 | 17 | sanitizers="address" 18 | old_cc="$CC" 19 | old_cflags="$CFLAGS" 20 | 21 | export CFLAGS+="-O3" 22 | 23 | if [ "$(uname)" == "Linux" ] 24 | then 25 | 26 | make clean 27 | scan-build --status-bugs make 28 | 29 | sanitizers+=",undefined" 30 | 31 | export CC=clang 32 | 33 | elif [ "$(uname)" == "Darwin" ] 34 | then 35 | 36 | make analyze 37 | fi 38 | 39 | export CFLAGS+=" -fsanitize=$sanitizers" 40 | 41 | echo "Building with sanitizers: $sanitizers" 42 | export LSAN_OPTIONS=verbosity=1:log_threads=1 43 | make clean 44 | make 45 | make test 46 | 47 | if [ "$(uname)" == "Linux" ] 48 | then 49 | 50 | if [ "$old_cc" ] 51 | then 52 | 53 | export CC=$old_cc 54 | else 55 | unset CC 56 | fi 57 | 58 | if [ "$old_cflags" ] 59 | then 60 | 61 | export CFLAGS=$old_cflags 62 | else 63 | unset CFLAGS 64 | fi 65 | 66 | echo "Building Cleanly" 67 | make clean 68 | make 69 | 70 | echo "Testing under valgrind" 71 | valgrind --leak-check=full --show-reachable=yes --track-origins=yes \ 72 | ./example/trilogy_query -h $MYSQL_HOST -u root -P 3306 -s "SHOW DATABASES" 73 | 74 | make test/test 75 | valgrind --leak-check=full --show-reachable=yes --track-origins=yes \ 76 | ./test/test 77 | fi 78 | 79 | # ensure fuzz harness compiles and doesn't bitrot 80 | echo "Testing fuzz harness" 81 | make fuzz 82 | 83 | # ruby 84 | echo "Testing ruby extension" 85 | make clean 86 | contrib/ruby/script/cibuild 87 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | SOURCES = $(shell find src -name '*.c') 2 | TEST_SOURCES = $(shell find test -name '*.c') 3 | OBJS = $(SOURCES:.c=.o) 4 | FUZZ_OBJ = test/fuzz.o 5 | 6 | CFLAGS ?= -O1 -ggdb3 7 | CFLAGS += -U_FORTIFY_SOURCE -D_FORTIFY_SOURCE=2 -fstack-protector 8 | CFLAGS += -Wall -Werror -Wextra -pedantic -Wsign-conversion -Wno-missing-field-initializers -std=gnu99 -iquote inc 9 | 10 | OPENSSL = -lcrypto -lssl 11 | EXAMPLES = example/trilogy_query 12 | 13 | UNAME_S := $(shell uname -s) 14 | 15 | ifeq ($(UNAME_S), Darwin) 16 | CFLAGS += "-I$(shell brew --prefix openssl@1.1)/include" 17 | LDFLAGS += "-L$(shell brew --prefix openssl@1.1)/lib" 18 | else 19 | CFLAGS += -fPIC 20 | LDFLAGS += -pie -Wl,-z,relro,-z,now 21 | endif 22 | 23 | .PHONY: all 24 | all: libtrilogy.a examples 25 | 26 | .PHONY: examples 27 | examples: $(EXAMPLES) 28 | 29 | example/%: example/%.c libtrilogy.a 30 | $(CC) -o $@ $(CFLAGS) -pedantic $(LDFLAGS) $^ $(OPENSSL) 31 | 32 | libtrilogy.a: $(OBJS) 33 | $(AR) r $@ $^ 34 | 35 | %.o: %.c inc/trilogy/*.h 36 | $(CC) -o $@ $(CFLAGS) -pedantic -c $< 37 | 38 | debug: CFLAGS += -O0 -ggdb3 39 | debug: all 40 | 41 | .PHONY: analyze 42 | analyze: 43 | $(CC) $(CFLAGS) -pedantic --analyze --analyzer-output text $(SOURCES) 44 | 45 | .PHONY: fuzz 46 | fuzz: $(FUZZ_OBJ) 47 | 48 | .PHONY: clean 49 | clean: 50 | rm -f libtrilogy.a $(EXAMPLES) $(OBJS) $(FUZZ_OBJ) 51 | rm -f test/test $(TEST_OBJS) 52 | 53 | test/test: $(TEST_SOURCES) libtrilogy.a 54 | $(CC) $(CFLAGS) $(LDFLAGS) -Wno-strict-prototypes -o test/test $(TEST_SOURCES) -L. -ltrilogy $(OPENSSL) 55 | 56 | update_greatest: 57 | curl -o test/greatest.h https://raw.githubusercontent.com/silentbicycle/greatest/master/greatest.h 58 | 59 | .PHONY: test 60 | test: test/test 61 | test/test 62 | -------------------------------------------------------------------------------- /test/client/connect_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../test.h" 8 | 9 | #include "trilogy/blocking.h" 10 | #include "trilogy/client.h" 11 | #include "trilogy/error.h" 12 | 13 | TEST test_connect_send() 14 | { 15 | trilogy_conn_t conn; 16 | 17 | int err = trilogy_init(&conn); 18 | ASSERT_OK(err); 19 | 20 | err = trilogy_connect_send(&conn, get_connopt()); 21 | ASSERT_OK(err); 22 | 23 | trilogy_free(&conn); 24 | PASS(); 25 | } 26 | 27 | TEST test_connect_recv() 28 | { 29 | trilogy_conn_t conn; 30 | 31 | int err = trilogy_init(&conn); 32 | ASSERT_OK(err); 33 | 34 | err = trilogy_connect_send(&conn, get_connopt()); 35 | ASSERT_OK(err); 36 | 37 | trilogy_handshake_t handshake; 38 | 39 | err = trilogy_connect_recv(&conn, &handshake); 40 | while (err == TRILOGY_AGAIN) { 41 | err = wait_readable(&conn); 42 | ASSERT_OK(err); 43 | 44 | err = trilogy_connect_recv(&conn, &handshake); 45 | } 46 | 47 | ASSERT_OK(err); 48 | 49 | trilogy_free(&conn); 50 | PASS(); 51 | } 52 | 53 | TEST test_connect_recv_after_close() 54 | { 55 | trilogy_conn_t conn; 56 | 57 | int err = trilogy_init(&conn); 58 | ASSERT_OK(err); 59 | 60 | err = trilogy_connect_send(&conn, get_connopt()); 61 | ASSERT_OK(err); 62 | 63 | close_socket(&conn); 64 | 65 | trilogy_handshake_t handshake; 66 | 67 | err = trilogy_connect_recv(&conn, &handshake); 68 | ASSERT_ERR(TRILOGY_SYSERR, err); 69 | 70 | trilogy_free(&conn); 71 | PASS(); 72 | } 73 | 74 | int client_connect_test() 75 | { 76 | RUN_TEST(test_connect_send); 77 | RUN_TEST(test_connect_recv); 78 | RUN_TEST(test_connect_recv_after_close); 79 | 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /test/protocol/parsing/column_packet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | static uint8_t column_packet[] = {0x03, 0x64, 0x65, 0x66, 0x00, 0x00, 0x00, 0x11, 0x40, 0x40, 0x76, 0x65, 0x72, 11 | 0x73, 0x69, 0x6f, 0x6e, 0x5f, 0x63, 0x6f, 0x6d, 0x6d, 0x65, 0x6e, 0x74, 0x00, 12 | 0x0c, 0x21, 0x00, 0x18, 0x00, 0x00, 0x00, 0xfd, 0x00, 0x00, 0x1f, 0x00, 0x00}; 13 | 14 | TEST test_parse_column_packet() 15 | { 16 | trilogy_column_packet_t packet; 17 | 18 | bool from_field_list = false; 19 | 20 | int err = trilogy_parse_column_packet(column_packet, sizeof(column_packet), from_field_list, &packet); 21 | ASSERT_OK(err); 22 | ASSERT_MEM_EQ(packet.catalog, "def", packet.catalog_len); 23 | ASSERT_EQ(0, packet.schema_len); 24 | ASSERT_EQ(0, packet.table_len); 25 | ASSERT_EQ(0, packet.original_table_len); 26 | ASSERT_MEM_EQ(packet.name, "@@version_comment", packet.name_len); 27 | ASSERT_EQ(0, packet.original_name_len); 28 | ASSERT_EQ(TRILOGY_CHARSET_UTF8_GENERAL_CI, packet.charset); 29 | ASSERT_EQ(24, packet.len); 30 | ASSERT_EQ(TRILOGY_TYPE_VAR_STRING, packet.type); 31 | ASSERT_EQ(0x0, packet.flags); 32 | ASSERT_EQ(31, packet.decimals); 33 | ASSERT_EQ(0, packet.default_value_len); 34 | 35 | PASS(); 36 | } 37 | 38 | TEST test_parse_column_packet_truncated() 39 | { 40 | trilogy_column_packet_t packet; 41 | 42 | bool from_field_list = false; 43 | 44 | int err = trilogy_parse_column_packet(column_packet, sizeof(column_packet) - 10, from_field_list, &packet); 45 | ASSERT_ERR(TRILOGY_TRUNCATED_PACKET, err); 46 | 47 | PASS(); 48 | } 49 | 50 | int parse_column_packet_test() 51 | { 52 | RUN_TEST(test_parse_column_packet); 53 | RUN_TEST(test_parse_column_packet_truncated); 54 | 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /test/socket_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include "test.h" 7 | 8 | #include "trilogy/client.h" 9 | #include "trilogy/error.h" 10 | 11 | #define do_connect(CONN) \ 12 | do { \ 13 | int err = trilogy_init(CONN); \ 14 | ASSERT_OK(err); \ 15 | err = trilogy_connect(CONN, get_connopt()); \ 16 | ASSERT_OK(err); \ 17 | } while (0) 18 | 19 | TEST test_check_connected() 20 | { 21 | trilogy_conn_t conn; 22 | 23 | do_connect(&conn); 24 | 25 | int err = trilogy_sock_check(conn.socket); 26 | ASSERT_OK(err); 27 | 28 | trilogy_free(&conn); 29 | PASS(); 30 | } 31 | 32 | 33 | TEST test_check_disconnected() 34 | { 35 | trilogy_conn_t conn; 36 | 37 | do_connect(&conn); 38 | shutdown(trilogy_sock_fd(conn.socket), SHUT_RD); 39 | 40 | int err = trilogy_sock_check(conn.socket); 41 | ASSERT_ERR(TRILOGY_CLOSED_CONNECTION, err); 42 | 43 | trilogy_free(&conn); 44 | PASS(); 45 | } 46 | 47 | TEST test_check_closed() 48 | { 49 | trilogy_conn_t conn; 50 | 51 | do_connect(&conn); 52 | close_socket(&conn); 53 | 54 | int err = trilogy_sock_check(conn.socket); 55 | ASSERT_ERR(TRILOGY_SYSERR, err); 56 | 57 | trilogy_free(&conn); 58 | PASS(); 59 | } 60 | 61 | int socket_test() 62 | { 63 | RUN_TEST(test_check_connected); 64 | RUN_TEST(test_check_disconnected); 65 | RUN_TEST(test_check_closed); 66 | 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /src/buffer.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "trilogy/buffer.h" 6 | #include "trilogy/error.h" 7 | 8 | int trilogy_buffer_init(trilogy_buffer_t *buffer, size_t initial_capacity) 9 | { 10 | buffer->len = 0; 11 | buffer->cap = initial_capacity; 12 | buffer->buff = malloc(initial_capacity); 13 | 14 | if (buffer->buff == NULL) { 15 | return TRILOGY_SYSERR; 16 | } 17 | 18 | return TRILOGY_OK; 19 | } 20 | 21 | #define EXPAND_MULTIPLIER 2 22 | 23 | int trilogy_buffer_expand(trilogy_buffer_t *buffer, size_t needed) 24 | { 25 | // expand buffer if necessary 26 | if (buffer->len + needed > buffer->cap) { 27 | if (buffer->buff == NULL) 28 | return TRILOGY_MEM_ERROR; 29 | 30 | size_t new_cap = buffer->cap; 31 | 32 | while (buffer->len + needed > new_cap) { 33 | // would this next step cause an overflow? 34 | if (new_cap > SIZE_MAX / EXPAND_MULTIPLIER) 35 | return TRILOGY_TYPE_OVERFLOW; 36 | 37 | new_cap *= EXPAND_MULTIPLIER; 38 | } 39 | 40 | uint8_t *new_buff = realloc(buffer->buff, new_cap); 41 | if (new_buff == NULL) 42 | return TRILOGY_SYSERR; 43 | 44 | buffer->buff = new_buff; 45 | buffer->cap = new_cap; 46 | } 47 | 48 | return TRILOGY_OK; 49 | } 50 | 51 | int trilogy_buffer_putc(trilogy_buffer_t *buffer, uint8_t c) 52 | { 53 | int rc = trilogy_buffer_expand(buffer, 1); 54 | 55 | if (rc) { 56 | return rc; 57 | } 58 | 59 | buffer->buff[buffer->len++] = c; 60 | 61 | return TRILOGY_OK; 62 | } 63 | 64 | int trilogy_buffer_write(trilogy_buffer_t *buffer, const uint8_t *ptr, size_t len) 65 | { 66 | int rc = trilogy_buffer_expand(buffer, len); 67 | if (rc) { 68 | return rc; 69 | } 70 | 71 | memcpy(buffer->buff + buffer->len, ptr, len); 72 | buffer->len += len; 73 | 74 | return TRILOGY_OK; 75 | } 76 | 77 | void trilogy_buffer_free(trilogy_buffer_t *buffer) 78 | { 79 | if (buffer->buff) { 80 | free(buffer->buff); 81 | buffer->buff = NULL; 82 | buffer->len = buffer->cap = 0; 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /test/buffer_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "test.h" 6 | 7 | #include "trilogy/buffer.h" 8 | #include "trilogy/error.h" 9 | 10 | TEST test_buffer_expand() 11 | { 12 | trilogy_buffer_t buff; 13 | 14 | int err = trilogy_buffer_init(&buff, 1); 15 | ASSERT_OK(err); 16 | ASSERT_EQ(0, buff.len); 17 | ASSERT_EQ(1, buff.cap); 18 | 19 | err = trilogy_buffer_expand(&buff, 1); 20 | ASSERT_OK(err); 21 | ASSERT_EQ(0, buff.len); 22 | ASSERT_EQ(1, buff.cap); 23 | 24 | err = trilogy_buffer_expand(&buff, 2); 25 | ASSERT_OK(err); 26 | ASSERT_EQ(0, buff.len); 27 | ASSERT_EQ(2, buff.cap); 28 | 29 | trilogy_buffer_free(&buff); 30 | 31 | PASS(); 32 | } 33 | 34 | TEST test_buffer_putc() 35 | { 36 | trilogy_buffer_t buff; 37 | 38 | int err = trilogy_buffer_init(&buff, 1); 39 | ASSERT_OK(err); 40 | ASSERT_EQ(0, buff.len); 41 | ASSERT_EQ(1, buff.cap); 42 | 43 | err = trilogy_buffer_putc(&buff, 'a'); 44 | ASSERT_OK(err); 45 | ASSERT_EQ(1, buff.len); 46 | ASSERT_EQ(1, buff.cap); 47 | 48 | err = trilogy_buffer_putc(&buff, 'b'); 49 | ASSERT_OK(err); 50 | ASSERT_EQ(2, buff.len); 51 | ASSERT_EQ(2, buff.cap); 52 | ASSERT_MEM_EQ(buff.buff, "ab", 2); 53 | 54 | trilogy_buffer_free(&buff); 55 | 56 | PASS(); 57 | } 58 | 59 | TEST test_buffer_puts() 60 | { 61 | trilogy_buffer_t buff; 62 | 63 | int err = trilogy_buffer_init(&buff, 1); 64 | ASSERT_OK(err); 65 | ASSERT_EQ(0, buff.len); 66 | ASSERT_EQ(1, buff.cap); 67 | 68 | err = trilogy_buffer_write(&buff, (uint8_t *)"aaaaBBBB", 4); 69 | ASSERT_OK(err); 70 | ASSERT_EQ(4, buff.len); 71 | ASSERT_EQ(4, buff.cap); 72 | 73 | err = trilogy_buffer_write(&buff, (uint8_t *)"ccccccc", 8); 74 | ASSERT_OK(err); 75 | ASSERT_EQ(12, buff.len); 76 | ASSERT_EQ(16, buff.cap); 77 | ASSERT_MEM_EQ(buff.buff, (uint8_t *)"aaaaccccccc", 12); 78 | 79 | trilogy_buffer_free(&buff); 80 | 81 | PASS(); 82 | } 83 | 84 | int buffer_test() 85 | { 86 | RUN_TEST(test_buffer_expand); 87 | RUN_TEST(test_buffer_putc); 88 | RUN_TEST(test_buffer_puts); 89 | 90 | return 0; 91 | } 92 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Contributing 2 | 3 | [fork]: https://github.com/trilogy-libraries/trilogy/fork 4 | [pr]: https://github.com/trilogy-libraries/trilogy/compare 5 | 6 | Hi there! We're thrilled that you'd like to contribute to this project. Your help is essential for keeping it great. 7 | 8 | Contributions to this project are [released](https://help.github.com/articles/github-terms-of-service/#6-contributions-under-repository-license) to the public under the [project's open source license](LICENSE.md). 9 | 10 | Please note that this project is released with a [Contributor Code of Conduct](CODE_OF_CONDUCT.md). By participating in this project you agree to abide by its terms. 11 | 12 | ## Submitting a pull request 13 | 14 | 0. [Fork][] and clone the repository 15 | 0. Build it and make sure the tests pass on your machine: `script/cibuild`. It will run both trilogy and ruby bindings suites in docker environment. 16 | 17 | To shorten the development loop you can: 18 | 19 | a) run trilogy tests locally with: `make test` 20 | b) run ruby binding tests with `cd contrib/ruby`, `bundle exec rake test`. It's possible to run a test single example by passing a `TESTOPTS` environment variable like so: `TESTOPTS=-n/test_packet_size_greater_than_trilogy_max_packet_len/`. 21 | 22 | 0. Create a new branch: `git checkout -b my-branch-name` 23 | 0. Make your change, add tests, and make sure the tests still pass 24 | 0. Push to your fork and [submit a pull request][pr] 25 | 0. Pat yourself on the back and wait for your pull request to be reviewed and merged. 26 | 27 | Here are a few things you can do that will increase the likelihood of your pull request being accepted: 28 | 29 | - Follow the existing style of the code you're changing. 30 | - Write tests. 31 | - Keep your change as focused as possible. If there are multiple changes you would like to make that are not dependent upon each other, consider submitting them as separate pull requests. 32 | - Write a [good commit message](http://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html). 33 | 34 | ## Resources 35 | 36 | - [Contributing to Open Source on GitHub](https://guides.github.com/activities/contributing-to-open-source/) 37 | - [Using Pull Requests](https://help.github.com/articles/using-pull-requests/) 38 | - [GitHub Help](https://help.github.com) 39 | -------------------------------------------------------------------------------- /.github/workflows/macos.yml: -------------------------------------------------------------------------------- 1 | permissions: 2 | contents: read 3 | 4 | name: macOS 5 | on: 6 | push: 7 | branches: 8 | - main 9 | tags: 10 | - v* 11 | pull_request: 12 | 13 | jobs: 14 | test: 15 | name: Test 16 | runs-on: macos-latest 17 | strategy: 18 | matrix: 19 | mysql: ["8.0", "8.4"] 20 | steps: 21 | - uses: actions/checkout@v6 22 | - name: Setup MySQL 23 | run: | 24 | brew install mysql@${{ matrix.mysql }} 25 | (unset CI; brew postinstall mysql@${{ matrix.mysql }}) 26 | brew services start mysql@${{ matrix.mysql }} 27 | sleep 5 28 | $(brew --prefix mysql@${{ matrix.mysql }})/bin/mysql -uroot -e 'CREATE DATABASE test' 29 | - name: Build 30 | run: CFLAGS="-I$(brew --prefix openssl@1.1)/include" LDFLAGS="-L$(brew --prefix openssl@1.1)/lib" make all test/test 31 | - name: test 32 | run: test/test 33 | test-ruby: 34 | name: Test Ruby 35 | runs-on: macos-latest 36 | strategy: 37 | matrix: 38 | mysql: ["8.0"] 39 | ruby: ["3.0", "3.1", "3.2", "3.3", "3.4"] 40 | steps: 41 | - uses: actions/checkout@v6 42 | - uses: ruby/setup-ruby@v1 43 | with: 44 | ruby-version: ${{ matrix.ruby }} 45 | - name: Setup MySQL 46 | env: 47 | MYSQL_VERSION: ${{ matrix.mysql }} 48 | run: | 49 | brew install mysql@${{ matrix.mysql }} 50 | (unset CI; brew postinstall mysql@${{ matrix.mysql }}) 51 | brew services start mysql@${{ matrix.mysql }} 52 | sleep 5 53 | $(brew --prefix mysql@${{ matrix.mysql }})/bin/mysql -uroot -e 'CREATE DATABASE test' 54 | [[ "$MYSQL_VERSION" == "8.0" ]] && $(brew --prefix mysql@${{ matrix.mysql }})/bin/mysql -uroot < test/mysql/docker-entrypoint-initdb.d/caching_sha2_password_user.sql 55 | $(brew --prefix mysql@${{ matrix.mysql }})/bin/mysql -uroot < test/mysql/docker-entrypoint-initdb.d/native_password_user.sql 56 | $(brew --prefix mysql@${{ matrix.mysql }})/bin/mysql -uroot < test/mysql/docker-entrypoint-initdb.d/x509_user.sql 57 | $(brew --prefix mysql@${{ matrix.mysql }})/bin/mysql -uroot < test/mysql/docker-entrypoint-initdb.d/cleartext_user.sql 58 | - name: Install dependencies 59 | run: | 60 | cd contrib/ruby 61 | bundle --without benchmark 62 | - name: Run tests 63 | run: | 64 | cd contrib/ruby 65 | bundle exec rake 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Trilogy 2 | 3 | Trilogy is a client library for MySQL-compatible database servers, designed for performance, flexibility, and ease of embedding. 4 | 5 | It's currently in production use on github.com. 6 | 7 | ## Features 8 | 9 | * Supports the most frequently used parts of the text protocol 10 | * Handshake 11 | * Password authentication 12 | * Query, ping, and quit commands 13 | 14 | * Support prepared statements (binary protocol) 15 | 16 | * Low-level protocol API completely decoupled from IO 17 | 18 | * Non-blocking client API wrapping the protocol API 19 | 20 | * Blocking client API wrapping the non-blocking API 21 | 22 | * No dependencies outside of POSIX, the C standard library & OpenSSL 23 | 24 | * Minimal dynamic allocation 25 | 26 | * MIT licensed 27 | 28 | ## Limitations 29 | 30 | * Only supports the parts of the text protocol that are in common use. 31 | 32 | * No support for `LOAD DATA INFILE` on local files 33 | 34 | * `trilogy_escape` assumes an ASCII-compatible connection encoding 35 | 36 | ## Building 37 | 38 | `make` - that's it. This will build a static `libtrilogy.a` 39 | 40 | Trilogy should build out of the box on most UNIX systems which have OpenSSL installed. 41 | 42 | ## API Documentation 43 | 44 | Documentation for Trilogy's various APIs can be found in these header files: 45 | 46 | * `blocking.h` 47 | 48 | The blocking client API. These are simply a set of convenient wrapper functions around the non-blocking client API in `client.h` 49 | 50 | * `client.h` 51 | 52 | The non-blocking client API. Every command is split into a `_send` and `_recv` function allowing callers to wait for IO readiness externally to Trilogy 53 | 54 | * `builder.h` 55 | 56 | MySQL-compatible packet builder API 57 | 58 | * `charset.h` 59 | 60 | Character set and encoding tables 61 | 62 | * `error.h` 63 | 64 | Error table. Every Trilogy function returning an `int` uses the error codes defined here 65 | 66 | * `packet_parser.h` 67 | 68 | Streaming packet frame parser 69 | 70 | * `protocol.h` 71 | 72 | Low-level protocol API. Provides IO-decoupled functions to parse and build packets 73 | 74 | * `reader.h` 75 | 76 | Bounds-checked packet reader API 77 | 78 | ## Bindings 79 | 80 | We maintain a [Ruby binding](contrib/ruby) in this repository. This is currently stable and production-ready. 81 | 82 | ## License 83 | 84 | Trilogy is released under the [MIT license](LICENSE). 85 | -------------------------------------------------------------------------------- /script/cibuild: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | 5 | output_fold() { 6 | # Exit early if no label provided 7 | if [ -z "$1" ]; then 8 | echo "output_fold(): requires a label argument." 9 | return 10 | fi 11 | 12 | exit_value=0 # exit_value is used to record exit status of the given command 13 | label=$1 # human-readable label describing what's being folded up 14 | shift 1 # having retrieved the output_fold()-specific arguments, strip them off $@ 15 | 16 | # Only echo the tags when in CI_MODE 17 | if [ "$CI_MODE" ]; then 18 | echo "::group::${label}" 19 | fi 20 | 21 | # run the remaining arguments. If the command exits non-0, the `||` will 22 | # prevent the `-e` flag from seeing the failure exit code, and we'll see 23 | # the second echo execute 24 | "$@" || exit_value=$? 25 | 26 | # Only echo the tags when in CI_MODE 27 | if [ "$CI_MODE" ]; then 28 | echo "::endgroup::" 29 | fi 30 | 31 | # preserve the exit code from the subcommand. 32 | return $exit_value 33 | } 34 | 35 | function cleanup() { 36 | echo 37 | echo "::group::Shutting down services..." 38 | docker compose logs db 39 | docker compose down --volumes 40 | echo "::endgroup::" 41 | } 42 | 43 | trap cleanup EXIT 44 | 45 | export CI_MODE=true 46 | 47 | if [ -z "$MYSQL_VERSION" ]; then export MYSQL_VERSION=8.0 ; fi 48 | if [ -z "$DISTRIBUTION" ]; then export DISTRIBUTION=debian:bookworm ; fi 49 | if [ -z "$RUBY_VERSION" ]; then export RUBY_VERSION=3.4 ; fi 50 | 51 | DISTRIBUTION_SLUG="$(echo "$DISTRIBUTION" | awk '{ gsub(":", "_") ; print $0 }')" 52 | export DISTRIBUTION_SLUG 53 | 54 | # Prepare the shared directory where the certificates will be stored. We need to create 55 | # and chmod this directory on the host so that the permissions are persisted when the 56 | # the directory is mounted in the containers. Since the mysql container runs as a non-root 57 | # user, we need to ensure that the directory is writable by all users. 58 | mkdir tmp/mysql-certs 59 | chmod 777 tmp/mysql-certs 60 | 61 | docker compose rm --stop --force --volumes 62 | output_fold "Pull cache image..." docker compose pull app db || true 63 | output_fold "Bootstrapping container..." docker compose build --build-arg MYSQL_VERSION=${MYSQL_VERSION} --build-arg DISTRIBUTION=${DISTRIBUTION} --build-arg RUBY_VERSION=${RUBY_VERSION} 64 | output_fold "Running tests..." docker compose run --rm app 65 | output_fold "Pushing cache image..." docker compose push app db || true # Don't fail if push fails 66 | -------------------------------------------------------------------------------- /test/fuzz.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "trilogy.h" 10 | 11 | // TODO@vmg: use a custom socket for fuzzing 12 | 13 | #if 0 14 | static void read_columns(trilogy_conn_t *conn, uint64_t column_count) { 15 | for (uint64_t i = 0; i < column_count; i++) { 16 | trilogy_column_packet_t column; 17 | if (trilogy_read_full_column(conn, &column) != TRILOGY_OK) 18 | return; 19 | } 20 | 21 | trilogy_value_t *values = calloc(column_count, sizeof(trilogy_value_t)); 22 | if (values) { 23 | while (trilogy_read_full_row(conn, values) == TRILOGY_OK) { 24 | } 25 | 26 | free(values); 27 | } 28 | } 29 | 30 | #define SQL_LEN (32) 31 | 32 | int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) { 33 | /* Use the first SQL_LEN bytes as a sql statement, then 34 | * the remainder is used as data from the server */ 35 | if (size < SQL_LEN) 36 | return 0; 37 | 38 | const char *sql = (const char *)data; 39 | data += SQL_LEN; 40 | size -= SQL_LEN; 41 | 42 | /* "Store" the remainder of the data in a socketpair, 43 | * which trilogy can then read from */ 44 | int socket_fds[2]; 45 | int res = socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds); 46 | if (res < 0) 47 | return 0; 48 | ssize_t send_res = send(socket_fds[1], data, size, 0); 49 | if (send_res != (ssize_t)size) 50 | return 0; 51 | res = shutdown(socket_fds[1], SHUT_WR); 52 | if (res != 0) 53 | return 0; 54 | 55 | trilogy_conn_t conn; 56 | trilogy_init(&conn); 57 | 58 | const char *user = "username"; 59 | const char *password = "password"; 60 | const char *db = "database"; 61 | 62 | if (trilogy_connect_fd(&conn, socket_fds[0], user, password, strlen(password), 0) == TRILOGY_OK) { 63 | if (trilogy_ping(&conn) == TRILOGY_OK) { 64 | if (trilogy_change_db(&conn, db, strlen(db)) == TRILOGY_OK) { 65 | const char *escaped_sql = NULL; 66 | size_t escaped_sql_len = 0; 67 | 68 | if (trilogy_escape(&conn, sql, SQL_LEN, &escaped_sql, &escaped_sql_len) == TRILOGY_OK) { 69 | uint64_t column_count = 0; 70 | 71 | while (trilogy_query(&conn, sql, strlen(sql), &column_count) == TRILOGY_HAVE_RESULTS) { 72 | if (column_count) { 73 | read_columns(&conn, column_count); 74 | } 75 | } 76 | } 77 | } 78 | } 79 | trilogy_close(&conn); 80 | } 81 | 82 | trilogy_free(&conn); 83 | close(socket_fds[0]); 84 | close(socket_fds[1]); 85 | return 0; 86 | } 87 | #endif 88 | -------------------------------------------------------------------------------- /inc/trilogy/vendor/openssl_hostname_validation.h: -------------------------------------------------------------------------------- 1 | #ifndef HEADER_OPENSSL_HOSTNAME_VALIDATION_H 2 | #define HEADER_OPENSSL_HOSTNAME_VALIDATION_H 3 | /* Obtained from: https://github.com/iSECPartners/ssl-conservatory */ 4 | 5 | /* 6 | Copyright (C) 2012, iSEC Partners. 7 | Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | this software and associated documentation files (the "Software"), to deal in 9 | the Software without restriction, including without limitation the rights to 10 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 11 | of the Software, and to permit persons to whom the Software is furnished to do 12 | so, subject to the following conditions: 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | */ 23 | 24 | /* 25 | * Helper functions to perform basic hostname validation using OpenSSL. 26 | * 27 | * Please read "everything-you-wanted-to-know-about-openssl.pdf" before 28 | * attempting to use this code. This whitepaper describes how the code works, 29 | * how it should be used, and what its limitations are. 30 | * 31 | * Author: Alban Diquet 32 | * License: See LICENSE 33 | * 34 | */ 35 | 36 | typedef enum { MatchFound, MatchNotFound, NoSANPresent, MalformedCertificate, Error } HostnameValidationResult; 37 | 38 | /** 39 | * Validates the server's identity by looking for the expected hostname in the 40 | * server's certificate. As described in RFC 6125, it first tries to find a 41 | * match in the Subject Alternative Name extension. If the extension is not 42 | * present in the certificate, it checks the Common Name instead. 43 | * 44 | * Returns MatchFound if a match was found. 45 | * Returns MatchNotFound if no matches were found. 46 | * Returns MalformedCertificate if any of the hostnames had a NUL character 47 | * embedded in it. Returns Error if there was an error. 48 | */ 49 | HostnameValidationResult validate_hostname(const char *hostname, const X509 *server_cert); 50 | 51 | #endif /* HEADER_OPENSSL_HOSTNAME_VALIDATION_H */ 52 | -------------------------------------------------------------------------------- /test/protocol/parsing/error_packet_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | static uint8_t error_packet[] = { 11 | 0xff, 0x28, 0x04, 0x23, 0x34, 0x32, 0x30, 0x30, 0x30, 0x59, 0x6f, 0x75, 0x20, 0x68, 0x61, 0x76, 0x65, 0x20, 12 | 0x61, 0x6e, 0x20, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x20, 0x69, 0x6e, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x53, 13 | 0x51, 0x4c, 0x20, 0x73, 0x79, 0x6e, 0x74, 0x61, 0x78, 0x3b, 0x20, 0x63, 0x68, 0x65, 0x63, 0x6b, 0x20, 0x74, 14 | 0x68, 0x65, 0x20, 0x6d, 0x61, 0x6e, 0x75, 0x61, 0x6c, 0x20, 0x74, 0x68, 0x61, 0x74, 0x20, 0x63, 0x6f, 0x72, 15 | 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x64, 0x73, 0x20, 0x74, 0x6f, 0x20, 0x79, 0x6f, 0x75, 0x72, 0x20, 0x4d, 16 | 0x79, 0x53, 0x51, 0x4c, 0x20, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x20, 0x76, 0x65, 0x72, 0x73, 0x69, 0x6f, 17 | 0x6e, 0x20, 0x66, 0x6f, 0x72, 0x20, 0x74, 0x68, 0x65, 0x20, 0x72, 0x69, 0x67, 0x68, 0x74, 0x20, 0x73, 0x79, 18 | 0x6e, 0x74, 0x61, 0x78, 0x20, 0x74, 0x6f, 0x20, 0x75, 0x73, 0x65, 0x20, 0x6e, 0x65, 0x61, 0x72, 0x20, 0x27, 19 | 0x53, 0x45, 0x4c, 0x43, 0x54, 0x20, 0x31, 0x27, 0x20, 0x61, 0x74, 0x20, 0x6c, 0x69, 0x6e, 0x65, 0x20, 0x31}; 20 | 21 | TEST test_parse_error_packet() 22 | { 23 | trilogy_err_packet_t packet; 24 | 25 | uint32_t flags = TRILOGY_CAPABILITIES_PROTOCOL_41; 26 | 27 | int err = trilogy_parse_err_packet(error_packet, sizeof(error_packet), flags, &packet); 28 | ASSERT_OK(err); 29 | 30 | ASSERT_EQ(1064, packet.error_code); 31 | ASSERT_EQ(0x23, packet.sql_state_marker[0]); 32 | 33 | const char expected_state[] = "42000"; 34 | ASSERT_MEM_EQ(packet.sql_state, expected_state, sizeof(packet.sql_state)); 35 | 36 | const char expected_message[] = "You have an error in your SQL syntax; check the manual that " 37 | "corresponds to your MySQL server version for the right syntax to use " 38 | "near 'SELCT 1' at line 1"; 39 | ASSERT_MEM_EQ(packet.error_message, expected_message, packet.error_message_len); 40 | 41 | PASS(); 42 | } 43 | 44 | TEST test_parse_error_packet_truncated() 45 | { 46 | trilogy_err_packet_t packet; 47 | 48 | uint32_t flags = TRILOGY_CAPABILITIES_PROTOCOL_41; 49 | 50 | int err = trilogy_parse_err_packet(error_packet, sizeof(error_packet) - 154, flags, &packet); 51 | ASSERT_ERR(TRILOGY_TRUNCATED_PACKET, err); 52 | 53 | PASS(); 54 | } 55 | 56 | int parse_error_packet_test() 57 | { 58 | RUN_TEST(test_parse_error_packet); 59 | RUN_TEST(test_parse_error_packet_truncated); 60 | 61 | return 0; 62 | } 63 | -------------------------------------------------------------------------------- /inc/trilogy/buffer.h: -------------------------------------------------------------------------------- 1 | #ifndef TRILOGY_BUFFER_H 2 | #define TRILOGY_BUFFER_H 3 | 4 | #include 5 | #include 6 | 7 | /* trilogy_buffer_t - A convenience type used for wrapping a reusable chunk of 8 | * memory. 9 | */ 10 | typedef struct { 11 | size_t len; 12 | size_t cap; 13 | uint8_t *buff; 14 | } trilogy_buffer_t; 15 | 16 | /* trilogy_buffer_init - Initialize an trilogy_buffer_t and pre-allocate 17 | * `initial_capacity` bytes. 18 | * 19 | * buffer - A pointer to an allocated, uninitialized trilogy_buffer_t. 20 | * initial_capacity - The initial capacity for the buffer. 21 | * 22 | * Return values: 23 | * TRILOGY_OK - The buffer was initialized. 24 | * TRILOGY_SYSERR - A system error occurred, check errno. 25 | */ 26 | int trilogy_buffer_init(trilogy_buffer_t *buffer, size_t initial_capacity); 27 | 28 | /* trilogy_buffer_expand - Make sure there is at least `needed` bytes available in 29 | * the underlying buffer, resizing it to be larger if necessary. 30 | * 31 | * buffer - A pre-initialized trilogy_buffer_t pointer. 32 | * needed - The amount of space requested to be available in the buffer after 33 | * after this call returns. 34 | * 35 | * Return values: 36 | * TRILOGY_OK - The buffer is guaranteed to have at least `needed` bytes of 37 | * space available. 38 | * TRILOGY_SYSERR - A system error occurred, check errno. 39 | * TRILOGY_TYPE_OVERFLOW - The amount of buffer spaced needed is larger than the 40 | * what can be represented by `size_t`. 41 | */ 42 | int trilogy_buffer_expand(trilogy_buffer_t *buffer, size_t needed); 43 | 44 | /* trilogy_buffer_putc - Appends a byte to the buffer, resizing the underlying 45 | * allocation if necessary. 46 | * 47 | * buffer - A pointer to a pre-initialized trilogy_buffer_t. 48 | * c - The byte to append to the buffer. 49 | * 50 | * Return values: 51 | * TRILOGY_OK - The character was appended to the buffer 52 | * TRILOGY_SYSERR - A system error occurred, check errno. 53 | */ 54 | int trilogy_buffer_putc(trilogy_buffer_t *buffer, uint8_t c); 55 | 56 | /* trilogy_buffer_write - Appends multiple bytes to the buffer, resizing the underlying 57 | * allocation if necessary. 58 | * 59 | * buffer - A pointer to a pre-initialized trilogy_buffer_t. 60 | * ptr - The pointer to the byte array. 61 | * len - How many bytes to append. 62 | * 63 | * Return values: 64 | * TRILOGY_OK - The character was appended to the buffer 65 | * TRILOGY_SYSERR - A system error occurred, check errno. 66 | */ 67 | int trilogy_buffer_write(trilogy_buffer_t *buffer, const uint8_t *ptr, size_t len); 68 | 69 | /* trilogy_buffer_free - Free an trilogy_buffer_t's underlying storage. The buffer 70 | * must be re-initialized with trilogy_buffer_init if it is to be reused. Any 71 | * operations performed on an unintialized or freed buffer are undefined. 72 | * 73 | * buffer - An initialized trilogy_buffer_t. 74 | */ 75 | void trilogy_buffer_free(trilogy_buffer_t *buffer); 76 | 77 | #endif 78 | -------------------------------------------------------------------------------- /contrib/ruby/README.md: -------------------------------------------------------------------------------- 1 | # trilogy 2 | 3 | Ruby bindings to the Trilogy client library 4 | 5 | ## Installation 6 | 7 | Add this line to your application's Gemfile: 8 | 9 | ``` ruby 10 | gem 'trilogy' 11 | ``` 12 | 13 | And then execute: 14 | 15 | ``` 16 | $ bundle 17 | ``` 18 | 19 | Or install it yourself as: 20 | 21 | ``` 22 | $ gem install trilogy 23 | ``` 24 | 25 | ## Usage 26 | 27 | ``` ruby 28 | client = Trilogy.new(host: "127.0.0.1", port: 3306, username: "root", read_timeout: 2) 29 | if client.ping 30 | client.change_db "mydb" 31 | 32 | result = client.query("SELECT id, created_at FROM users LIMIT 10") 33 | result.each_hash do |user| 34 | p user 35 | end 36 | end 37 | ``` 38 | 39 | ### Processing multiple result sets 40 | 41 | In order to send and receive multiple result sets, pass the `multi_statement` option when connecting. 42 | `Trilogy#more_results_exist?` will return true if more results exist, false if no more results exist, or raise 43 | an error if the respective query errored. `Trilogy#next_result` will retrieve the next result set, or return nil 44 | if no more results exist. 45 | 46 | ``` ruby 47 | client = Trilogy.new(host: "127.0.0.1", port: 3306, username: "root", read_timeout: 2, multi_statement: true) 48 | 49 | results = [] 50 | results << client.query("SELECT name FROM users WHERE id = 1; SELECT name FROM users WHERE id = 2") 51 | results << client.next_result while client.more_results_exist? 52 | ``` 53 | 54 | ## Building 55 | You should use the rake commands to build/install/release the gem 56 | For instance: 57 | ```shell 58 | bundle exec rake build 59 | ``` 60 | 61 | ## Contributing 62 | 63 | The official Ruby bindings are inside of the canonical trilogy repository itself. 64 | 65 | 1. Fork it ( https://github.com/trilogy-libraries/trilogy/fork ) 66 | 2. Create your feature branch (`git checkout -b my-new-feature`) 67 | 3. Commit your changes (`git commit -am 'Add some feature'`) 68 | 4. Push to the branch (`git push origin my-new-feature`) 69 | 5. Create a new Pull Request 70 | 71 | ## mysql2 gem compatibility 72 | 73 | The trilogy API was heavily inspired by the mysql2 gem but has a few notable 74 | differences: 75 | 76 | * The `query_flags` don't inherit from the connection options hash. 77 | This means that options like turning on/of casting will need to be set before 78 | a query and not passed in at connect time. 79 | * For performance reasons there is no `application_timezone` query option. If 80 | casting is enabled and your database timezone is different than what the 81 | application is expecting you'll need to do the conversion yourself later. 82 | * While we still tag strings with the encoding configured on the field they came 83 | from - for performance reasons no automatic transcoding into 84 | `Encoding.default_internal` is done. Similarly to not automatically converting 85 | Time objects from `database_timezone` into `application_timezone`, we leave 86 | the transcoding step up to the caller. 87 | * There is no `as` query option. Calling `Trilogy::Result#each` will yield an array 88 | of row values. If you want a hash you should use `Trilogy::Result#each_hash`. 89 | -------------------------------------------------------------------------------- /test/client/ping_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../test.h" 8 | 9 | #include "trilogy/blocking.h" 10 | #include "trilogy/client.h" 11 | #include "trilogy/error.h" 12 | 13 | #define do_connect(CONN) \ 14 | do { \ 15 | int err = trilogy_init(CONN); \ 16 | ASSERT_OK(err); \ 17 | err = trilogy_connect(CONN, get_connopt()); \ 18 | ASSERT_OK(err); \ 19 | } while (0) 20 | 21 | TEST test_ping_send() 22 | { 23 | trilogy_conn_t conn; 24 | 25 | do_connect(&conn); 26 | 27 | int err = trilogy_ping_send(&conn); 28 | while (err == TRILOGY_AGAIN) { 29 | err = wait_writable(&conn); 30 | ASSERT_OK(err); 31 | 32 | err = trilogy_flush_writes(&conn); 33 | } 34 | ASSERT_OK(err); 35 | 36 | trilogy_free(&conn); 37 | PASS(); 38 | } 39 | 40 | TEST test_ping_send_closed_socket() 41 | { 42 | trilogy_conn_t conn; 43 | 44 | do_connect(&conn); 45 | 46 | close_socket(&conn); 47 | 48 | int err = trilogy_ping_send(&conn); 49 | ASSERT_ERR(TRILOGY_SYSERR, err); 50 | 51 | trilogy_free(&conn); 52 | PASS(); 53 | } 54 | 55 | TEST test_ping_recv() 56 | { 57 | trilogy_conn_t conn; 58 | 59 | do_connect(&conn); 60 | 61 | int err = trilogy_ping_send(&conn); 62 | while (err == TRILOGY_AGAIN) { 63 | err = wait_writable(&conn); 64 | ASSERT_OK(err); 65 | 66 | err = trilogy_flush_writes(&conn); 67 | } 68 | ASSERT_OK(err); 69 | 70 | err = trilogy_ping_recv(&conn); 71 | while (err == TRILOGY_AGAIN) { 72 | err = wait_readable(&conn); 73 | ASSERT_OK(err); 74 | 75 | err = trilogy_ping_recv(&conn); 76 | } 77 | ASSERT_OK(err); 78 | 79 | trilogy_free(&conn); 80 | PASS(); 81 | } 82 | 83 | TEST test_ping_recv_closed_socket() 84 | { 85 | trilogy_conn_t conn; 86 | 87 | do_connect(&conn); 88 | 89 | int err = trilogy_ping_send(&conn); 90 | while (err == TRILOGY_AGAIN) { 91 | err = wait_writable(&conn); 92 | ASSERT_OK(err); 93 | 94 | err = trilogy_flush_writes(&conn); 95 | } 96 | ASSERT_OK(err); 97 | 98 | close_socket(&conn); 99 | 100 | err = trilogy_ping_recv(&conn); 101 | ASSERT_ERR(TRILOGY_SYSERR, err); 102 | 103 | trilogy_free(&conn); 104 | PASS(); 105 | } 106 | 107 | int client_ping_test() 108 | { 109 | RUN_TEST(test_ping_send); 110 | RUN_TEST(test_ping_send_closed_socket); 111 | RUN_TEST(test_ping_recv); 112 | RUN_TEST(test_ping_recv_closed_socket); 113 | 114 | return 0; 115 | } 116 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to make participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, sex characteristics, gender identity and expression, 9 | level of experience, education, socio-economic status, nationality, personal 10 | appearance, race, religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies within all project spaces, and it also applies when 49 | an individual is representing the project or its community in public spaces. 50 | Examples of representing a project or community include using an official 51 | project e-mail address, posting via an official social media account, or acting 52 | as an appointed representative at an online or offline event. Representation of 53 | a project may be further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at opensource+trilogy@github.com. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | -------------------------------------------------------------------------------- /test/client/set_option_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../test.h" 8 | 9 | #include "trilogy/blocking.h" 10 | #include "trilogy/client.h" 11 | #include "trilogy/error.h" 12 | 13 | #define do_connect(CONN) \ 14 | do { \ 15 | int err = trilogy_init(CONN); \ 16 | ASSERT_OK(err); \ 17 | err = trilogy_connect(CONN, get_connopt()); \ 18 | ASSERT_OK(err); \ 19 | } while (0) 20 | 21 | TEST test_set_option_send() 22 | { 23 | trilogy_conn_t conn; 24 | 25 | do_connect(&conn); 26 | 27 | const uint16_t option = 1; 28 | 29 | int err = trilogy_set_option_send(&conn, option); 30 | while (err == TRILOGY_AGAIN) { 31 | err = wait_writable(&conn); 32 | ASSERT_OK(err); 33 | 34 | err = trilogy_flush_writes(&conn); 35 | } 36 | ASSERT_OK(err); 37 | 38 | trilogy_free(&conn); 39 | PASS(); 40 | } 41 | 42 | TEST test_set_option_send_closed_socket() 43 | { 44 | trilogy_conn_t conn; 45 | 46 | do_connect(&conn); 47 | 48 | close_socket(&conn); 49 | 50 | const uint16_t option = 0; 51 | 52 | int err = trilogy_set_option_send(&conn, option); 53 | ASSERT_ERR(TRILOGY_SYSERR, err); 54 | 55 | trilogy_free(&conn); 56 | PASS(); 57 | } 58 | 59 | TEST test_set_option_recv() 60 | { 61 | trilogy_conn_t conn; 62 | 63 | do_connect(&conn); 64 | 65 | const uint16_t option = 1; 66 | 67 | int err = trilogy_set_option_send(&conn, option); 68 | while (err == TRILOGY_AGAIN) { 69 | err = wait_writable(&conn); 70 | ASSERT_OK(err); 71 | 72 | err = trilogy_flush_writes(&conn); 73 | } 74 | ASSERT_OK(err); 75 | 76 | err = trilogy_set_option_recv(&conn); 77 | while (err == TRILOGY_AGAIN) { 78 | err = wait_readable(&conn); 79 | ASSERT_OK(err); 80 | 81 | err = trilogy_set_option_recv(&conn); 82 | } 83 | 84 | ASSERT_OK(err); 85 | 86 | trilogy_free(&conn); 87 | PASS(); 88 | } 89 | 90 | TEST test_set_option_recv_closed_socket() 91 | { 92 | trilogy_conn_t conn; 93 | 94 | do_connect(&conn); 95 | 96 | const uint16_t option = 1; 97 | 98 | int err = trilogy_set_option_send(&conn, option); 99 | while (err == TRILOGY_AGAIN) { 100 | err = wait_writable(&conn); 101 | ASSERT_OK(err); 102 | 103 | err = trilogy_flush_writes(&conn); 104 | } 105 | ASSERT_OK(err); 106 | 107 | close_socket(&conn); 108 | 109 | err = trilogy_set_option_recv(&conn); 110 | ASSERT_ERR(TRILOGY_SYSERR, err); 111 | 112 | trilogy_free(&conn); 113 | PASS(); 114 | } 115 | 116 | int client_set_option_test() 117 | { 118 | RUN_TEST(test_set_option_send); 119 | RUN_TEST(test_set_option_send_closed_socket); 120 | RUN_TEST(test_set_option_recv); 121 | RUN_TEST(test_set_option_recv_closed_socket); 122 | 123 | return 0; 124 | } 125 | -------------------------------------------------------------------------------- /inc/trilogy/error.h: -------------------------------------------------------------------------------- 1 | #ifndef TRILOGY_ERROR_H 2 | #define TRILOGY_ERROR_H 3 | 4 | #define TRILOGY_ERROR_CODES(XX) \ 5 | XX(TRILOGY_OK, 0) \ 6 | XX(TRILOGY_ERR, -1) \ 7 | XX(TRILOGY_EOF, -2) \ 8 | XX(TRILOGY_SYSERR, -3) /* check errno */ \ 9 | XX(TRILOGY_UNEXPECTED_PACKET, -4) \ 10 | XX(TRILOGY_TRUNCATED_PACKET, -5) \ 11 | XX(TRILOGY_PROTOCOL_VIOLATION, -6) \ 12 | XX(TRILOGY_AUTH_PLUGIN_TOO_LONG, -7) \ 13 | XX(TRILOGY_EXTRA_DATA_IN_PACKET, -8) \ 14 | XX(TRILOGY_INVALID_CHARSET, -9) \ 15 | XX(TRILOGY_AGAIN, -10) \ 16 | XX(TRILOGY_CLOSED_CONNECTION, -11) \ 17 | XX(TRILOGY_HAVE_RESULTS, -12) \ 18 | XX(TRILOGY_NULL_VALUE, -13) \ 19 | XX(TRILOGY_INVALID_SEQUENCE_ID, -14) \ 20 | XX(TRILOGY_TYPE_OVERFLOW, -15) \ 21 | XX(TRILOGY_OPENSSL_ERR, -16) /* check ERR_get_error() */ \ 22 | XX(TRILOGY_UNSUPPORTED, -17) \ 23 | XX(TRILOGY_DNS_ERR, -18) \ 24 | XX(TRILOGY_AUTH_SWITCH, -19) \ 25 | XX(TRILOGY_MAX_PACKET_EXCEEDED, -20) \ 26 | XX(TRILOGY_UNKNOWN_TYPE, -21) \ 27 | XX(TRILOGY_TIMEOUT, -22) \ 28 | XX(TRILOGY_AUTH_PLUGIN_ERROR, -23) \ 29 | XX(TRILOGY_MEM_ERROR, -24) 30 | 31 | enum { 32 | #define XX(name, code) name = code, 33 | TRILOGY_ERROR_CODES(XX) 34 | #undef XX 35 | }; 36 | 37 | /* trilogy_error - Get the string version of an Trilogy error code. 38 | * 39 | * This can be useful for logging or debugging as printing the error value 40 | * integer itself doesn't provide much context. 41 | * 42 | * error - An Trilogy error code integer. 43 | * 44 | * Returns an error name constant from TRILOGY_ERROR_CODES as a C-string. 45 | */ 46 | const char *trilogy_error(int error); 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /test/client/change_db_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../test.h" 8 | 9 | #include "trilogy/blocking.h" 10 | #include "trilogy/client.h" 11 | #include "trilogy/error.h" 12 | 13 | #define do_connect(CONN) \ 14 | do { \ 15 | int err = trilogy_init(CONN); \ 16 | ASSERT_OK(err); \ 17 | err = trilogy_connect(CONN, get_connopt()); \ 18 | ASSERT_OK(err); \ 19 | } while (0); 20 | 21 | TEST test_change_db_send() 22 | { 23 | trilogy_conn_t conn; 24 | const char *default_db = get_connopt()->database; 25 | 26 | do_connect(&conn); 27 | 28 | int err = trilogy_change_db_send(&conn, default_db, strlen(default_db)); 29 | while (err == TRILOGY_AGAIN) { 30 | err = wait_writable(&conn); 31 | ASSERT_OK(err); 32 | 33 | err = trilogy_flush_writes(&conn); 34 | } 35 | ASSERT_OK(err); 36 | 37 | trilogy_free(&conn); 38 | PASS(); 39 | } 40 | 41 | TEST test_change_db_send_closed_socket() 42 | { 43 | trilogy_conn_t conn; 44 | const char *default_db = get_connopt()->database; 45 | 46 | do_connect(&conn); 47 | 48 | close_socket(&conn); 49 | 50 | int err = trilogy_change_db_send(&conn, default_db, strlen(default_db)); 51 | ASSERT_ERR(TRILOGY_SYSERR, err); 52 | 53 | trilogy_free(&conn); 54 | PASS(); 55 | } 56 | 57 | TEST test_change_db_recv() 58 | { 59 | trilogy_conn_t conn; 60 | const char *default_db = get_connopt()->database; 61 | 62 | do_connect(&conn); 63 | 64 | int err = trilogy_change_db_send(&conn, default_db, strlen(default_db)); 65 | while (err == TRILOGY_AGAIN) { 66 | err = wait_writable(&conn); 67 | ASSERT_OK(err); 68 | 69 | err = trilogy_flush_writes(&conn); 70 | } 71 | ASSERT_OK(err); 72 | 73 | err = trilogy_change_db_recv(&conn); 74 | while (err == TRILOGY_AGAIN) { 75 | err = wait_readable(&conn); 76 | 77 | ASSERT_OK(err); 78 | err = trilogy_change_db_recv(&conn); 79 | } 80 | ASSERT_OK(err); 81 | 82 | trilogy_free(&conn); 83 | PASS(); 84 | } 85 | 86 | TEST test_change_db_recv_closed_socket() 87 | { 88 | trilogy_conn_t conn; 89 | const char *default_db = get_connopt()->database; 90 | 91 | do_connect(&conn); 92 | 93 | int err = trilogy_change_db_send(&conn, default_db, strlen(default_db)); 94 | while (err == TRILOGY_AGAIN) { 95 | err = wait_writable(&conn); 96 | ASSERT_OK(err); 97 | 98 | err = trilogy_change_db_send(&conn, default_db, strlen(default_db)); 99 | } 100 | ASSERT_OK(err); 101 | 102 | close_socket(&conn); 103 | 104 | err = trilogy_change_db_recv(&conn); 105 | ASSERT_ERR(TRILOGY_SYSERR, err); 106 | 107 | trilogy_free(&conn); 108 | PASS(); 109 | } 110 | 111 | int client_change_db_test() 112 | { 113 | RUN_TEST(test_change_db_send); 114 | RUN_TEST(test_change_db_send_closed_socket); 115 | RUN_TEST(test_change_db_recv); 116 | RUN_TEST(test_change_db_recv_closed_socket); 117 | 118 | return 0; 119 | } 120 | -------------------------------------------------------------------------------- /contrib/ruby/lib/trilogy/encoding.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | class Trilogy 4 | module Encoding 5 | RUBY_ENCODINGS = { 6 | "big5" => "Big5", 7 | "dec8" => nil, 8 | "cp850" => "CP850", 9 | "hp8" => nil, 10 | "koi8r" => "KOI8-R", 11 | "latin1" => "ISO-8859-1", 12 | "latin2" => "ISO-8859-2", 13 | "swe7" => nil, 14 | "ascii" => "US-ASCII", 15 | "ujis" => "eucJP-ms", 16 | "sjis" => "Shift_JIS", 17 | "hebrew" => "ISO-8859-8", 18 | "tis620" => "TIS-620", 19 | "euckr" => "EUC-KR", 20 | "koi8u" => "KOI8-R", 21 | "gb2312" => "GB2312", 22 | "greek" => "ISO-8859-7", 23 | "cp1250" => "Windows-1250", 24 | "gbk" => "GBK", 25 | "latin5" => "ISO-8859-9", 26 | "armscii8" => nil, 27 | "utf8" => "UTF-8", 28 | "ucs2" => "UTF-16BE", 29 | "cp866" => "IBM866", 30 | "keybcs2" => nil, 31 | "macce" => "macCentEuro", 32 | "macroman" => "macRoman", 33 | "cp852" => "CP852", 34 | "latin7" => "ISO-8859-13", 35 | "utf8mb4" => "UTF-8", 36 | "cp1251" => "Windows-1251", 37 | "utf16" => "UTF-16", 38 | "cp1256" => "Windows-1256", 39 | "cp1257" => "Windows-1257", 40 | "utf32" => "UTF-32", 41 | "binary" => "ASCII-8BIT", 42 | "geostd8" => nil, 43 | "cp932" => "Windows-31J", 44 | "eucjpms" => "eucJP-ms", 45 | "utf16le" => "UTF-16LE", 46 | "gb18030" => "GB18030", 47 | }.freeze 48 | 49 | CHARSETS = { 50 | "big5" => CHARSET_BIG5_CHINESE_CI, 51 | "cp850" => CHARSET_CP850_GENERAL_CI, 52 | "koi8r" => CHARSET_KOI8R_GENERAL_CI, 53 | "latin1" => CHARSET_LATIN1_GENERAL_CI, 54 | "latin2" => CHARSET_LATIN2_GENERAL_CI, 55 | "ascii" => CHARSET_ASCII_GENERAL_CI, 56 | "ujis" => CHARSET_UJIS_JAPANESE_CI, 57 | "sjis" => CHARSET_SJIS_JAPANESE_CI, 58 | "hebrew" => CHARSET_HEBREW_GENERAL_CI, 59 | "tis620" => CHARSET_TIS620_THAI_CI, 60 | "euckr" => CHARSET_EUCKR_KOREAN_CI, 61 | "koi8u" => CHARSET_KOI8U_GENERAL_CI, 62 | "gb2312" => CHARSET_GB2312_CHINESE_CI, 63 | "greek" => CHARSET_GREEK_GENERAL_CI, 64 | "cp1250" => CHARSET_CP1250_GENERAL_CI, 65 | "gbk" => CHARSET_GBK_CHINESE_CI, 66 | "latin5" => CHARSET_LATIN5_TURKISH_CI, 67 | "utf8" => CHARSET_UTF8_GENERAL_CI, 68 | "ucs2" => CHARSET_UCS2_GENERAL_CI, 69 | "cp866" => CHARSET_CP866_GENERAL_CI, 70 | "cp932" => CHARSET_CP932_JAPANESE_CI, 71 | "eucjpms" => CHARSET_EUCJPMS_JAPANESE_CI, 72 | "utf16le" => CHARSET_UTF16_GENERAL_CI, 73 | "gb18030" => CHARSET_GB18030_CHINESE_CI, 74 | "macce" => CHARSET_MACCE_GENERAL_CI, 75 | "macroman" => CHARSET_MACROMAN_GENERAL_CI, 76 | "cp852" => CHARSET_CP852_GENERAL_CI, 77 | "latin7" => CHARSET_LATIN7_GENERAL_CI, 78 | "utf8mb4" => CHARSET_UTF8MB4_GENERAL_CI, 79 | "cp1251" => CHARSET_CP1251_GENERAL_CI, 80 | "utf16" => CHARSET_UTF16_GENERAL_CI, 81 | "cp1256" => CHARSET_CP1256_GENERAL_CI, 82 | "cp1257" => CHARSET_CP1257_GENERAL_CI, 83 | "utf32" => CHARSET_UTF32_GENERAL_CI, 84 | "binary" => CHARSET_BINARY, 85 | }.freeze 86 | 87 | def self.find(mysql_encoding) 88 | unless rb_encoding = RUBY_ENCODINGS[mysql_encoding] 89 | raise ArgumentError, "Unknown or unsupported encoding: #{mysql_encoding}" 90 | end 91 | 92 | ::Encoding.find(rb_encoding) 93 | end 94 | 95 | def self.charset(mysql_encoding) 96 | CHARSETS[mysql_encoding] 97 | end 98 | end 99 | end 100 | -------------------------------------------------------------------------------- /contrib/ruby/script/benchmark: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "rubygems" if !defined?(Gem) 4 | require "bundler/setup" 5 | 6 | require "benchmark/ips" 7 | require "trilogy" 8 | require "mysql2" 9 | 10 | DEFAULT_USER = ENV["MYSQL_USER"] || "root" 11 | DEFAULT_PASS = ENV["MYSQL_PASS"] 12 | DEFAULT_SOCK = ENV["MYSQL_SOCK"] || "/tmp/mysql.sock" 13 | 14 | connect_options = { 15 | path: DEFAULT_SOCK, # for trilogy 16 | socket: DEFAULT_SOCK, # for mysql2 17 | username: DEFAULT_USER, 18 | password: DEFAULT_PASS 19 | } 20 | 21 | Benchmark.ips do |x| 22 | x.report "trilogy connect/close" do 23 | client = Trilogy.new(connect_options) 24 | client.close 25 | end 26 | 27 | x.report "mysql2 connect/close" do 28 | client = Mysql2::Client.new(connect_options) 29 | client.close 30 | end 31 | 32 | x.compare! 33 | end 34 | 35 | trilogy_client = Trilogy.new(connect_options) 36 | mysql2_client = Mysql2::Client.new(connect_options) 37 | 38 | ESCAPE_STR = "abc\\'def\\\"ghi\\0jkl%mno" 39 | 40 | Benchmark.ips do |x| 41 | x.report "trilogy escape" do 42 | trilogy_client.escape ESCAPE_STR 43 | end 44 | 45 | x.report "mysql2 escape" do 46 | mysql2_client.escape ESCAPE_STR 47 | end 48 | 49 | x.compare! 50 | end 51 | 52 | Benchmark.ips do |x| 53 | x.report "trilogy ping" do 54 | trilogy_client.ping 55 | end 56 | 57 | x.report "mysql2 ping" do 58 | mysql2_client.ping 59 | end 60 | 61 | x.compare! 62 | end 63 | 64 | TEST_DB = "test" 65 | 66 | Benchmark.ips do |x| 67 | x.report "trilogy change_db" do 68 | trilogy_client.change_db TEST_DB 69 | end 70 | 71 | x.report "mysql2 change_db" do 72 | mysql2_client.select_db TEST_DB 73 | end 74 | 75 | x.compare! 76 | end 77 | 78 | QUERY = "SELECT 1" 79 | 80 | Benchmark.ips do |x| 81 | x.report "trilogy query" do 82 | trilogy_client.query QUERY 83 | end 84 | 85 | x.report "trilogy query (no-casting)" do 86 | trilogy_client.query_flags &= ~Trilogy::QUERY_FLAGS_CAST 87 | result = trilogy_client.query QUERY 88 | result.to_a 89 | end 90 | 91 | x.report "mysql2 query" do 92 | result = mysql2_client.query QUERY 93 | result.to_a 94 | end 95 | 96 | x.report "mysql2 query (no-casting)" do 97 | result = mysql2_client.query QUERY, cast: false 98 | result.to_a 99 | end 100 | 101 | x.report "mysql2 query (stream)" do 102 | result = mysql2_client.query QUERY, stream: true, cache_rows: false 103 | result.to_a 104 | end 105 | 106 | x.report "mysql2 query (stream + no-casting)" do 107 | result = mysql2_client.query QUERY, stream: true, cache_rows: false 108 | result.to_a 109 | end 110 | 111 | x.compare! 112 | end 113 | 114 | # affect some rows 115 | trilogy_client.query("INSERT INTO trilogy_test (varchar_test) VALUES ('a')") 116 | mysql2_client.query("INSERT INTO trilogy_test (varchar_test) VALUES ('a')") 117 | 118 | Benchmark.ips do |x| 119 | x.report "trilogy affected_rows" do 120 | trilogy_client.affected_rows 121 | end 122 | 123 | x.report "mysql2 affected_rows" do 124 | mysql2_client.affected_rows 125 | end 126 | 127 | x.compare! 128 | end 129 | 130 | # reset warning count 131 | trilogy_client.query("SELECT 1") 132 | mysql2_client.query("SELECT 1").to_a 133 | 134 | Benchmark.ips do |x| 135 | x.report "trilogy warning_count" do 136 | trilogy_client.warning_count 137 | end 138 | 139 | x.report "mysql2 warning_count" do 140 | mysql2_client.warning_count 141 | end 142 | 143 | x.compare! 144 | end 145 | 146 | # setup an insert id 147 | trilogy_client.query("INSERT INTO trilogy_test (varchar_test) VALUES ('a')") 148 | mysql2_client.query("INSERT INTO trilogy_test (varchar_test) VALUES ('a')") 149 | 150 | Benchmark.ips do |x| 151 | x.report "trilogy last_insert_id" do 152 | trilogy_client.last_insert_id 153 | end 154 | 155 | x.report "mysql2 last_insert_id" do 156 | mysql2_client.last_id 157 | end 158 | 159 | x.compare! 160 | end 161 | -------------------------------------------------------------------------------- /src/packet_parser.c: -------------------------------------------------------------------------------- 1 | #include "trilogy/packet_parser.h" 2 | #include "trilogy/error.h" 3 | 4 | enum { 5 | S_LEN_0 = 0, 6 | S_LEN_1 = 1, 7 | S_LEN_2 = 2, 8 | S_SEQ = 3, 9 | S_PAYLOAD = 4, 10 | }; 11 | 12 | void trilogy_packet_parser_init(trilogy_packet_parser_t *parser, const trilogy_packet_parser_callbacks_t *callbacks) 13 | { 14 | parser->user_data = NULL; 15 | parser->callbacks = callbacks; 16 | parser->state = S_LEN_0; 17 | parser->fragment = 0; 18 | parser->deferred_end_callback = 0; 19 | parser->sequence_number = 0; 20 | } 21 | 22 | size_t trilogy_packet_parser_execute(trilogy_packet_parser_t *parser, const uint8_t *buff, size_t len, int *error) 23 | { 24 | size_t i = 0; 25 | 26 | if (parser->deferred_end_callback) { 27 | parser->deferred_end_callback = 0; 28 | 29 | int rc = parser->callbacks->on_packet_end(parser->user_data); 30 | 31 | if (rc) { 32 | *error = rc; 33 | return 0; 34 | } 35 | } 36 | 37 | while (i < len) { 38 | uint8_t cur_byte = buff[i]; 39 | 40 | switch (parser->state) { 41 | case S_LEN_0: { 42 | parser->bytes_remaining = cur_byte; 43 | parser->state = S_LEN_1; 44 | 45 | i++; 46 | break; 47 | } 48 | case S_LEN_1: { 49 | parser->bytes_remaining |= cur_byte << 8; 50 | parser->state = S_LEN_2; 51 | 52 | i++; 53 | break; 54 | } 55 | case S_LEN_2: { 56 | parser->bytes_remaining |= cur_byte << 16; 57 | 58 | int was_fragment = parser->fragment; 59 | 60 | parser->fragment = (parser->bytes_remaining == TRILOGY_MAX_PACKET_LEN); 61 | 62 | parser->state = S_SEQ; 63 | i++; 64 | 65 | if (!was_fragment) { 66 | int rc = parser->callbacks->on_packet_begin(parser->user_data); 67 | 68 | if (rc) { 69 | *error = rc; 70 | return i; 71 | } 72 | } 73 | 74 | break; 75 | } 76 | case S_SEQ: { 77 | if (cur_byte != parser->sequence_number) { 78 | *error = TRILOGY_INVALID_SEQUENCE_ID; 79 | return i; 80 | } 81 | 82 | parser->sequence_number++; 83 | parser->state = S_PAYLOAD; 84 | 85 | i++; 86 | 87 | if (parser->bytes_remaining == 0) { 88 | goto end_of_payload; 89 | } 90 | 91 | break; 92 | } 93 | case S_PAYLOAD: { 94 | const uint8_t *ptr = buff + i; 95 | size_t chunk_length = len - i; 96 | 97 | if (chunk_length > parser->bytes_remaining) { 98 | chunk_length = parser->bytes_remaining; 99 | } 100 | 101 | i += chunk_length; 102 | parser->bytes_remaining -= chunk_length; 103 | 104 | int rc = parser->callbacks->on_packet_data(parser->user_data, ptr, chunk_length); 105 | 106 | if (rc) { 107 | if (parser->bytes_remaining == 0) { 108 | parser->deferred_end_callback = 1; 109 | } 110 | 111 | *error = rc; 112 | return i; 113 | } 114 | 115 | if (parser->bytes_remaining == 0) { 116 | goto end_of_payload; 117 | } 118 | 119 | break; 120 | } 121 | end_of_payload : { 122 | parser->state = S_LEN_0; 123 | 124 | if (!parser->fragment) { 125 | int rc = parser->callbacks->on_packet_end(parser->user_data); 126 | 127 | if (rc) { 128 | *error = rc; 129 | return i; 130 | } 131 | } 132 | 133 | break; 134 | } 135 | } 136 | } 137 | 138 | *error = 0; 139 | return i; 140 | } 141 | -------------------------------------------------------------------------------- /contrib/ruby/lib/trilogy/error.rb: -------------------------------------------------------------------------------- 1 | class Trilogy 2 | # Trilogy::Error is the base error type. All errors raised by Trilogy 3 | # should be descendants of Trilogy::Error 4 | module Error 5 | attr_reader :error_code 6 | end 7 | 8 | # Trilogy::ConnectionError is the base error type for all potentially transient 9 | # network errors. 10 | module ConnectionError 11 | include Error 12 | end 13 | 14 | # Trilogy may raise various syscall errors, which we treat as Trilogy::Errors. 15 | module SyscallError 16 | ERRORS = {} 17 | 18 | Errno.constants 19 | .map { |c| Errno.const_get(c) }.uniq 20 | .select { |c| c.is_a?(Class) && c < SystemCallError } 21 | .each do |c| 22 | errno_name = c.to_s.split('::').last 23 | ERRORS[c::Errno] = const_set(errno_name, Class.new(c) { 24 | include Trilogy::ConnectionError 25 | singleton_class.define_method(:===, Module.instance_method(:===)) 26 | }) 27 | end 28 | 29 | ERRORS.freeze 30 | 31 | class << self 32 | def from_errno(errno, message) 33 | ERRORS[errno].new(message) 34 | end 35 | end 36 | end 37 | 38 | ConnectionRefusedError = SyscallError::ECONNREFUSED 39 | deprecate_constant :ConnectionRefusedError 40 | ConnectionResetError = SyscallError::ECONNRESET 41 | deprecate_constant :ConnectionResetError 42 | 43 | class BaseError < StandardError 44 | include Error 45 | 46 | def initialize(error_message = nil, error_code = nil) 47 | message = error_code ? "#{error_code}: #{error_message}" : error_message 48 | super(message) 49 | @error_code = error_code 50 | end 51 | end 52 | 53 | class BaseConnectionError < BaseError 54 | include ConnectionError 55 | end 56 | 57 | # Trilogy::ClientError is the base error type for invalid queries or parameters 58 | # that shouldn't be retried. 59 | class ClientError < BaseError 60 | include Error 61 | end 62 | 63 | class QueryError < ClientError 64 | end 65 | 66 | class CastError < ClientError 67 | end 68 | 69 | class TimeoutError < BaseConnectionError 70 | end 71 | 72 | # DatabaseError was replaced by ProtocolError, but we'll keep it around as an 73 | # ancestor of ProtocolError for compatibility reasons (e.g. so `rescue DatabaseError` 74 | # still works. We can remove this class in the next major release. 75 | module DatabaseError 76 | end 77 | 78 | class ProtocolError < BaseError 79 | include DatabaseError 80 | 81 | ERROR_CODES = { 82 | 1205 => TimeoutError, # ER_LOCK_WAIT_TIMEOUT 83 | 1044 => BaseConnectionError, # ER_DBACCESS_DENIED_ERROR 84 | 1045 => BaseConnectionError, # ER_ACCESS_DENIED_ERROR 85 | 1064 => QueryError, # ER_PARSE_ERROR 86 | 1152 => BaseConnectionError, # ER_ABORTING_CONNECTION 87 | 1153 => BaseConnectionError, # ER_NET_PACKET_TOO_LARGE 88 | 1154 => BaseConnectionError, # ER_NET_READ_ERROR_FROM_PIPE 89 | 1155 => BaseConnectionError, # ER_NET_FCNTL_ERROR 90 | 1156 => BaseConnectionError, # ER_NET_PACKETS_OUT_OF_ORDER 91 | 1157 => BaseConnectionError, # ER_NET_UNCOMPRESS_ERROR 92 | 1158 => BaseConnectionError, # ER_NET_READ_ERROR 93 | 1159 => BaseConnectionError, # ER_NET_READ_INTERRUPTED 94 | 1160 => BaseConnectionError, # ER_NET_ERROR_ON_WRITE 95 | 1161 => BaseConnectionError, # ER_NET_WRITE_INTERRUPTED 96 | 1927 => BaseConnectionError, # ER_CONNECTION_KILLED 97 | } 98 | class << self 99 | def from_code(message, code) 100 | ERROR_CODES.fetch(code, self).new(message, code) 101 | end 102 | end 103 | end 104 | 105 | class SSLError < BaseError 106 | include ConnectionError 107 | end 108 | 109 | # Raised on attempt to use connection which was explicitly closed by the user 110 | class ConnectionClosed < IOError 111 | include ConnectionError 112 | end 113 | 114 | # Occurs when a socket read or write returns EOF or when an operation is 115 | # attempted on a socket which previously encountered an error. 116 | class EOFError < BaseConnectionError 117 | end 118 | 119 | # Occurs when the server request an auth switch to an incompatible 120 | # authentication plugin 121 | class AuthPluginError < Trilogy::BaseConnectionError 122 | end 123 | end 124 | -------------------------------------------------------------------------------- /test/client/stmt_prepare_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../test.h" 8 | 9 | #include "trilogy/blocking.h" 10 | #include "trilogy/client.h" 11 | #include "trilogy/error.h" 12 | 13 | #define do_connect(CONN) \ 14 | do { \ 15 | int err = trilogy_init(CONN); \ 16 | ASSERT_OK(err); \ 17 | err = trilogy_connect(CONN, get_connopt()); \ 18 | ASSERT_OK(err); \ 19 | } while (0) 20 | 21 | TEST test_stmt_prepare_send() 22 | { 23 | trilogy_conn_t conn; 24 | 25 | do_connect(&conn); 26 | 27 | const char *sql = "SELECT ?"; 28 | size_t sql_len = strlen(sql); 29 | 30 | int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); 31 | while (err == TRILOGY_AGAIN) { 32 | err = wait_writable(&conn); 33 | ASSERT_OK(err); 34 | 35 | err = trilogy_flush_writes(&conn); 36 | } 37 | ASSERT_OK(err); 38 | 39 | trilogy_free(&conn); 40 | PASS(); 41 | } 42 | 43 | TEST test_stmt_prepare_send_closed_socket() 44 | { 45 | trilogy_conn_t conn; 46 | 47 | do_connect(&conn); 48 | 49 | close_socket(&conn); 50 | 51 | const char *sql = "SELECT ?"; 52 | size_t sql_len = strlen(sql); 53 | 54 | int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); 55 | ASSERT_ERR(TRILOGY_SYSERR, err); 56 | 57 | trilogy_free(&conn); 58 | PASS(); 59 | } 60 | 61 | TEST test_stmt_prepare_recv() 62 | { 63 | trilogy_conn_t conn; 64 | 65 | do_connect(&conn); 66 | 67 | const char *sql = "SELECT ?"; 68 | size_t sql_len = strlen(sql); 69 | 70 | int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); 71 | while (err == TRILOGY_AGAIN) { 72 | err = wait_writable(&conn); 73 | ASSERT_OK(err); 74 | 75 | err = trilogy_flush_writes(&conn); 76 | } 77 | ASSERT_OK(err); 78 | 79 | trilogy_stmt_t stmt; 80 | 81 | err = trilogy_stmt_prepare_recv(&conn, &stmt); 82 | while (err == TRILOGY_AGAIN) { 83 | err = wait_readable(&conn); 84 | ASSERT_OK(err); 85 | 86 | err = trilogy_stmt_prepare_recv(&conn, &stmt); 87 | } 88 | ASSERT_OK(err); 89 | 90 | ASSERT_EQ(1, stmt.parameter_count); 91 | 92 | trilogy_column_packet_t params[1]; 93 | 94 | for (uint64_t i = 0; i < stmt.parameter_count; i++) { 95 | trilogy_column_packet_t *param = ¶ms[i]; 96 | 97 | err = trilogy_read_full_column(&conn, param); 98 | 99 | if (err < 0) { 100 | return err; 101 | } 102 | } 103 | 104 | ASSERT_EQ(1, stmt.column_count); 105 | 106 | trilogy_column_packet_t column_defs[1]; 107 | 108 | for (uint64_t i = 0; i < stmt.column_count; i++) { 109 | trilogy_column_packet_t *column = &column_defs[i]; 110 | 111 | err = trilogy_read_full_column(&conn, column); 112 | 113 | if (err < 0) { 114 | return err; 115 | } 116 | } 117 | 118 | trilogy_free(&conn); 119 | PASS(); 120 | } 121 | 122 | TEST test_stmt_prepare_recv_closed_socket() 123 | { 124 | trilogy_conn_t conn; 125 | 126 | do_connect(&conn); 127 | 128 | const char *sql = "SELECT ?"; 129 | size_t sql_len = strlen(sql); 130 | 131 | int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); 132 | while (err == TRILOGY_AGAIN) { 133 | err = wait_writable(&conn); 134 | ASSERT_OK(err); 135 | 136 | err = trilogy_flush_writes(&conn); 137 | } 138 | ASSERT_OK(err); 139 | 140 | close_socket(&conn); 141 | 142 | trilogy_stmt_t stmt; 143 | 144 | err = trilogy_stmt_prepare_recv(&conn, &stmt); 145 | ASSERT_ERR(TRILOGY_SYSERR, err); 146 | 147 | trilogy_free(&conn); 148 | PASS(); 149 | } 150 | 151 | int client_stmt_prepare_test() 152 | { 153 | RUN_TEST(test_stmt_prepare_send); 154 | RUN_TEST(test_stmt_prepare_send_closed_socket); 155 | RUN_TEST(test_stmt_prepare_recv); 156 | RUN_TEST(test_stmt_prepare_recv_closed_socket); 157 | 158 | return 0; 159 | } 160 | -------------------------------------------------------------------------------- /contrib/ruby/test/auth_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class AuthTest < TrilogyTest 4 | def setup 5 | client = new_tcp_client 6 | 7 | plugin_exists = client.query("SELECT name FROM mysql.plugin WHERE name = 'cleartext_plugin_server'").rows.first 8 | unless plugin_exists 9 | client.query("INSTALL PLUGIN cleartext_plugin_server SONAME 'auth_test_plugin.so'") 10 | end 11 | 12 | super 13 | end 14 | 15 | def has_caching_sha2? 16 | server_version = new_tcp_client.server_version 17 | server_version.split(".", 2)[0].to_i >= 8 18 | end 19 | 20 | def test_connect_native_with_password 21 | create_and_delete_test_user(username: "native", auth_plugin: "mysql_native_password") do 22 | client = new_tcp_client username: "native", password: "password" 23 | 24 | refute_nil client 25 | ensure 26 | ensure_closed client 27 | end 28 | end 29 | 30 | def test_connect_caching_sha2_with_password 31 | return skip unless has_caching_sha2? 32 | create_and_delete_test_user(username: "caching_sha2", auth_plugin: "caching_sha2_password") do 33 | 34 | # Ensure correct setup 35 | assert_equal [["caching_sha2_password"]], new_tcp_client.query("SELECT plugin FROM mysql.user WHERE user = 'caching_sha2'").rows 36 | 37 | client = new_tcp_client username: "caching_sha2", password: "password" 38 | 39 | refute_nil client 40 | ensure 41 | ensure_closed client 42 | end 43 | end 44 | 45 | def test_connect_with_unix_and_caching_sha2_works 46 | return skip unless has_caching_sha2? 47 | return skip unless ["127.0.0.1", "localhost"].include?(DEFAULT_HOST) 48 | create_and_delete_test_user(username: "caching_sha2", host: "localhost", auth_plugin: "caching_sha2_password") do 49 | 50 | socket = new_tcp_client.query("SHOW VARIABLES LIKE 'socket'").to_a[0][1] 51 | 52 | if !File.exist?(socket) 53 | skip "cound not find socket at #{socket}" 54 | end 55 | 56 | client = new_unix_client(socket, username: "caching_sha2", password: "password") 57 | refute_nil client 58 | ensure 59 | ensure_closed client 60 | end 61 | end 62 | 63 | def test_connect_without_ssl_or_unix_socket_caching_sha2_raises 64 | return skip unless has_caching_sha2? 65 | 66 | create_and_delete_test_user(username: "caching_sha2", auth_plugin: "caching_sha2_password") do 67 | # Ensure correct setup 68 | assert_equal [["caching_sha2_password"]], new_tcp_client.query("SELECT plugin FROM mysql.user WHERE user = 'caching_sha2'").rows 69 | 70 | options = { 71 | host: DEFAULT_HOST, 72 | port: DEFAULT_PORT, 73 | username: "caching_sha2", 74 | password: "password", 75 | ssl: false, 76 | ssl_mode: 0 77 | } 78 | 79 | err = assert_raises Trilogy::ConnectionError do 80 | new_tcp_client options 81 | end 82 | 83 | assert_includes err.message, "TRILOGY_UNSUPPORTED" 84 | assert_includes err.message, "caching_sha2_password requires either TCP with TLS or a unix socket" 85 | end 86 | end 87 | 88 | def test_connection_error_native 89 | create_and_delete_test_user(username: "native", auth_plugin: "mysql_native_password") do 90 | 91 | err = assert_raises Trilogy::ConnectionError do 92 | new_tcp_client(username: "native", password: "incorrect") 93 | end 94 | 95 | assert_includes err.message, "Access denied for user 'native" 96 | end 97 | end 98 | 99 | def test_connection_error_caching_sha2 100 | return skip unless has_caching_sha2? 101 | 102 | create_and_delete_test_user(username: "caching_sha2", auth_plugin: "caching_sha2_password") do 103 | 104 | err = assert_raises Trilogy::ConnectionError do 105 | new_tcp_client(username: "caching_sha2", password: "incorrect") 106 | end 107 | assert_includes err.message, "Access denied for user 'caching_sha2" 108 | end 109 | end 110 | 111 | def test_cleartext_auth_plugin_with_password 112 | create_and_delete_test_user(username: "cleartext_user", auth_plugin: "cleartext_plugin_server") do 113 | client = new_tcp_client username: "cleartext_user", password: "password", enable_cleartext_plugin: true 114 | refute_nil client 115 | ensure 116 | ensure_closed client 117 | end 118 | end 119 | 120 | def test_cleartext_auth_plugin_disabled 121 | create_and_delete_test_user(username: "cleartext_user", password: "", auth_plugin: "cleartext_plugin_server") do 122 | 123 | assert_raises Trilogy::AuthPluginError do 124 | new_tcp_client username: "cleartext_user", password: "password" 125 | end 126 | end 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /inc/trilogy/socket.h: -------------------------------------------------------------------------------- 1 | #ifndef TRILOGY_SOCKET_H 2 | #define TRILOGY_SOCKET_H 3 | 4 | #include "trilogy/error.h" 5 | #include "trilogy/protocol.h" 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | typedef enum { 13 | TRILOGY_WAIT_READ = 0, 14 | TRILOGY_WAIT_WRITE = 1, 15 | TRILOGY_WAIT_HANDSHAKE = 2, 16 | TRILOGY_WAIT_CONNECT = 3, 17 | } trilogy_wait_t; 18 | 19 | // We use the most strict mode as value 1 so if anyone ever 20 | // treats this as a boolean, they get the most strict behavior 21 | // by default. 22 | typedef enum { 23 | TRILOGY_SSL_DISABLED = 0, 24 | TRILOGY_SSL_VERIFY_IDENTITY = 1, 25 | TRILOGY_SSL_VERIFY_CA = 2, 26 | TRILOGY_SSL_REQUIRED_NOVERIFY = 3, 27 | TRILOGY_SSL_PREFERRED_NOVERIFY = 4, 28 | } trilogy_ssl_mode_t; 29 | 30 | typedef enum { 31 | TRILOGY_TLS_VERSION_UNDEF = 0, 32 | TRILOGY_TLS_VERSION_10 = 1, 33 | TRILOGY_TLS_VERSION_11 = 2, 34 | TRILOGY_TLS_VERSION_12 = 3, 35 | TRILOGY_TLS_VERSION_13 = 4, 36 | } trilogy_tls_version_t; 37 | 38 | typedef struct { 39 | char *hostname; 40 | char *path; 41 | char *database; 42 | char *username; 43 | char *password; 44 | size_t password_len; 45 | uint8_t encoding; 46 | 47 | trilogy_ssl_mode_t ssl_mode; 48 | trilogy_tls_version_t tls_min_version; 49 | trilogy_tls_version_t tls_max_version; 50 | uint16_t port; 51 | 52 | char *ssl_ca; 53 | char *ssl_capath; 54 | char *ssl_cert; 55 | char *ssl_cipher; 56 | char *ssl_crl; 57 | char *ssl_crlpath; 58 | char *ssl_key; 59 | char *tls_ciphersuites; 60 | 61 | struct timeval connect_timeout; 62 | struct timeval read_timeout; 63 | struct timeval write_timeout; 64 | 65 | bool keepalive_enabled; 66 | uint16_t keepalive_idle; 67 | uint16_t keepalive_count; 68 | uint16_t keepalive_interval; 69 | 70 | bool enable_cleartext_plugin; 71 | 72 | TRILOGY_CAPABILITIES_t flags; 73 | 74 | size_t max_allowed_packet; 75 | } trilogy_sockopt_t; 76 | 77 | typedef struct trilogy_sock_t { 78 | int (*connect_cb)(struct trilogy_sock_t *self); 79 | ssize_t (*read_cb)(struct trilogy_sock_t *self, void *buf, size_t nread); 80 | ssize_t (*write_cb)(struct trilogy_sock_t *self, const void *buf, size_t nwrite); 81 | int (*wait_cb)(struct trilogy_sock_t *self, trilogy_wait_t wait); 82 | int (*shutdown_cb)(struct trilogy_sock_t *self); 83 | int (*close_cb)(struct trilogy_sock_t *self); 84 | int (*fd_cb)(struct trilogy_sock_t *self); 85 | 86 | trilogy_sockopt_t opts; 87 | } trilogy_sock_t; 88 | 89 | static inline int trilogy_sock_connect(trilogy_sock_t *sock) { return sock->connect_cb(sock); } 90 | 91 | static inline ssize_t trilogy_sock_read(trilogy_sock_t *sock, void *buf, size_t n) 92 | { 93 | return sock->read_cb(sock, buf, n); 94 | } 95 | 96 | static inline ssize_t trilogy_sock_write(trilogy_sock_t *sock, const void *buf, size_t n) 97 | { 98 | return sock->write_cb(sock, buf, n); 99 | } 100 | 101 | static inline int trilogy_sock_wait(trilogy_sock_t *sock, trilogy_wait_t wait) { return sock->wait_cb(sock, wait); } 102 | 103 | static inline int trilogy_sock_wait_read(trilogy_sock_t *sock) { return sock->wait_cb(sock, TRILOGY_WAIT_READ); } 104 | 105 | static inline int trilogy_sock_wait_write(trilogy_sock_t *sock) { return sock->wait_cb(sock, TRILOGY_WAIT_WRITE); } 106 | 107 | static inline int trilogy_sock_shutdown(trilogy_sock_t *sock) { return sock->shutdown_cb(sock); } 108 | 109 | static inline int trilogy_sock_close(trilogy_sock_t *sock) { return sock->close_cb(sock); } 110 | 111 | static inline int trilogy_sock_fd(trilogy_sock_t *sock) { return sock->fd_cb(sock); } 112 | 113 | trilogy_sock_t *trilogy_sock_new(const trilogy_sockopt_t *opts); 114 | int trilogy_sock_resolve(trilogy_sock_t *raw); 115 | int trilogy_sock_upgrade_ssl(trilogy_sock_t *raw); 116 | 117 | /* trilogy_sock_check - Verify if the socket is still alive and not disconnected. 118 | * 119 | * This check is very cheap to do and reduces the number of errors when for 120 | * example the server has restarted since the connection was opened. In connection 121 | * pooling implementations, this check can be done before the connection is 122 | * returned. 123 | * 124 | * raw - A connected trilogy_sock_t pointer. Using a disconnected trilogy_sock_t is undefined. 125 | * 126 | * Return values: 127 | * TRILOGY_OK - The connection is alive on the client side and can be. 128 | * TRILOGY_CLOSED_CONNECTION - The connection is closed. 129 | * TRILOGY_SYSERR - A system error occurred, check errno. 130 | */ 131 | int trilogy_sock_check(trilogy_sock_t *raw); 132 | 133 | #endif 134 | -------------------------------------------------------------------------------- /test/client/stmt_close_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../test.h" 8 | 9 | #include "trilogy/blocking.h" 10 | #include "trilogy/client.h" 11 | #include "trilogy/error.h" 12 | 13 | #define do_connect(CONN) \ 14 | do { \ 15 | int err = trilogy_init(CONN); \ 16 | ASSERT_OK(err); \ 17 | err = trilogy_connect(CONN, get_connopt()); \ 18 | ASSERT_OK(err); \ 19 | } while (0) 20 | 21 | TEST test_stmt_close_send() 22 | { 23 | trilogy_conn_t conn; 24 | 25 | do_connect(&conn); 26 | 27 | const char *sql = "SELECT ?"; 28 | size_t sql_len = strlen(sql); 29 | 30 | int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); 31 | while (err == TRILOGY_AGAIN) { 32 | err = wait_writable(&conn); 33 | ASSERT_OK(err); 34 | 35 | err = trilogy_flush_writes(&conn); 36 | } 37 | ASSERT_OK(err); 38 | 39 | trilogy_stmt_t stmt; 40 | 41 | err = trilogy_stmt_prepare_recv(&conn, &stmt); 42 | while (err == TRILOGY_AGAIN) { 43 | err = wait_readable(&conn); 44 | ASSERT_OK(err); 45 | 46 | err = trilogy_stmt_prepare_recv(&conn, &stmt); 47 | } 48 | ASSERT_OK(err); 49 | 50 | ASSERT_EQ(1, stmt.parameter_count); 51 | 52 | trilogy_column_packet_t params[1]; 53 | 54 | for (uint64_t i = 0; i < stmt.parameter_count; i++) { 55 | trilogy_column_packet_t *param = ¶ms[i]; 56 | 57 | err = trilogy_read_full_column(&conn, param); 58 | 59 | if (err < 0) { 60 | return err; 61 | } 62 | } 63 | 64 | ASSERT_EQ(1, stmt.column_count); 65 | 66 | trilogy_column_packet_t column_defs[1]; 67 | 68 | for (uint64_t i = 0; i < stmt.column_count; i++) { 69 | trilogy_column_packet_t *column = &column_defs[i]; 70 | 71 | err = trilogy_read_full_column(&conn, column); 72 | 73 | if (err < 0) { 74 | return err; 75 | } 76 | } 77 | 78 | err = trilogy_stmt_close_send(&conn, &stmt); 79 | while (err == TRILOGY_AGAIN) { 80 | err = wait_writable(&conn); 81 | ASSERT_OK(err); 82 | 83 | err = trilogy_flush_writes(&conn); 84 | } 85 | ASSERT_OK(err); 86 | 87 | trilogy_free(&conn); 88 | PASS(); 89 | } 90 | 91 | TEST test_stmt_close_send_closed_socket() 92 | { 93 | trilogy_conn_t conn; 94 | 95 | do_connect(&conn); 96 | 97 | const char *sql = "SELECT ?"; 98 | size_t sql_len = strlen(sql); 99 | 100 | int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); 101 | while (err == TRILOGY_AGAIN) { 102 | err = wait_writable(&conn); 103 | ASSERT_OK(err); 104 | 105 | err = trilogy_flush_writes(&conn); 106 | } 107 | ASSERT_OK(err); 108 | 109 | trilogy_stmt_t stmt; 110 | 111 | err = trilogy_stmt_prepare_recv(&conn, &stmt); 112 | while (err == TRILOGY_AGAIN) { 113 | err = wait_readable(&conn); 114 | ASSERT_OK(err); 115 | 116 | err = trilogy_stmt_prepare_recv(&conn, &stmt); 117 | } 118 | ASSERT_OK(err); 119 | 120 | ASSERT_EQ(1, stmt.parameter_count); 121 | 122 | trilogy_column_packet_t params[1]; 123 | 124 | for (uint64_t i = 0; i < stmt.parameter_count; i++) { 125 | trilogy_column_packet_t *param = ¶ms[i]; 126 | 127 | err = trilogy_read_full_column(&conn, param); 128 | 129 | if (err < 0) { 130 | return err; 131 | } 132 | } 133 | 134 | ASSERT_EQ(1, stmt.column_count); 135 | 136 | trilogy_column_packet_t column_defs[1]; 137 | 138 | for (uint64_t i = 0; i < stmt.column_count; i++) { 139 | trilogy_column_packet_t *column = &column_defs[i]; 140 | 141 | err = trilogy_read_full_column(&conn, column); 142 | 143 | if (err < 0) { 144 | return err; 145 | } 146 | } 147 | 148 | close_socket(&conn); 149 | 150 | err = trilogy_stmt_close_send(&conn, &stmt); 151 | ASSERT_ERR(TRILOGY_SYSERR, err); 152 | 153 | trilogy_free(&conn); 154 | PASS(); 155 | } 156 | 157 | int client_stmt_close_test() 158 | { 159 | RUN_TEST(test_stmt_close_send); 160 | RUN_TEST(test_stmt_close_send_closed_socket); 161 | 162 | return 0; 163 | } 164 | -------------------------------------------------------------------------------- /test/packet_parser_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/packet_parser.h" 9 | 10 | const uint8_t empty_packet[] = {0x00, 0x00, 0x00, 0x00}; 11 | 12 | const uint8_t multiple_packet_buffer[] = {0x01, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x01, 0x01}; 13 | 14 | const uint8_t handshake_packet[] = {0x4a, 0x00, 0x00, 0x00, 0x0a, 0x35, 0x2e, 0x36, 0x2e, 0x32, 0x37, 0x00, 0xf5, 15 | 0x02, 0x00, 0x00, 0x64, 0x63, 0x47, 0x75, 0x39, 0x5b, 0x40, 0x6d, 0x00, 0xff, 16 | 0xf7, 0x21, 0x02, 0x00, 0x7F, 0x80, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 17 | 0x00, 0x00, 0x00, 0x00, 0x25, 0x6e, 0x7b, 0x3f, 0x68, 0x6d, 0x6c, 0x56, 0x49, 18 | 0x4c, 0x24, 0x69, 0x00, 0x6d, 0x79, 0x73, 0x71, 0x6c, 0x5f, 0x6e, 0x61, 0x74, 19 | 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x00}; 20 | 21 | static const uint8_t *expected_packet; 22 | static size_t expected_len = 0; 23 | 24 | static size_t packets_received; 25 | static size_t last_packet_len; 26 | static char last_packet[512]; 27 | 28 | static void reset_last_packet() 29 | { 30 | packets_received = 0; 31 | last_packet_len = 0; 32 | } 33 | 34 | static int on_packet_begin(void *opaque) 35 | { 36 | (void)opaque; 37 | 38 | return TRILOGY_OK; 39 | } 40 | 41 | static int on_packet_data(void *opaque, const uint8_t *data, size_t len) 42 | { 43 | (void)opaque; 44 | (void)len; 45 | 46 | packets_received++; 47 | last_packet_len = len; 48 | memcpy(last_packet, data, len); 49 | 50 | return TRILOGY_OK; 51 | } 52 | 53 | static int on_packet_end(void *opaque) 54 | { 55 | (void)opaque; 56 | 57 | // pause packet parsing so we can return the packet we just read to the 58 | // caller 59 | return 1; 60 | } 61 | 62 | static trilogy_packet_parser_callbacks_t packet_parser_callbacks = { 63 | .on_packet_begin = on_packet_begin, 64 | .on_packet_data = on_packet_data, 65 | .on_packet_end = on_packet_end, 66 | }; 67 | 68 | TEST test_parse_packet() 69 | { 70 | trilogy_packet_parser_t parser; 71 | trilogy_packet_parser_init(&parser, &packet_parser_callbacks); 72 | 73 | int err; 74 | 75 | expected_packet = handshake_packet; 76 | expected_len = sizeof(handshake_packet) - 4; 77 | 78 | reset_last_packet(); 79 | 80 | size_t num_parsed = trilogy_packet_parser_execute(&parser, handshake_packet, sizeof(handshake_packet), &err); 81 | 82 | ASSERT_EQ(sizeof(handshake_packet), num_parsed); 83 | ASSERT_EQ(1, packets_received); 84 | ASSERT_EQ(expected_len, last_packet_len); 85 | ASSERT_MEM_EQ(expected_packet + 4, last_packet, expected_len); 86 | 87 | PASS(); 88 | } 89 | 90 | TEST test_parse_partial_packet() 91 | { 92 | trilogy_packet_parser_t parser; 93 | trilogy_packet_parser_init(&parser, &packet_parser_callbacks); 94 | 95 | int err; 96 | 97 | expected_packet = handshake_packet; 98 | expected_len = sizeof(handshake_packet) - 14; 99 | 100 | reset_last_packet(); 101 | 102 | size_t num_parsed = trilogy_packet_parser_execute(&parser, handshake_packet, sizeof(handshake_packet) - 10, &err); 103 | 104 | ASSERT_EQ(sizeof(handshake_packet) - 10, num_parsed); 105 | ASSERT_EQ(1, packets_received); 106 | ASSERT_EQ(expected_len, last_packet_len); 107 | ASSERT_MEM_EQ(expected_packet + 4, last_packet, expected_len); 108 | 109 | PASS(); 110 | } 111 | 112 | TEST test_parse_empty_packet() 113 | { 114 | trilogy_packet_parser_t parser; 115 | trilogy_packet_parser_init(&parser, &packet_parser_callbacks); 116 | 117 | int err; 118 | 119 | expected_packet = empty_packet; 120 | expected_len = 0; 121 | 122 | reset_last_packet(); 123 | 124 | size_t num_parsed = trilogy_packet_parser_execute(&parser, empty_packet, sizeof(empty_packet), &err); 125 | 126 | ASSERT_EQ(4, num_parsed); 127 | ASSERT_EQ(0, packets_received); 128 | 129 | PASS(); 130 | } 131 | 132 | TEST test_parse_multi_packet_buffer() 133 | { 134 | trilogy_packet_parser_t parser; 135 | trilogy_packet_parser_init(&parser, &packet_parser_callbacks); 136 | 137 | int err; 138 | 139 | expected_packet = multiple_packet_buffer; 140 | expected_len = 1; 141 | 142 | reset_last_packet(); 143 | 144 | size_t num_parsed = 145 | trilogy_packet_parser_execute(&parser, multiple_packet_buffer, sizeof(multiple_packet_buffer), &err); 146 | 147 | ASSERT_EQ(5, num_parsed); 148 | ASSERT_EQ(1, packets_received); 149 | ASSERT_EQ(expected_len, last_packet_len); 150 | ASSERT_MEM_EQ(expected_packet + 4, last_packet, expected_len); 151 | 152 | PASS(); 153 | } 154 | 155 | int packet_parser_test() 156 | { 157 | RUN_TEST(test_parse_packet); 158 | RUN_TEST(test_parse_partial_packet); 159 | RUN_TEST(test_parse_empty_packet); 160 | RUN_TEST(test_parse_multi_packet_buffer); 161 | 162 | return 0; 163 | } 164 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). 5 | 6 | ## Unreleased 7 | 8 | ## 2.9.0 9 | 10 | ### Added 11 | 12 | - Add support for the VECTOR type. #194 13 | 14 | ### Changed 15 | - Mark C-extension as Ractor-safe. #192 16 | 17 | ### Fixed 18 | 19 | - Fix bug allowing queries larger than the configured `max_allowed_packet`. #203 20 | - Restore error message context that was accidentally removed. #187 21 | 22 | ## 2.8.1 23 | 24 | ### Fixed 25 | 26 | - Fix "Got packets out of order" errors on connect. #184 27 | 28 | ## 2.8.0 29 | 30 | ### Added 31 | 32 | - Add support for `caching_sha2_password` when using MySQL 8.0+. #165 33 | - Add support for `mysql_clear_password` client plugin. #171 34 | - Add connection `#check`. #154 35 | 36 | ### Fixed 37 | 38 | - Use `connect_timeout` for initial connection. #159 39 | 40 | ## 2.7.0 41 | 42 | ### Changed 43 | 44 | - `Trilogy::SyscallError::*` errors now use the standard `Module#===` implementation #143 45 | - `Trilogy::TimeoutError` no longer inherits from `Errno::ETIMEDOUT` #143 46 | - Deprecated `Trilogy::ConnectionRefusedError` and `Trilogy::ConnectionResetError`, 47 | replaced by `Trilogy::SyscallError::ECONNREFUSED` and `Trilogy::SyscallError::ECONNRESET` #143 48 | 49 | ## 2.6.1 50 | 51 | ### Fixed 52 | 53 | - Report `EOFError: TRILOGY_CLOSED_CONNECTION` for `SSL_ERROR_ZERO_RETURN` 54 | - `write_timeout` on connection now raises `Trilogy::TimeoutError` (previously it raised `EINPROGRESS`) 55 | - Fix memory leak on failed connections 56 | - Fix memory leak when connecting to unix socket 57 | 58 | ## 2.6.0 59 | 60 | ### Changed 61 | 62 | - `TCP_NODELAY` is enabled on all TCP connections #122 63 | - `Trilogy::EOFError` is now raised for `TRILOGY_CLOSED_CONNECTION` instead 64 | of the generic `Trilogy::QueryError` #118 65 | - `Trilogy::SyscallError` now inherits `Trilogy::ConnectionError` #118 66 | 67 | ## 2.5.0 68 | 69 | ### Fixed 70 | - Fix build with LibreSSL #73 71 | - Fix build error on FreeBSD #82 72 | - Fix Trilogy.new with no arguments #94 73 | - Fix issues with OpenSSL #95 #112 74 | - Avoid closing connections that are not connected 75 | - Always close socket on error 76 | - Clear error queue after close 77 | - Clear error queue before each operation to defend against other misbehaving libraries 78 | - Close connection if interrupted by a Ruby timeout #110 79 | - Correctly cast time of 00:00:00 #97 80 | 81 | ### Added 82 | - Add option to disable multi_result capability #77 83 | - Add option to validate max_allowed_packet #84 84 | - Add binary protocol/prepared statement support to the C library #3 85 | - Cast port option to integer #100 86 | - Add select_db as an alias for change_db #101 87 | 88 | ## 2.4.1 89 | 90 | ### Fixed 91 | - Set error code on deadlock timeout errors #69 92 | 93 | ### Changed 94 | - Remove superfluous `multi_result` connection option #68 95 | 96 | ## 2.4.0 97 | 98 | ### Added 99 | - Implement set_option functionality, and add #set_server_option method to the Ruby binding. #52 100 | - Implement multi-result support on the Ruby binding; TRILOGY_CAPABILITIES_MULTI_RESULTS flag enabled by default. #57 101 | - Add `TRILOGY_FLAGS_CAST_ALL_DECIMALS_TO_BIGDECIMALS` flag, which enforces casting to BigDecimal even for column types 102 | without decimal digits. #59 103 | - Implement #discard to close child connections without impacting parent. #65 104 | 105 | ### Fixed 106 | - Fix msec values for time columns. #61 107 | 108 | ### Changed 109 | - (BREAKING CHANGE) C API `#trilogy_build_auth_packet` accepts encoding option now. The Ruby binding for the 110 | Trilogy client can now accept an `:encoding` option, which will tell the connection to use the specified encoding, 111 | and will ensure that outgoing query strings are transcoded appropriately. If no encoding is supplied, 112 | utf8mb4 is used by default. #64 113 | - All SystemCallErrors classified as `Trilogy::Error`. #63 114 | 115 | ## 2.3.0 116 | 117 | ### Added 118 | - Implement multi-statement support on the Ruby binding. #35 119 | - Add `#connection_options` method to Ruby binding. #48 120 | - Introduced a variety of more detailed error classes. #15 and #41 121 | 122 | ### Changed 123 | - Cast to Integer rather than BigDecimal for column types without decimal digits. #37 124 | - Error codes 1044-1045, 1064, 1152-1161, 1205, and 1927 recategorized as `ConnectionError`. #15 125 | 126 | ## 2.2.0 127 | 128 | ### Added 129 | - Add `#closed?` method to Ruby binding. #30 130 | 131 | ### Changed 132 | - Support Ruby's `memsize` callback 133 | 134 | ### Fixed 135 | - Ruby: Fixed a memory leak on freed connections 136 | 137 | ## 2.1.2 138 | 139 | 2022-10-04 140 | 141 | ### Fixed 142 | 143 | - Don't scramble passwords when responding to auth switch request. #21 144 | - Allow connecting to MariaDB. #22 145 | 146 | ## 2.1.1 147 | 148 | 2022-06-06 149 | 150 | ### Fixed 151 | 152 | - Verify exact length of auth_data_len 153 | 154 | ## 2.1.0 155 | 156 | 2022-03-11 157 | 158 | ### Added 159 | 160 | - Adjust read and write timeouts after connecting. #10 161 | - Include `affected_rows` and `last_insert_id` on the result. #17 162 | 163 | ### Changed 164 | 165 | - Re-use existing interned strings where possible. #12 166 | 167 | ### Fixed 168 | 169 | - Clang 13 warnings. #13 and #14 170 | 171 | ## 2.0.0 172 | 173 | 2021-12-15 174 | 175 | ### Added 176 | 177 | - Initial release of the Trilogy client. 178 | -------------------------------------------------------------------------------- /test/protocol/parsing/handshake_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "../../test.h" 6 | 7 | #include "trilogy/error.h" 8 | #include "trilogy/protocol.h" 9 | 10 | static const uint8_t valid_handshake_packet[] = { 11 | 0x0a, 0x35, 0x2e, 0x36, 0x2e, 0x32, 0x37, 0x00, 0xae, 0x01, 0x00, 0x00, 0x36, 0x67, 0x28, 0x30, 0x57, 0x45, 0x35, 12 | 0x79, 0x00, 0xff, 0xf7, 0x21, 0x02, 0x00, 0x7f, 0x80, 0x15, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 13 | 0x00, 0x23, 0x40, 0x57, 0x76, 0x6a, 0x32, 0x59, 0x48, 0x3f, 0x43, 0x71, 0x2f, 0x00, 0x6d, 0x79, 0x73, 0x71, 0x6c, 14 | 0x5f, 0x6e, 0x61, 0x74, 0x69, 0x76, 0x65, 0x5f, 0x70, 0x61, 0x73, 0x73, 0x77, 0x6f, 0x72, 0x64, 0x00}; 15 | 16 | TEST test_parse_handshake() 17 | { 18 | trilogy_handshake_t packet; 19 | 20 | uint8_t handshake_packet[sizeof(valid_handshake_packet)]; 21 | memcpy(handshake_packet, valid_handshake_packet, sizeof(valid_handshake_packet)); 22 | 23 | int err = trilogy_parse_handshake_packet(handshake_packet, sizeof(handshake_packet), &packet); 24 | ASSERT_OK(err); 25 | 26 | ASSERT_EQ(0x0a, packet.proto_version); 27 | 28 | const char expected_version[] = "5.6.27"; 29 | ASSERT_MEM_EQ(packet.server_version, expected_version, sizeof(expected_version)); 30 | 31 | ASSERT_EQ(430, packet.conn_id); 32 | 33 | const char expected_scramble[] = "6g(0WE5y#@Wvj2YH?Cq/"; 34 | ASSERT_MEM_EQ(packet.scramble, expected_scramble, sizeof(expected_scramble)); 35 | 36 | ASSERT_EQ(0x807FF7FF, packet.capabilities); 37 | 38 | ASSERT_EQ(TRILOGY_CHARSET_UTF8_GENERAL_CI, packet.server_charset); 39 | 40 | ASSERT_EQ(0x00000002, packet.server_status); 41 | 42 | const char expected_auth_plugin[] = "mysql_native_password"; 43 | ASSERT_MEM_EQ(packet.auth_plugin, expected_auth_plugin, sizeof(expected_auth_plugin)); 44 | 45 | PASS(); 46 | } 47 | 48 | TEST test_parse_handshake_truncated() 49 | { 50 | uint8_t handshake_packet[sizeof(valid_handshake_packet)]; 51 | memcpy(handshake_packet, valid_handshake_packet, sizeof(valid_handshake_packet)); 52 | 53 | trilogy_handshake_t packet; 54 | 55 | int err = trilogy_parse_handshake_packet(handshake_packet, sizeof(handshake_packet) - 10, &packet); 56 | ASSERT_ERR(TRILOGY_TRUNCATED_PACKET, err); 57 | 58 | PASS(); 59 | } 60 | 61 | TEST test_parse_handshake_invalid_protocol() 62 | { 63 | uint8_t handshake_packet[sizeof(valid_handshake_packet)]; 64 | memcpy(handshake_packet, valid_handshake_packet, sizeof(valid_handshake_packet)); 65 | 66 | trilogy_handshake_t packet; 67 | handshake_packet[0] = 0xff; 68 | int err = trilogy_parse_handshake_packet(handshake_packet, sizeof(handshake_packet), &packet); 69 | ASSERT_ERR(TRILOGY_PROTOCOL_VIOLATION, err); 70 | 71 | memcpy(handshake_packet, valid_handshake_packet, sizeof(valid_handshake_packet)); 72 | handshake_packet[21] = 0x00; 73 | handshake_packet[22] = 0x00; 74 | err = trilogy_parse_handshake_packet(handshake_packet, sizeof(handshake_packet), &packet); 75 | ASSERT_ERR(TRILOGY_PROTOCOL_VIOLATION, err); 76 | 77 | PASS(); 78 | } 79 | 80 | TEST test_parse_handshake_no_protocol41_flag() 81 | { 82 | uint8_t handshake_packet[sizeof(valid_handshake_packet)]; 83 | memcpy(handshake_packet, valid_handshake_packet, sizeof(valid_handshake_packet)); 84 | 85 | trilogy_handshake_t packet; 86 | 87 | handshake_packet[21] = 0x00; 88 | handshake_packet[22] = 0x00; 89 | int err = trilogy_parse_handshake_packet(handshake_packet, sizeof(handshake_packet), &packet); 90 | ASSERT_ERR(TRILOGY_PROTOCOL_VIOLATION, err); 91 | 92 | PASS(); 93 | } 94 | 95 | TEST test_parse_handshake_no_secure_connection_flag() 96 | { 97 | uint8_t handshake_packet[sizeof(valid_handshake_packet)]; 98 | memcpy(handshake_packet, valid_handshake_packet, sizeof(valid_handshake_packet)); 99 | 100 | trilogy_handshake_t packet; 101 | 102 | handshake_packet[21] &= (~TRILOGY_CAPABILITIES_SECURE_CONNECTION) & 0xff; 103 | handshake_packet[22] &= ((~TRILOGY_CAPABILITIES_SECURE_CONNECTION) >> 8) & 0xff; 104 | int err = trilogy_parse_handshake_packet(handshake_packet, sizeof(handshake_packet), &packet); 105 | ASSERT_ERR(TRILOGY_PROTOCOL_VIOLATION, err); 106 | 107 | PASS(); 108 | } 109 | 110 | TEST test_parse_handshake_invalid_null_filler() 111 | { 112 | uint8_t handshake_packet[sizeof(valid_handshake_packet)]; 113 | memcpy(handshake_packet, valid_handshake_packet, sizeof(valid_handshake_packet)); 114 | 115 | trilogy_handshake_t packet; 116 | 117 | handshake_packet[20] = 0xff; 118 | int err = trilogy_parse_handshake_packet(handshake_packet, sizeof(handshake_packet), &packet); 119 | ASSERT_ERR(TRILOGY_PROTOCOL_VIOLATION, err); 120 | 121 | PASS(); 122 | } 123 | 124 | TEST test_parse_handshake_ignores_reserved_filler() 125 | { 126 | uint8_t handshake_packet[sizeof(valid_handshake_packet)]; 127 | memcpy(handshake_packet, valid_handshake_packet, sizeof(valid_handshake_packet)); 128 | 129 | trilogy_handshake_t packet; 130 | 131 | handshake_packet[29] = 0xff; 132 | int err = trilogy_parse_handshake_packet(handshake_packet, sizeof(handshake_packet), &packet); 133 | ASSERT_OK(err); 134 | 135 | PASS(); 136 | } 137 | 138 | int parse_handshake_test() 139 | { 140 | RUN_TEST(test_parse_handshake); 141 | RUN_TEST(test_parse_handshake_truncated); 142 | RUN_TEST(test_parse_handshake_invalid_protocol); 143 | RUN_TEST(test_parse_handshake_no_protocol41_flag); 144 | RUN_TEST(test_parse_handshake_no_secure_connection_flag); 145 | RUN_TEST(test_parse_handshake_invalid_null_filler); 146 | RUN_TEST(test_parse_handshake_ignores_reserved_filler); 147 | 148 | return 0; 149 | } 150 | -------------------------------------------------------------------------------- /test/runner.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "test.h" 6 | 7 | GREATEST_MAIN_DEFS(); 8 | 9 | typedef int (*trilogy_test_t)(); 10 | 11 | static trilogy_sockopt_t connopt; 12 | 13 | const trilogy_sockopt_t *get_connopt(void) { return &connopt; } 14 | 15 | #define ALL_SUITES(SUITE) \ 16 | SUITE(reader_test) \ 17 | SUITE(buffer_test) \ 18 | SUITE(builder_test) \ 19 | SUITE(error_test) \ 20 | SUITE(packet_parser_test) \ 21 | SUITE(charset_test) \ 22 | SUITE(blocking_test) \ 23 | SUITE(socket_test) \ 24 | SUITE(parse_handshake_test) \ 25 | SUITE(parse_ok_packet_test) \ 26 | SUITE(parse_eof_packet_test) \ 27 | SUITE(parse_result_packet_test) \ 28 | SUITE(parse_error_packet_test) \ 29 | SUITE(parse_column_packet_test) \ 30 | SUITE(parse_row_packet_test) \ 31 | SUITE(build_auth_packet_test) \ 32 | SUITE(build_change_db_packet_test) \ 33 | SUITE(build_ping_packet_test) \ 34 | SUITE(build_quit_packet_test) \ 35 | SUITE(build_set_option_packet_test) \ 36 | SUITE(build_query_packet_test) \ 37 | SUITE(stmt_prepare_packet_test) \ 38 | SUITE(stmt_bind_data_packet_test) \ 39 | SUITE(stmt_execute_packet_test) \ 40 | SUITE(stmt_reset_packet_test) \ 41 | SUITE(stmt_close_packet_test) \ 42 | SUITE(client_connect_test) \ 43 | SUITE(client_escape_test) \ 44 | SUITE(client_auth_test) \ 45 | SUITE(client_change_db_test) \ 46 | SUITE(client_set_option_test) \ 47 | SUITE(client_ping_test) \ 48 | SUITE(client_stmt_prepare_test) \ 49 | SUITE(client_stmt_execute_test) \ 50 | SUITE(client_stmt_reset_test) \ 51 | SUITE(client_stmt_close_test) \ 52 | 53 | #define XX(name) extern int name(); 54 | ALL_SUITES(XX) 55 | #undef XX 56 | 57 | int main(int argc, char **argv) 58 | { 59 | GREATEST_MAIN_BEGIN(); 60 | 61 | connopt.hostname = getenv("MYSQL_HOST"); 62 | if (connopt.hostname == NULL) { 63 | connopt.hostname = "127.0.0.1"; 64 | } 65 | 66 | const char *port = getenv("MYSQL_PORT"); 67 | if (port != NULL) { 68 | connopt.port = atoi(port); 69 | } 70 | 71 | if (connopt.port == 0) { 72 | connopt.port = 3306; 73 | } 74 | 75 | connopt.username = getenv("MYSQL_USER"); 76 | if (connopt.username == NULL) { 77 | connopt.username = "root"; 78 | } 79 | 80 | connopt.password = getenv("MYSQL_PASS"); 81 | if (connopt.password != NULL) { 82 | connopt.password_len = strlen(connopt.password); 83 | } 84 | 85 | connopt.database = getenv("MYSQL_DB"); 86 | if (connopt.database == NULL) { 87 | connopt.database = "test"; 88 | } 89 | 90 | #define XX(name) name(); 91 | ALL_SUITES(XX) 92 | #undef XX 93 | 94 | GREATEST_MAIN_END(); 95 | } 96 | -------------------------------------------------------------------------------- /test/client/auth_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../test.h" 8 | 9 | #include "trilogy/blocking.h" 10 | #include "trilogy/client.h" 11 | #include "trilogy/error.h" 12 | 13 | #define do_connect(CONN, HANDSHAKE) \ 14 | do { \ 15 | int err = trilogy_init(CONN); \ 16 | ASSERT_OK(err); \ 17 | err = trilogy_connect_send(CONN, get_connopt()); \ 18 | ASSERT_OK(err); \ 19 | err = trilogy_connect_recv(CONN, HANDSHAKE); \ 20 | while (err == TRILOGY_AGAIN) { \ 21 | err = wait_readable(CONN); \ 22 | ASSERT_OK(err); \ 23 | err = trilogy_connect_recv(CONN, HANDSHAKE); \ 24 | } \ 25 | ASSERT_OK(err); \ 26 | } while (0); 27 | 28 | TEST test_auth_send() 29 | { 30 | trilogy_conn_t conn; 31 | trilogy_handshake_t handshake; 32 | 33 | do_connect(&conn, &handshake); 34 | 35 | int err = trilogy_auth_send(&conn, &handshake); 36 | while (err == TRILOGY_AGAIN) { 37 | err = wait_writable(&conn); 38 | ASSERT_OK(err); 39 | 40 | err = trilogy_flush_writes(&conn); 41 | } 42 | ASSERT_OK(err); 43 | 44 | trilogy_free(&conn); 45 | PASS(); 46 | } 47 | 48 | TEST test_auth_send_closed_socket() 49 | { 50 | trilogy_conn_t conn; 51 | trilogy_handshake_t handshake; 52 | 53 | do_connect(&conn, &handshake); 54 | close_socket(&conn); 55 | 56 | int err = trilogy_auth_send(&conn, &handshake); 57 | ASSERT_ERR(TRILOGY_SYSERR, err); 58 | 59 | trilogy_free(&conn); 60 | PASS(); 61 | } 62 | 63 | TEST test_auth_recv() 64 | { 65 | trilogy_conn_t conn; 66 | trilogy_handshake_t handshake; 67 | 68 | do_connect(&conn, &handshake); 69 | 70 | int err = trilogy_auth_send(&conn, &handshake); 71 | while (err == TRILOGY_AGAIN) { 72 | err = wait_writable(&conn); 73 | ASSERT_OK(err); 74 | 75 | err = trilogy_flush_writes(&conn); 76 | } 77 | ASSERT_OK(err); 78 | 79 | err = trilogy_auth_recv(&conn, &handshake); 80 | while (err == TRILOGY_AGAIN) { 81 | err = wait_readable(&conn); 82 | ASSERT_OK(err); 83 | 84 | err = trilogy_auth_recv(&conn, &handshake); 85 | } 86 | if (err == TRILOGY_AUTH_SWITCH) { 87 | err = trilogy_auth_switch_send(&conn, &handshake); 88 | 89 | while (err == TRILOGY_AGAIN) { 90 | err = wait_readable(&conn); 91 | ASSERT_OK(err); 92 | 93 | err = trilogy_auth_recv(&conn, &handshake); 94 | } 95 | } 96 | ASSERT_OK(err); 97 | 98 | trilogy_free(&conn); 99 | PASS(); 100 | } 101 | 102 | TEST test_auth_recv_closed_socket() 103 | { 104 | trilogy_conn_t conn; 105 | trilogy_handshake_t handshake; 106 | 107 | do_connect(&conn, &handshake); 108 | 109 | int err = trilogy_auth_send(&conn, &handshake); 110 | while (err == TRILOGY_AGAIN) { 111 | err = wait_writable(&conn); 112 | ASSERT_OK(err); 113 | 114 | err = trilogy_flush_writes(&conn); 115 | } 116 | ASSERT_OK(err); 117 | 118 | close_socket(&conn); 119 | 120 | err = trilogy_auth_recv(&conn, &handshake); 121 | ASSERT_ERR(TRILOGY_SYSERR, err); 122 | 123 | trilogy_free(&conn); 124 | PASS(); 125 | } 126 | 127 | TEST test_ssl_handshake() 128 | { 129 | trilogy_conn_t conn; 130 | trilogy_handshake_t handshake; 131 | 132 | if (getenv("MYSQL_SSL") == NULL) 133 | SKIP(); 134 | 135 | do_connect(&conn, &handshake); 136 | int err = trilogy_ssl_request_send(&conn); 137 | while (err == TRILOGY_AGAIN) { 138 | err = wait_writable(&conn); 139 | ASSERT_OK(err); 140 | err = trilogy_flush_writes(&conn); 141 | } 142 | ASSERT_OK(err); 143 | err = trilogy_sock_upgrade_ssl(conn.socket); 144 | ASSERT_OK(err); 145 | err = trilogy_auth_send(&conn, &handshake); 146 | while (err == TRILOGY_AGAIN) { 147 | err = wait_writable(&conn); 148 | ASSERT_OK(err); 149 | err = trilogy_flush_writes(&conn); 150 | } 151 | ASSERT_OK(err); 152 | err = trilogy_auth_recv(&conn, &handshake); 153 | while (err == TRILOGY_AGAIN) { 154 | err = wait_readable(&conn); 155 | ASSERT_OK(err); 156 | err = trilogy_auth_recv(&conn, &handshake); 157 | } 158 | if (err == TRILOGY_AUTH_SWITCH) { 159 | err = trilogy_auth_switch_send(&conn, &handshake); 160 | 161 | while (err == TRILOGY_AGAIN) { 162 | err = wait_readable(&conn); 163 | ASSERT_OK(err); 164 | 165 | err = trilogy_auth_recv(&conn, &handshake); 166 | } 167 | } 168 | ASSERT_OK(err); 169 | trilogy_free(&conn); 170 | PASS(); 171 | } 172 | 173 | int client_auth_test() 174 | { 175 | RUN_TEST(test_auth_send); 176 | RUN_TEST(test_auth_send_closed_socket); 177 | RUN_TEST(test_auth_recv); 178 | RUN_TEST(test_auth_recv_closed_socket); 179 | RUN_TEST(test_ssl_handshake); 180 | 181 | return 0; 182 | } 183 | -------------------------------------------------------------------------------- /contrib/ruby/test/test_helper.rb: -------------------------------------------------------------------------------- 1 | require "trilogy" 2 | require "socket" 3 | require "timeout" 4 | 5 | require "minitest/autorun" 6 | 7 | $LOAD_PATH.unshift File.expand_path("../../lib", __FILE__) 8 | $LOAD_PATH.unshift File.expand_path("../", __FILE__) 9 | 10 | if GC.respond_to?(:verify_compaction_references) 11 | # This method was added in Ruby 3.0.0. Calling it this way asks the GC to 12 | # move objects around, helping to find object movement bugs. 13 | if Gem::Version::new(RUBY_VERSION) >= Gem::Version::new("3.2.0") 14 | # double_heap is deprecated and expand_heap is the updated argument. This change 15 | # was introduced in: 16 | # https://github.com/ruby/ruby/commit/a6dd859affc42b667279e513bb94fb75cfb133c1 17 | GC.verify_compaction_references(expand_heap: true, toward: :empty) 18 | else 19 | GC.verify_compaction_references(double_heap: true, toward: :empty) 20 | end 21 | end 22 | 23 | class TrilogyTest < Minitest::Test 24 | DEFAULT_HOST = (ENV["MYSQL_HOST"] || "127.0.0.1").freeze 25 | DEFAULT_PORT = (port = ENV["MYSQL_PORT"].to_i) && port != 0 ? port : 3306 26 | DEFAULT_USER = (ENV["MYSQL_USER"] || "root").freeze 27 | DEFAULT_PASS = ENV["MYSQL_PASS"].freeze 28 | 29 | def assert_equal_timestamp(time1, time2) 30 | assert_equal time1.to_i, time2.to_i 31 | assert_equal time1.utc_offset, time2.utc_offset 32 | end 33 | 34 | def allocations 35 | before = GC.stat :total_allocated_objects 36 | yield 37 | after = GC.stat :total_allocated_objects 38 | after - before 39 | end 40 | 41 | def new_tcp_client(opts = {}) 42 | defaults = { 43 | host: DEFAULT_HOST, 44 | port: DEFAULT_PORT, 45 | username: DEFAULT_USER, 46 | password: DEFAULT_PASS, 47 | ssl: true, 48 | ssl_mode: Trilogy::SSL_PREFERRED_NOVERIFY, 49 | tls_min_version: Trilogy::TLS_VERSION_12, 50 | }.merge(opts) 51 | 52 | c = Trilogy.new defaults 53 | c.query "SET SESSION sql_mode = ''" 54 | c 55 | end 56 | 57 | def new_unix_client(socket, opts = {}) 58 | defaults = { 59 | username: DEFAULT_USER, 60 | password: DEFAULT_PASS, 61 | socket: socket, 62 | }.merge(opts) 63 | 64 | c = Trilogy.new defaults 65 | c.query "SET SESSION sql_mode = ''" 66 | c 67 | end 68 | 69 | @@server_global_variables = Hash.new do |h, k| 70 | client = Trilogy.new( 71 | host: DEFAULT_HOST, 72 | port: DEFAULT_PORT, 73 | username: DEFAULT_USER, 74 | password: DEFAULT_PASS, 75 | ) 76 | name = k 77 | result = client.query("SHOW GLOBAL VARIABLES LIKE '#{client.escape name}'") 78 | if result.count == 0 79 | h[k] = nil 80 | else 81 | h[k] = result.rows[0][1] 82 | end 83 | end 84 | 85 | def server_global_variable(name) 86 | @@server_global_variables[name] 87 | end 88 | 89 | def ensure_closed(socket) 90 | socket.close if socket 91 | end 92 | 93 | def create_and_delete_test_user(opts = {}, &block) 94 | client = new_tcp_client 95 | user_created = create_test_user(client, opts) 96 | yield 97 | ensure 98 | delete_test_user(client, opts) if user_created 99 | ensure_closed client 100 | end 101 | 102 | def create_test_user(client, opts = {}) 103 | username = opts[:username] 104 | password = opts[:password] || "password" 105 | host = opts[:host] || DEFAULT_HOST 106 | auth_plugin = opts[:auth_plugin] 107 | 108 | raise ArgumentError if username.nil? || auth_plugin.nil? 109 | user_exists = client.query("SELECT user FROM mysql.user WHERE user = '#{username}';").rows.first 110 | return if user_exists 111 | 112 | client.query("CREATE USER '#{username}'@'#{host}'") 113 | client.query("GRANT ALL PRIVILEGES ON test.* TO '#{username}'@'#{host}';") 114 | client.query("ALTER USER '#{username}'@'#{host}' IDENTIFIED WITH #{auth_plugin} BY '#{password}';") 115 | client.query("SELECT user FROM mysql.user WHERE user = '#{username}';").rows.first 116 | end 117 | 118 | def delete_test_user(client, opts = {}) 119 | username = opts[:username] || "auth_user" 120 | host = opts[:host] || DEFAULT_HOST 121 | 122 | client.query("DROP USER IF EXISTS '#{username}'@'#{host}'") 123 | end 124 | 125 | def create_test_table(client) 126 | client.change_db "test" 127 | 128 | client.query("DROP TABLE IF EXISTS trilogy_test") 129 | 130 | sql = <<-SQL 131 | CREATE TABLE `trilogy_test` ( 132 | `id` INT(11) NOT NULL AUTO_INCREMENT, 133 | `null_test` VARCHAR(10) DEFAULT NULL, 134 | `bit_test` BIT(64) DEFAULT NULL, 135 | `single_bit_test` BIT(1) DEFAULT NULL, 136 | `tiny_int_test` TINYINT(4) DEFAULT NULL, 137 | `bool_cast_test` TINYINT(1) DEFAULT NULL, 138 | `small_int_test` SMALLINT(6) DEFAULT NULL, 139 | `medium_int_test` MEDIUMINT(9) DEFAULT NULL, 140 | `int_test` INT(11) DEFAULT NULL, 141 | `big_int_test` BIGINT(20) DEFAULT NULL, 142 | `unsigned_big_int_test` BIGINT(20) UNSIGNED DEFAULT NULL, 143 | `float_test` FLOAT(10,3) DEFAULT NULL, 144 | `float_zero_test` FLOAT(10,3) DEFAULT NULL, 145 | `double_test` DOUBLE(10,3) DEFAULT NULL, 146 | `decimal_test` DECIMAL(10,3) DEFAULT NULL, 147 | `decimal_zero_test` DECIMAL(10,3) DEFAULT NULL, 148 | `date_test` DATE DEFAULT NULL, 149 | `date_time_test` DATETIME DEFAULT NULL, 150 | `date_time_with_precision_test` DATETIME(3) DEFAULT NULL, 151 | `time_with_precision_test` TIME(3) DEFAULT NULL, 152 | `timestamp_test` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, 153 | `time_test` TIME DEFAULT NULL, 154 | `year_test` YEAR(4) DEFAULT NULL, 155 | `char_test` CHAR(10) DEFAULT NULL, 156 | `varchar_test` VARCHAR(10) DEFAULT NULL, 157 | `binary_test` BINARY(10) DEFAULT NULL, 158 | `varbinary_test` VARBINARY(10) DEFAULT NULL, 159 | `tiny_blob_test` TINYBLOB, 160 | `tiny_text_test` TINYTEXT, 161 | `blob_test` BLOB, 162 | `text_test` TEXT, 163 | `medium_blob_test` MEDIUMBLOB, 164 | `medium_text_test` MEDIUMTEXT, 165 | `long_blob_test` LONGBLOB, 166 | `long_text_test` LONGTEXT, 167 | `enum_test` ENUM('val1','val2') DEFAULT NULL, 168 | `set_test` SET('val1','val2') DEFAULT NULL, 169 | PRIMARY KEY (`id`) 170 | ) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8; 171 | SQL 172 | 173 | client.query sql 174 | end 175 | 176 | def assert_raises_connection_error(&block) 177 | err = assert_raises(Trilogy::Error, &block) 178 | 179 | if err.is_a?(Trilogy::EOFError) 180 | assert_includes err.message, "TRILOGY_CLOSED_CONNECTION" 181 | elsif err.is_a?(Trilogy::SSLError) 182 | assert_includes err.message, "unexpected eof while reading" 183 | else 184 | assert_instance_of Trilogy::ConnectionResetError, err 185 | end 186 | 187 | err 188 | end 189 | end 190 | -------------------------------------------------------------------------------- /src/reader.c: -------------------------------------------------------------------------------- 1 | #include "trilogy/reader.h" 2 | #include "trilogy/error.h" 3 | 4 | #include 5 | 6 | #define CHECK(bytes) \ 7 | if ((bytes) > (reader->len - reader->pos)) { \ 8 | return TRILOGY_TRUNCATED_PACKET; \ 9 | } 10 | 11 | void trilogy_reader_init(trilogy_reader_t *reader, const uint8_t *buff, size_t len) 12 | { 13 | reader->buff = buff; 14 | reader->len = len; 15 | reader->pos = 0; 16 | } 17 | 18 | static uint8_t next_uint8(trilogy_reader_t *reader) { return reader->buff[reader->pos++]; } 19 | 20 | int trilogy_reader_get_uint8(trilogy_reader_t *reader, uint8_t *out) 21 | { 22 | CHECK(1); 23 | 24 | uint8_t a = next_uint8(reader); 25 | 26 | if (out) { 27 | *out = a; 28 | } 29 | 30 | return TRILOGY_OK; 31 | } 32 | 33 | int trilogy_reader_get_uint16(trilogy_reader_t *reader, uint16_t *out) 34 | { 35 | CHECK(2); 36 | 37 | uint16_t a = next_uint8(reader); 38 | uint16_t b = next_uint8(reader); 39 | 40 | if (out) { 41 | *out = (uint16_t)(a | (b << 8)); 42 | } 43 | 44 | return TRILOGY_OK; 45 | } 46 | 47 | int trilogy_reader_get_uint24(trilogy_reader_t *reader, uint32_t *out) 48 | { 49 | CHECK(3); 50 | 51 | uint32_t a = next_uint8(reader); 52 | uint32_t b = next_uint8(reader); 53 | uint32_t c = next_uint8(reader); 54 | 55 | if (out) { 56 | *out = a | (b << 8) | (c << 16); 57 | } 58 | 59 | return TRILOGY_OK; 60 | } 61 | 62 | int trilogy_reader_get_uint32(trilogy_reader_t *reader, uint32_t *out) 63 | { 64 | CHECK(4); 65 | 66 | uint32_t a = next_uint8(reader); 67 | uint32_t b = next_uint8(reader); 68 | uint32_t c = next_uint8(reader); 69 | uint32_t d = next_uint8(reader); 70 | 71 | if (out) { 72 | *out = a | (b << 8) | (c << 16) | (d << 24); 73 | } 74 | 75 | return TRILOGY_OK; 76 | } 77 | 78 | int trilogy_reader_get_uint64(trilogy_reader_t *reader, uint64_t *out) 79 | { 80 | CHECK(8); 81 | 82 | uint64_t a = next_uint8(reader); 83 | uint64_t b = next_uint8(reader); 84 | uint64_t c = next_uint8(reader); 85 | uint64_t d = next_uint8(reader); 86 | uint64_t e = next_uint8(reader); 87 | uint64_t f = next_uint8(reader); 88 | uint64_t g = next_uint8(reader); 89 | uint64_t h = next_uint8(reader); 90 | 91 | if (out) { 92 | *out = a | (b << 8) | (c << 16) | (d << 24) | (e << 32) | (f << 40) | (g << 48) | (h << 56); 93 | } 94 | 95 | return TRILOGY_OK; 96 | } 97 | 98 | int trilogy_reader_get_float(trilogy_reader_t *reader, float *out) 99 | { 100 | CHECK(4); 101 | 102 | union { 103 | float f; 104 | uint32_t u; 105 | } float_val; 106 | 107 | int rc = trilogy_reader_get_uint32(reader, &float_val.u); 108 | if (rc != TRILOGY_OK) { 109 | return rc; 110 | } 111 | 112 | *out = float_val.f; 113 | 114 | return TRILOGY_OK; 115 | } 116 | 117 | int trilogy_reader_get_double(trilogy_reader_t *reader, double *out) 118 | { 119 | CHECK(8); 120 | 121 | union { 122 | double d; 123 | uint64_t u; 124 | } double_val; 125 | 126 | int rc = trilogy_reader_get_uint64(reader, &double_val.u); 127 | if (rc != TRILOGY_OK) { 128 | return rc; 129 | } 130 | 131 | *out = double_val.d; 132 | 133 | return TRILOGY_OK; 134 | } 135 | 136 | int trilogy_reader_get_lenenc(trilogy_reader_t *reader, uint64_t *out) 137 | { 138 | CHECK(1); 139 | 140 | uint8_t leader = next_uint8(reader); 141 | 142 | if (leader < 0xfb) { 143 | if (out) { 144 | *out = leader; 145 | } 146 | 147 | return TRILOGY_OK; 148 | } 149 | 150 | switch (leader) { 151 | case 0xfb: 152 | return TRILOGY_NULL_VALUE; 153 | 154 | case 0xfc: { 155 | uint16_t u16 = 0; 156 | int rc = trilogy_reader_get_uint16(reader, &u16); 157 | 158 | if (out) { 159 | *out = u16; 160 | } 161 | 162 | return rc; 163 | } 164 | 165 | case 0xfd: { 166 | uint32_t u24 = 0; 167 | int rc = trilogy_reader_get_uint24(reader, &u24); 168 | 169 | if (out) { 170 | *out = u24; 171 | } 172 | 173 | return rc; 174 | } 175 | 176 | case 0xfe: 177 | return trilogy_reader_get_uint64(reader, out); 178 | 179 | default: 180 | return TRILOGY_PROTOCOL_VIOLATION; 181 | } 182 | } 183 | 184 | int trilogy_reader_get_buffer(trilogy_reader_t *reader, size_t len, const void **out) 185 | { 186 | CHECK(len); 187 | 188 | if (out) { 189 | *out = (const void *)(reader->buff + reader->pos); 190 | } 191 | 192 | reader->pos += len; 193 | 194 | return TRILOGY_OK; 195 | } 196 | 197 | int trilogy_reader_copy_buffer(trilogy_reader_t *reader, size_t len, void *out) 198 | { 199 | CHECK(len); 200 | 201 | if (out) { 202 | memcpy(out, reader->buff + reader->pos, len); 203 | } 204 | 205 | reader->pos += len; 206 | 207 | return TRILOGY_OK; 208 | } 209 | 210 | int trilogy_reader_get_lenenc_buffer(trilogy_reader_t *reader, size_t *out_len, const void **out) 211 | { 212 | uint64_t len; 213 | 214 | int rc = trilogy_reader_get_lenenc(reader, &len); 215 | 216 | if (rc) { 217 | return rc; 218 | } 219 | 220 | // check len is not larger than the amount of bytes left before downcasting 221 | // to size_t (which may be smaller than uint64_t on some architectures) 222 | if (len > (uint64_t)(reader->len - reader->pos)) { 223 | return TRILOGY_TRUNCATED_PACKET; 224 | } 225 | 226 | if (out_len) { 227 | *out_len = (size_t)len; 228 | } 229 | 230 | return trilogy_reader_get_buffer(reader, (size_t)len, out); 231 | } 232 | 233 | int trilogy_reader_get_string(trilogy_reader_t *reader, const char **out, size_t *out_len) 234 | { 235 | const uint8_t *pos = reader->buff + reader->pos; 236 | 237 | const uint8_t *end = memchr(pos, 0, reader->len - reader->pos); 238 | 239 | if (!end) { 240 | return TRILOGY_TRUNCATED_PACKET; 241 | } 242 | 243 | if (out) { 244 | *out = (const char *)pos; 245 | } 246 | 247 | size_t len = (size_t)(end - pos); 248 | 249 | if (out_len) { 250 | *out_len = len; 251 | } 252 | 253 | reader->pos += len + 1; 254 | 255 | return TRILOGY_OK; 256 | } 257 | 258 | int trilogy_reader_get_eof_buffer(trilogy_reader_t *reader, size_t *out_len, const void **out) 259 | { 260 | if (out_len) { 261 | *out_len = reader->len - reader->pos; 262 | } 263 | 264 | if (out) { 265 | *out = reader->buff + reader->pos; 266 | } 267 | 268 | reader->pos = reader->len; 269 | 270 | return TRILOGY_OK; 271 | } 272 | 273 | bool trilogy_reader_eof(trilogy_reader_t *reader) { return !(reader->pos < reader->len); } 274 | 275 | int trilogy_reader_finish(trilogy_reader_t *reader) 276 | { 277 | if (reader->pos < reader->len) { 278 | return TRILOGY_EXTRA_DATA_IN_PACKET; 279 | } else { 280 | return TRILOGY_OK; 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /src/vendor/openssl_hostname_validation.c: -------------------------------------------------------------------------------- 1 | /* Obtained from: https://github.com/iSECPartners/ssl-conservatory */ 2 | 3 | /* 4 | Copyright (C) 2012, iSEC Partners. 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 9 | of the Software, and to permit persons to whom the Software is furnished to do 10 | so, subject to the following conditions: 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | */ 21 | 22 | /* 23 | * Helper functions to perform basic hostname validation using OpenSSL. 24 | * 25 | * Please read "everything-you-wanted-to-know-about-openssl.pdf" before 26 | * attempting to use this code. This whitepaper describes how the code works, 27 | * how it should be used, and what its limitations are. 28 | * 29 | * Author: Alban Diquet 30 | * License: See LICENSE 31 | * 32 | */ 33 | 34 | // Get rid of OSX 10.7 and greater deprecation warnings. 35 | #if defined(__APPLE__) && defined(__clang__) 36 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" 37 | #endif 38 | 39 | #include 40 | #include 41 | #include 42 | 43 | #include "trilogy/vendor/curl_hostcheck.h" 44 | #include "trilogy/vendor/openssl_hostname_validation.h" 45 | 46 | #define HOSTNAME_MAX_SIZE 255 47 | 48 | #if (OPENSSL_VERSION_NUMBER < 0x10100000L) || \ 49 | (defined(LIBRESSL_VERSION_NUMBER) && LIBRESSL_VERSION_NUMBER < 0x20700000L) 50 | #define ASN1_STRING_get0_data ASN1_STRING_data 51 | #endif 52 | 53 | /** 54 | * Tries to find a match for hostname in the certificate's Common Name field. 55 | * 56 | * Returns MatchFound if a match was found. 57 | * Returns MatchNotFound if no matches were found. 58 | * Returns MalformedCertificate if the Common Name had a NUL character embedded 59 | * in it. Returns Error if the Common Name could not be extracted. 60 | */ 61 | static HostnameValidationResult matches_common_name(const char *hostname, const X509 *server_cert) 62 | { 63 | int common_name_loc = -1; 64 | X509_NAME_ENTRY *common_name_entry = NULL; 65 | ASN1_STRING *common_name_asn1 = NULL; 66 | const char *common_name_str = NULL; 67 | 68 | // Find the position of the CN field in the Subject field of the certificate 69 | common_name_loc = X509_NAME_get_index_by_NID(X509_get_subject_name((X509 *)server_cert), NID_commonName, -1); 70 | if (common_name_loc < 0) { 71 | return Error; 72 | } 73 | 74 | // Extract the CN field 75 | common_name_entry = X509_NAME_get_entry(X509_get_subject_name((X509 *)server_cert), common_name_loc); 76 | if (common_name_entry == NULL) { 77 | return Error; 78 | } 79 | 80 | // Convert the CN field to a C string 81 | common_name_asn1 = X509_NAME_ENTRY_get_data(common_name_entry); 82 | if (common_name_asn1 == NULL) { 83 | return Error; 84 | } 85 | common_name_str = (char *)ASN1_STRING_get0_data(common_name_asn1); 86 | 87 | // Make sure there isn't an embedded NUL character in the CN 88 | if ((size_t)ASN1_STRING_length(common_name_asn1) != strlen(common_name_str)) { 89 | return MalformedCertificate; 90 | } 91 | 92 | // Compare expected hostname with the CN 93 | if (Curl_cert_hostcheck(common_name_str, hostname) == CURL_HOST_MATCH) { 94 | return MatchFound; 95 | } else { 96 | return MatchNotFound; 97 | } 98 | } 99 | 100 | /** 101 | * Tries to find a match for hostname in the certificate's Subject Alternative 102 | * Name extension. 103 | * 104 | * Returns MatchFound if a match was found. 105 | * Returns MatchNotFound if no matches were found. 106 | * Returns MalformedCertificate if any of the hostnames had a NUL character 107 | * embedded in it. Returns NoSANPresent if the SAN extension was not present in 108 | * the certificate. 109 | */ 110 | static HostnameValidationResult matches_subject_alternative_name(const char *hostname, const X509 *server_cert) 111 | { 112 | HostnameValidationResult result = MatchNotFound; 113 | int i; 114 | int san_names_nb = -1; 115 | STACK_OF(GENERAL_NAME) *san_names = NULL; 116 | 117 | // Try to extract the names within the SAN extension from the certificate 118 | san_names = X509_get_ext_d2i((X509 *)server_cert, NID_subject_alt_name, NULL, NULL); 119 | if (san_names == NULL) { 120 | return NoSANPresent; 121 | } 122 | san_names_nb = sk_GENERAL_NAME_num(san_names); 123 | 124 | // Check each name within the extension 125 | for (i = 0; i < san_names_nb; i++) { 126 | const GENERAL_NAME *current_name = sk_GENERAL_NAME_value(san_names, i); 127 | 128 | if (current_name->type == GEN_DNS) { 129 | // Current name is a DNS name, let's check it 130 | const char *dns_name = (char *)ASN1_STRING_get0_data(current_name->d.dNSName); 131 | 132 | // Make sure there isn't an embedded NUL character in the DNS name 133 | if ((size_t)ASN1_STRING_length(current_name->d.dNSName) != strlen(dns_name)) { 134 | result = MalformedCertificate; 135 | break; 136 | } else { // Compare expected hostname with the DNS name 137 | if (Curl_cert_hostcheck(dns_name, hostname) == CURL_HOST_MATCH) { 138 | result = MatchFound; 139 | break; 140 | } 141 | } 142 | } 143 | } 144 | sk_GENERAL_NAME_pop_free(san_names, GENERAL_NAME_free); 145 | 146 | return result; 147 | } 148 | 149 | /** 150 | * Validates the server's identity by looking for the expected hostname in the 151 | * server's certificate. As described in RFC 6125, it first tries to find a 152 | * match in the Subject Alternative Name extension. If the extension is not 153 | * present in the certificate, it checks the Common Name instead. 154 | * 155 | * Returns MatchFound if a match was found. 156 | * Returns MatchNotFound if no matches were found. 157 | * Returns MalformedCertificate if any of the hostnames had a NUL character 158 | * embedded in it. Returns Error if there was an error. 159 | */ 160 | HostnameValidationResult validate_hostname(const char *hostname, const X509 *server_cert) 161 | { 162 | HostnameValidationResult result; 163 | 164 | if ((hostname == NULL) || (server_cert == NULL)) 165 | return Error; 166 | 167 | // First try the Subject Alternative Names extension 168 | result = matches_subject_alternative_name(hostname, server_cert); 169 | if (result == NoSANPresent) { 170 | // Extension was not found: try the Common Name 171 | result = matches_common_name(hostname, server_cert); 172 | } 173 | 174 | return result; 175 | } 176 | -------------------------------------------------------------------------------- /example/trilogy_query.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "trilogy.h" 8 | 9 | #define DEFAULT_HOST "127.0.0.1" 10 | #define DEFAULT_PORT 3306 11 | #define DEFAULT_USER "nobody" 12 | 13 | static int execute_query(trilogy_conn_t *conn, const char *sql) 14 | { 15 | fprintf(stderr, "\nsending query command\n"); 16 | 17 | uint64_t column_count = 0; 18 | 19 | int rc = trilogy_query(conn, sql, strlen(sql), &column_count); 20 | 21 | switch (rc) { 22 | case TRILOGY_OK: 23 | fprintf(stderr, "(no results)\n"); 24 | return rc; 25 | 26 | case TRILOGY_HAVE_RESULTS: 27 | break; 28 | 29 | default: 30 | return rc; 31 | } 32 | 33 | bool *binary_columns = calloc(column_count, sizeof(bool)); 34 | 35 | for (uint64_t i = 0; i < column_count; i++) { 36 | trilogy_column_packet_t column; 37 | 38 | rc = trilogy_read_full_column(conn, &column); 39 | 40 | if (rc < 0) { 41 | free(binary_columns); 42 | return rc; 43 | } 44 | 45 | printf("%.*s", (int)column.name_len, column.name); 46 | 47 | if (i + 1 < column_count) { 48 | printf(","); 49 | } 50 | 51 | binary_columns[i] = false; 52 | if (column.flags & TRILOGY_COLUMN_FLAG_BINARY) { 53 | binary_columns[i] = true; 54 | } 55 | } 56 | 57 | printf("\n"); 58 | 59 | // shut scan-build up 60 | if (column_count == 0) { 61 | free(binary_columns); 62 | return TRILOGY_OK; 63 | } 64 | 65 | trilogy_value_t *values = calloc(column_count, sizeof(trilogy_value_t)); 66 | 67 | while ((rc = trilogy_read_full_row(conn, values)) == TRILOGY_OK) { 68 | for (uint64_t i = 0; i < column_count; i++) { 69 | if (values[i].is_null) { 70 | printf("NULL"); 71 | } else { 72 | if (binary_columns[i]) { 73 | printf("\"<<>>\"", values[i].data_len); 74 | } else { 75 | printf("\"%.*s\"", (int)values[i].data_len, (const char *)values[i].data); 76 | } 77 | } 78 | 79 | if (i + 1 < column_count) { 80 | printf(","); 81 | } 82 | } 83 | 84 | printf("\n"); 85 | } 86 | 87 | free(binary_columns); 88 | free(values); 89 | 90 | if (rc == TRILOGY_EOF) { 91 | rc = TRILOGY_OK; 92 | } 93 | 94 | return rc; 95 | } 96 | 97 | void fail_on_error(const trilogy_conn_t *conn, int err, const char *description) 98 | { 99 | if (err < 0) { 100 | fprintf(stderr, "%s error: %s %d\n", description, trilogy_error(err), err); 101 | if (err == TRILOGY_ERR) { 102 | fprintf(stderr, "%d %.*s\n", conn->error_code, (int)conn->error_message_len, conn->error_message); 103 | } else if (err == TRILOGY_SYSERR) { 104 | perror(""); 105 | } 106 | exit(EXIT_FAILURE); 107 | } 108 | } 109 | 110 | int main(int argc, char *argv[]) 111 | { 112 | trilogy_sockopt_t connopt = {0}; 113 | char *sql = NULL; 114 | 115 | static struct option longopts[] = {{"host", optional_argument, NULL, 'h'}, 116 | {"port", optional_argument, NULL, 'P'}, 117 | {"sql", optional_argument, NULL, 's'}, 118 | {"database", optional_argument, NULL, 'd'}, 119 | {"user", optional_argument, NULL, 'u'}, 120 | {"pass", optional_argument, NULL, 'p'}, 121 | {NULL, 0, NULL, 0}}; 122 | 123 | if (!(connopt.hostname = getenv("MYSQL_HOST"))) { 124 | connopt.hostname = DEFAULT_HOST; 125 | } 126 | connopt.hostname = strdup(connopt.hostname); 127 | 128 | const char *port = getenv("MYSQL_TCP_PORT"); 129 | 130 | if (port != NULL) { 131 | connopt.port = atoi(port); 132 | } 133 | 134 | if (connopt.port == 0) { 135 | connopt.port = DEFAULT_PORT; 136 | } 137 | 138 | if (!(connopt.username = getenv("USER"))) { 139 | connopt.username = DEFAULT_USER; 140 | } 141 | connopt.username = strdup(connopt.username); 142 | 143 | int opt = 0; 144 | while ((opt = getopt_long(argc, argv, "h:P:s:d:u:p:", longopts, NULL)) != -1) { 145 | switch (opt) { 146 | case 'h': 147 | if (optarg) { 148 | free(connopt.hostname); 149 | connopt.hostname = strdup(optarg); 150 | } 151 | break; 152 | case 'P': 153 | if (optarg) { 154 | connopt.port = atoi(optarg); 155 | } 156 | break; 157 | case 's': 158 | if (optarg) { 159 | free(sql); 160 | sql = strdup(optarg); 161 | } 162 | break; 163 | case 'd': 164 | if (optarg) { 165 | free(connopt.database); 166 | connopt.database = strdup(optarg); 167 | } 168 | break; 169 | case 'u': 170 | if (optarg) { 171 | free(connopt.username); 172 | connopt.username = strdup(optarg); 173 | } 174 | break; 175 | case 'p': 176 | if (optarg) { 177 | free(connopt.password); 178 | connopt.password = strdup(optarg); 179 | connopt.password_len = strlen(optarg); 180 | } 181 | break; 182 | } 183 | } 184 | 185 | int err; 186 | 187 | trilogy_conn_t conn; 188 | trilogy_init(&conn); 189 | 190 | fprintf(stderr, "connecting to %s:%hu as %s...\n", connopt.hostname, connopt.port, connopt.username); 191 | err = trilogy_connect(&conn, &connopt); 192 | fail_on_error(&conn, err, "connect"); 193 | fprintf(stderr, "connected\n"); 194 | 195 | fprintf(stderr, "\nsending ping command\n"); 196 | err = trilogy_ping(&conn); 197 | fail_on_error(&conn, err, "ping"); 198 | fprintf(stderr, "ping success\n"); 199 | 200 | if (connopt.database) { 201 | fprintf(stderr, "\nsending change db command\n"); 202 | err = trilogy_change_db(&conn, connopt.database, strlen(connopt.database)); 203 | fail_on_error(&conn, err, "change db"); 204 | fprintf(stderr, "change db success\n"); 205 | } 206 | 207 | if (sql) { 208 | switch ((err = execute_query(&conn, sql))) { 209 | case TRILOGY_OK: 210 | break; 211 | 212 | case TRILOGY_ERR: 213 | fprintf(stderr, "error executing query: mysql said: %d %.*s\n", conn.error_code, 214 | (int)conn.error_message_len, conn.error_message); 215 | break; 216 | 217 | default: 218 | fail_on_error(&conn, err, "executing query"); 219 | } 220 | } 221 | 222 | fprintf(stderr, "\nsending quit command and closing connection\n"); 223 | err = trilogy_close(&conn); 224 | fail_on_error(&conn, err, "closing connection"); 225 | fprintf(stderr, "connection closed\n"); 226 | 227 | free(connopt.hostname); 228 | free(connopt.username); 229 | free(sql); 230 | free(connopt.database); 231 | free(connopt.password); 232 | 233 | trilogy_free(&conn); 234 | exit(EXIT_SUCCESS); 235 | } 236 | -------------------------------------------------------------------------------- /src/vendor/curl_hostcheck.c: -------------------------------------------------------------------------------- 1 | /*************************************************************************** 2 | * _ _ ____ _ 3 | * Project ___| | | | _ \| | 4 | * / __| | | | |_) | | 5 | * | (__| |_| | _ <| |___ 6 | * \___|\___/|_| \_\_____| 7 | * 8 | * Copyright (C) 1998 - 2012, Daniel Stenberg, , et al. 9 | * 10 | * This software is licensed as described in the file COPYING, which 11 | * you should have received as part of this distribution. The terms 12 | * are also available at http://curl.haxx.se/docs/copyright.html. 13 | * 14 | * You may opt to use, copy, modify, merge, publish, distribute and/or sell 15 | * copies of the Software, and permit persons to whom the Software is 16 | * furnished to do so, under the terms of the COPYING file. 17 | * 18 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY 19 | * KIND, either express or implied. 20 | * 21 | ***************************************************************************/ 22 | 23 | /* This file is an amalgamation of hostcheck.c and most of rawstr.c 24 | from cURL. The contents of the COPYING file mentioned above are: 25 | COPYRIGHT AND PERMISSION NOTICE 26 | Copyright (c) 1996 - 2013, Daniel Stenberg, . 27 | All rights reserved. 28 | Permission to use, copy, modify, and distribute this software for any purpose 29 | with or without fee is hereby granted, provided that the above copyright 30 | notice and this permission notice appear in all copies. 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF THIRD PARTY RIGHTS. IN 34 | NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 35 | DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR 36 | OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE 37 | OR OTHER DEALINGS IN THE SOFTWARE. 38 | Except as contained in this notice, the name of a copyright holder shall not 39 | be used in advertising or otherwise to promote the sale, use or other dealings 40 | in this Software without prior written authorization of the copyright holder. 41 | */ 42 | 43 | #include "trilogy/vendor/curl_hostcheck.h" 44 | #include 45 | 46 | /* Portable, consistent toupper (remember EBCDIC). Do not use toupper() because 47 | its behavior is altered by the current locale. */ 48 | static char Curl_raw_toupper(char in) 49 | { 50 | switch (in) { 51 | case 'a': 52 | return 'A'; 53 | case 'b': 54 | return 'B'; 55 | case 'c': 56 | return 'C'; 57 | case 'd': 58 | return 'D'; 59 | case 'e': 60 | return 'E'; 61 | case 'f': 62 | return 'F'; 63 | case 'g': 64 | return 'G'; 65 | case 'h': 66 | return 'H'; 67 | case 'i': 68 | return 'I'; 69 | case 'j': 70 | return 'J'; 71 | case 'k': 72 | return 'K'; 73 | case 'l': 74 | return 'L'; 75 | case 'm': 76 | return 'M'; 77 | case 'n': 78 | return 'N'; 79 | case 'o': 80 | return 'O'; 81 | case 'p': 82 | return 'P'; 83 | case 'q': 84 | return 'Q'; 85 | case 'r': 86 | return 'R'; 87 | case 's': 88 | return 'S'; 89 | case 't': 90 | return 'T'; 91 | case 'u': 92 | return 'U'; 93 | case 'v': 94 | return 'V'; 95 | case 'w': 96 | return 'W'; 97 | case 'x': 98 | return 'X'; 99 | case 'y': 100 | return 'Y'; 101 | case 'z': 102 | return 'Z'; 103 | } 104 | return in; 105 | } 106 | 107 | /* 108 | * Curl_raw_equal() is for doing "raw" case insensitive strings. This is meant 109 | * to be locale independent and only compare strings we know are safe for 110 | * this. See http://daniel.haxx.se/blog/2008/10/15/strcasecmp-in-turkish/ for 111 | * some further explanation to why this function is necessary. 112 | * 113 | * The function is capable of comparing a-z case insensitively even for 114 | * non-ascii. 115 | */ 116 | 117 | static int Curl_raw_equal(const char *first, const char *second) 118 | { 119 | while (*first && *second) { 120 | if (Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) 121 | /* get out of the loop as soon as they don't match */ 122 | break; 123 | first++; 124 | second++; 125 | } 126 | /* we do the comparison here (possibly again), just to make sure that if the 127 | loop above is skipped because one of the strings reached zero, we must 128 | not return this as a successful match */ 129 | return (Curl_raw_toupper(*first) == Curl_raw_toupper(*second)); 130 | } 131 | 132 | static int Curl_raw_nequal(const char *first, const char *second, size_t max) 133 | { 134 | while (*first && *second && max) { 135 | if (Curl_raw_toupper(*first) != Curl_raw_toupper(*second)) { 136 | break; 137 | } 138 | max--; 139 | first++; 140 | second++; 141 | } 142 | if (0 == max) 143 | return 1; /* they are equal this far */ 144 | 145 | return Curl_raw_toupper(*first) == Curl_raw_toupper(*second); 146 | } 147 | 148 | /* 149 | * Match a hostname against a wildcard pattern. 150 | * E.g. 151 | * "foo.host.com" matches "*.host.com". 152 | * 153 | * We use the matching rule described in RFC6125, section 6.4.3. 154 | * http://tools.ietf.org/html/rfc6125#section-6.4.3 155 | */ 156 | 157 | static int hostmatch(const char *hostname, const char *pattern) 158 | { 159 | const char *pattern_label_end, *pattern_wildcard, *hostname_label_end; 160 | int wildcard_enabled; 161 | size_t prefixlen, suffixlen; 162 | pattern_wildcard = strchr(pattern, '*'); 163 | if (pattern_wildcard == NULL) 164 | return Curl_raw_equal(pattern, hostname) ? CURL_HOST_MATCH : CURL_HOST_NOMATCH; 165 | 166 | /* We require at least 2 dots in pattern to avoid too wide wildcard 167 | match. */ 168 | wildcard_enabled = 1; 169 | pattern_label_end = strchr(pattern, '.'); 170 | if (pattern_label_end == NULL || strchr(pattern_label_end + 1, '.') == NULL || 171 | pattern_wildcard > pattern_label_end || Curl_raw_nequal(pattern, "xn--", 4)) { 172 | wildcard_enabled = 0; 173 | } 174 | if (!wildcard_enabled) 175 | return Curl_raw_equal(pattern, hostname) ? CURL_HOST_MATCH : CURL_HOST_NOMATCH; 176 | 177 | hostname_label_end = strchr(hostname, '.'); 178 | if (hostname_label_end == NULL || !Curl_raw_equal(pattern_label_end, hostname_label_end)) 179 | return CURL_HOST_NOMATCH; 180 | 181 | /* The wildcard must match at least one character, so the left-most 182 | label of the hostname is at least as large as the left-most label 183 | of the pattern. */ 184 | if (hostname_label_end - hostname < pattern_label_end - pattern) 185 | return CURL_HOST_NOMATCH; 186 | 187 | prefixlen = (size_t)(pattern_wildcard - pattern); 188 | suffixlen = (size_t)(pattern_label_end - (pattern_wildcard + 1)); 189 | return Curl_raw_nequal(pattern, hostname, prefixlen) && 190 | Curl_raw_nequal(pattern_wildcard + 1, hostname_label_end - suffixlen, suffixlen) 191 | ? CURL_HOST_MATCH 192 | : CURL_HOST_NOMATCH; 193 | } 194 | 195 | int Curl_cert_hostcheck(const char *match_pattern, const char *hostname) 196 | { 197 | if (!match_pattern || !*match_pattern || !hostname || !*hostname) /* sanity check */ 198 | return 0; 199 | 200 | if (Curl_raw_equal(hostname, match_pattern)) /* trivial case */ 201 | return 1; 202 | 203 | if (hostmatch(hostname, match_pattern) == CURL_HOST_MATCH) 204 | return 1; 205 | return 0; 206 | } 207 | -------------------------------------------------------------------------------- /test/client/stmt_reset_test.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include "../test.h" 8 | 9 | #include "trilogy/blocking.h" 10 | #include "trilogy/client.h" 11 | #include "trilogy/error.h" 12 | 13 | #define do_connect(CONN) \ 14 | do { \ 15 | int err = trilogy_init(CONN); \ 16 | ASSERT_OK(err); \ 17 | err = trilogy_connect(CONN, get_connopt()); \ 18 | ASSERT_OK(err); \ 19 | } while (0) 20 | 21 | TEST test_stmt_reset_send() 22 | { 23 | trilogy_conn_t conn; 24 | 25 | do_connect(&conn); 26 | 27 | const char *sql = "SELECT ?"; 28 | size_t sql_len = strlen(sql); 29 | 30 | int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); 31 | while (err == TRILOGY_AGAIN) { 32 | err = wait_writable(&conn); 33 | ASSERT_OK(err); 34 | 35 | err = trilogy_flush_writes(&conn); 36 | } 37 | ASSERT_OK(err); 38 | 39 | trilogy_stmt_t stmt; 40 | 41 | err = trilogy_stmt_prepare_recv(&conn, &stmt); 42 | while (err == TRILOGY_AGAIN) { 43 | err = wait_readable(&conn); 44 | ASSERT_OK(err); 45 | 46 | err = trilogy_stmt_prepare_recv(&conn, &stmt); 47 | } 48 | ASSERT_OK(err); 49 | 50 | ASSERT_EQ(1, stmt.parameter_count); 51 | 52 | trilogy_column_packet_t params[1]; 53 | 54 | for (uint64_t i = 0; i < stmt.parameter_count; i++) { 55 | trilogy_column_packet_t *param = ¶ms[i]; 56 | 57 | err = trilogy_read_full_column(&conn, param); 58 | 59 | if (err < 0) { 60 | return err; 61 | } 62 | } 63 | 64 | ASSERT_EQ(1, stmt.column_count); 65 | 66 | trilogy_column_packet_t column_defs[1]; 67 | 68 | for (uint64_t i = 0; i < stmt.column_count; i++) { 69 | trilogy_column_packet_t *column = &column_defs[i]; 70 | 71 | err = trilogy_read_full_column(&conn, column); 72 | 73 | if (err < 0) { 74 | return err; 75 | } 76 | } 77 | 78 | err = trilogy_stmt_reset_send(&conn, &stmt); 79 | while (err == TRILOGY_AGAIN) { 80 | err = wait_writable(&conn); 81 | ASSERT_OK(err); 82 | 83 | err = trilogy_flush_writes(&conn); 84 | } 85 | ASSERT_OK(err); 86 | 87 | trilogy_free(&conn); 88 | PASS(); 89 | } 90 | 91 | TEST test_stmt_reset_send_closed_socket() 92 | { 93 | trilogy_conn_t conn; 94 | 95 | do_connect(&conn); 96 | 97 | const char *sql = "SELECT ?"; 98 | size_t sql_len = strlen(sql); 99 | 100 | int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); 101 | while (err == TRILOGY_AGAIN) { 102 | err = wait_writable(&conn); 103 | ASSERT_OK(err); 104 | 105 | err = trilogy_flush_writes(&conn); 106 | } 107 | ASSERT_OK(err); 108 | 109 | trilogy_stmt_t stmt; 110 | 111 | err = trilogy_stmt_prepare_recv(&conn, &stmt); 112 | while (err == TRILOGY_AGAIN) { 113 | err = wait_readable(&conn); 114 | ASSERT_OK(err); 115 | 116 | err = trilogy_stmt_prepare_recv(&conn, &stmt); 117 | } 118 | ASSERT_OK(err); 119 | 120 | ASSERT_EQ(1, stmt.parameter_count); 121 | 122 | trilogy_column_packet_t params[1]; 123 | 124 | for (uint64_t i = 0; i < stmt.parameter_count; i++) { 125 | trilogy_column_packet_t *param = ¶ms[i]; 126 | 127 | err = trilogy_read_full_column(&conn, param); 128 | 129 | if (err < 0) { 130 | return err; 131 | } 132 | } 133 | 134 | ASSERT_EQ(1, stmt.column_count); 135 | 136 | trilogy_column_packet_t column_defs[1]; 137 | 138 | for (uint64_t i = 0; i < stmt.column_count; i++) { 139 | trilogy_column_packet_t *column = &column_defs[i]; 140 | 141 | err = trilogy_read_full_column(&conn, column); 142 | 143 | if (err < 0) { 144 | return err; 145 | } 146 | } 147 | 148 | close_socket(&conn); 149 | 150 | err = trilogy_stmt_reset_send(&conn, &stmt); 151 | ASSERT_ERR(TRILOGY_SYSERR, err); 152 | 153 | trilogy_free(&conn); 154 | PASS(); 155 | } 156 | 157 | TEST test_stmt_reset_recv() 158 | { 159 | trilogy_conn_t conn; 160 | 161 | do_connect(&conn); 162 | 163 | const char *sql = "SELECT ?"; 164 | size_t sql_len = strlen(sql); 165 | 166 | int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); 167 | while (err == TRILOGY_AGAIN) { 168 | err = wait_writable(&conn); 169 | ASSERT_OK(err); 170 | 171 | err = trilogy_flush_writes(&conn); 172 | } 173 | ASSERT_OK(err); 174 | 175 | trilogy_stmt_t stmt; 176 | 177 | err = trilogy_stmt_prepare_recv(&conn, &stmt); 178 | while (err == TRILOGY_AGAIN) { 179 | err = wait_readable(&conn); 180 | ASSERT_OK(err); 181 | 182 | err = trilogy_stmt_prepare_recv(&conn, &stmt); 183 | } 184 | ASSERT_OK(err); 185 | 186 | ASSERT_EQ(1, stmt.parameter_count); 187 | 188 | trilogy_column_packet_t params[1]; 189 | 190 | for (uint64_t i = 0; i < stmt.parameter_count; i++) { 191 | trilogy_column_packet_t *param = ¶ms[i]; 192 | 193 | err = trilogy_read_full_column(&conn, param); 194 | 195 | if (err < 0) { 196 | return err; 197 | } 198 | } 199 | 200 | ASSERT_EQ(1, stmt.column_count); 201 | 202 | trilogy_column_packet_t column_defs[1]; 203 | 204 | for (uint64_t i = 0; i < stmt.column_count; i++) { 205 | trilogy_column_packet_t *column = &column_defs[i]; 206 | 207 | err = trilogy_read_full_column(&conn, column); 208 | 209 | if (err < 0) { 210 | return err; 211 | } 212 | } 213 | 214 | err = trilogy_stmt_reset_send(&conn, &stmt); 215 | while (err == TRILOGY_AGAIN) { 216 | err = wait_writable(&conn); 217 | ASSERT_OK(err); 218 | 219 | err = trilogy_flush_writes(&conn); 220 | } 221 | ASSERT_OK(err); 222 | 223 | err = trilogy_stmt_reset_recv(&conn); 224 | while (err == TRILOGY_AGAIN) { 225 | err = wait_readable(&conn); 226 | ASSERT_OK(err); 227 | 228 | err = trilogy_stmt_reset_recv(&conn); 229 | } 230 | ASSERT_OK(err); 231 | 232 | trilogy_free(&conn); 233 | PASS(); 234 | } 235 | 236 | TEST test_stmt_reset_recv_closed_socket() 237 | { 238 | trilogy_conn_t conn; 239 | 240 | do_connect(&conn); 241 | 242 | const char *sql = "SELECT ?"; 243 | size_t sql_len = strlen(sql); 244 | 245 | int err = trilogy_stmt_prepare_send(&conn, sql, sql_len); 246 | while (err == TRILOGY_AGAIN) { 247 | err = wait_writable(&conn); 248 | ASSERT_OK(err); 249 | 250 | err = trilogy_flush_writes(&conn); 251 | } 252 | ASSERT_OK(err); 253 | 254 | trilogy_stmt_t stmt; 255 | 256 | err = trilogy_stmt_prepare_recv(&conn, &stmt); 257 | while (err == TRILOGY_AGAIN) { 258 | err = wait_readable(&conn); 259 | ASSERT_OK(err); 260 | 261 | err = trilogy_stmt_prepare_recv(&conn, &stmt); 262 | } 263 | ASSERT_OK(err); 264 | 265 | ASSERT_EQ(1, stmt.parameter_count); 266 | 267 | trilogy_column_packet_t params[1]; 268 | 269 | for (uint64_t i = 0; i < stmt.parameter_count; i++) { 270 | trilogy_column_packet_t *param = ¶ms[i]; 271 | 272 | err = trilogy_read_full_column(&conn, param); 273 | 274 | if (err < 0) { 275 | return err; 276 | } 277 | } 278 | 279 | ASSERT_EQ(1, stmt.column_count); 280 | 281 | trilogy_column_packet_t column_defs[1]; 282 | 283 | for (uint64_t i = 0; i < stmt.column_count; i++) { 284 | trilogy_column_packet_t *column = &column_defs[i]; 285 | 286 | err = trilogy_read_full_column(&conn, column); 287 | 288 | if (err < 0) { 289 | return err; 290 | } 291 | } 292 | 293 | err = trilogy_stmt_reset_send(&conn, &stmt); 294 | while (err == TRILOGY_AGAIN) { 295 | err = wait_writable(&conn); 296 | ASSERT_OK(err); 297 | 298 | err = trilogy_flush_writes(&conn); 299 | } 300 | ASSERT_OK(err); 301 | 302 | close_socket(&conn); 303 | 304 | err = trilogy_stmt_reset_recv(&conn); 305 | ASSERT_ERR(TRILOGY_SYSERR, err); 306 | 307 | trilogy_free(&conn); 308 | PASS(); 309 | } 310 | 311 | int client_stmt_reset_test() 312 | { 313 | RUN_TEST(test_stmt_reset_send); 314 | RUN_TEST(test_stmt_reset_send_closed_socket); 315 | RUN_TEST(test_stmt_reset_recv); 316 | RUN_TEST(test_stmt_reset_recv_closed_socket); 317 | 318 | return 0; 319 | } 320 | -------------------------------------------------------------------------------- /inc/trilogy/builder.h: -------------------------------------------------------------------------------- 1 | #ifndef TRILOGY_BUILDER_H 2 | #define TRILOGY_BUILDER_H 3 | 4 | #include 5 | #include 6 | 7 | #include "trilogy/buffer.h" 8 | 9 | /* Trilogy Packet Builder API 10 | * 11 | * The builder API is used for building protocol packet buffers. 12 | */ 13 | 14 | /* trilogy_builder_t - The builder API's instance type. 15 | */ 16 | typedef struct { 17 | trilogy_buffer_t *buffer; 18 | size_t header_offset; 19 | size_t packet_length; 20 | size_t packet_max_length; 21 | uint32_t fragment_length; 22 | uint8_t seq; 23 | } trilogy_builder_t; 24 | 25 | /* trilogy_builder_init - Initialize a pre-allocated trilogy_builder_t 26 | * 27 | * builder - A pre-allocated trilogy_builder_t pointer 28 | * buffer - A pre-initialized trilogy_buffer_t pointer 29 | * seq - The initial sequence number for the packet to be built. This is 30 | * the initial number because the builder API will automatically 31 | * split buffers that are larger than TRILOGY_MAX_PACKET_LEN into 32 | * multiple packets and increment the sequence number in each packet 33 | * following the initial. 34 | * 35 | * Return values: 36 | * TRILOGY_OK - The builder was successfully initialized. 37 | * TRILOGY_SYSERR - A system error occurred, check errno. 38 | */ 39 | int trilogy_builder_init(trilogy_builder_t *builder, trilogy_buffer_t *buffer, uint8_t seq); 40 | 41 | /* trilogy_builder_finalize - Finalize internal buffer state, ensuring all of the 42 | * packets inside are valid and ready for use over the wire. 43 | * 44 | * builder - A pre-initialized trilogy_builder_t pointer. 45 | * 46 | * Returns nothing. 47 | */ 48 | void trilogy_builder_finalize(trilogy_builder_t *builder); 49 | 50 | /* trilogy_builder_write_uint8 - Append an unsigned 8-bit integer to the packet 51 | * buffer. 52 | * 53 | * builder - A pre-initialized trilogy_builder_t pointer. 54 | * val - The value to append to the buffer. 55 | * 56 | * Return values: 57 | * TRILOGY_OK - The value was appended to the packet buffer. 58 | * TRILOGY_SYSERR - A system error occurred, check errno. 59 | * TRILOGY_MAX_PACKET_EXCEEDED - Appending this value would exceed the maximum 60 | * packet size. 61 | */ 62 | int trilogy_builder_write_uint8(trilogy_builder_t *builder, uint8_t val); 63 | 64 | /* trilogy_builder_write_uint16 - Append an unsigned 16-bit integer to the packet 65 | * buffer. 66 | * 67 | * builder - A pre-initialized trilogy_builder_t pointer. 68 | * val - The value to append to the buffer. 69 | * 70 | * Return values: 71 | * TRILOGY_OK - The value was appended to the packet buffer. 72 | * TRILOGY_SYSERR - A system error occurred, check errno. 73 | * TRILOGY_MAX_PACKET_EXCEEDED - Appending this value would exceed the maximum 74 | * packet size. 75 | */ 76 | int trilogy_builder_write_uint16(trilogy_builder_t *builder, uint16_t val); 77 | 78 | /* trilogy_builder_write_uint24 - Append an unsigned 24-bit integer to the packet 79 | * buffer. 80 | * 81 | * builder - A pre-initialized trilogy_builder_t pointer. 82 | * val - The value to append to the buffer. 83 | * 84 | * Return values: 85 | * TRILOGY_OK - The value was appended to the packet buffer. 86 | * TRILOGY_SYSERR - A system error occurred, check errno. 87 | * TRILOGY_MAX_PACKET_EXCEEDED - Appending this value would exceed the maximum 88 | * packet size. 89 | */ 90 | int trilogy_builder_write_uint24(trilogy_builder_t *builder, uint32_t val); 91 | 92 | /* trilogy_builder_write_uint32 - Append an unsigned 32-bit integer to the packet 93 | * buffer. 94 | * 95 | * builder - A pre-initialized trilogy_builder_t pointer 96 | * val - The value to append to the buffer 97 | * 98 | * Return values: 99 | * TRILOGY_OK - The value was appended to the packet buffer. 100 | * TRILOGY_SYSERR - A system error occurred, check errno. 101 | * TRILOGY_MAX_PACKET_EXCEEDED - Appending this value would exceed the maximum 102 | * packet size. 103 | */ 104 | int trilogy_builder_write_uint32(trilogy_builder_t *builder, uint32_t val); 105 | 106 | /* trilogy_builder_write_uint64 - Append an unsigned 64-bit integer to the packet 107 | * buffer. 108 | * 109 | * builder - A pre-initialized trilogy_builder_t pointer 110 | * val - The value to append to the buffer 111 | * 112 | * Return values: 113 | * TRILOGY_OK - The value was appended to the packet buffer. 114 | * TRILOGY_SYSERR - A system error occurred, check errno. 115 | * TRILOGY_MAX_PACKET_EXCEEDED - Appending this value would exceed the maximum 116 | * packet size. 117 | */ 118 | int trilogy_builder_write_uint64(trilogy_builder_t *builder, uint64_t val); 119 | 120 | /* trilogy_builder_write_float - Append a float to the packet buffer. 121 | * 122 | * builder - A pre-initialized trilogy_builder_t pointer 123 | * val - The value to append to the buffer 124 | * 125 | * Return values: 126 | * TRILOGY_OK - The value was appended to the packet buffer. 127 | * TRILOGY_SYSERR - A system error occurred, check errno. 128 | * TRILOGY_MAX_PACKET_EXCEEDED - Appending this value would exceed the maximum 129 | * packet size. 130 | */ 131 | int trilogy_builder_write_float(trilogy_builder_t *builder, float val); 132 | 133 | /* trilogy_builder_write_double - Append a double to the packet buffer. 134 | * 135 | * builder - A pre-initialized trilogy_builder_t pointer 136 | * val - The value to append to the buffer 137 | * 138 | * Return values: 139 | * TRILOGY_OK - The value was appended to the packet buffer. 140 | * TRILOGY_SYSERR - A system error occurred, check errno. 141 | * TRILOGY_MAX_PACKET_EXCEEDED - Appending this value would exceed the maximum 142 | * packet size. 143 | */ 144 | int trilogy_builder_write_double(trilogy_builder_t *builder, double val); 145 | 146 | /* trilogy_builder_write_lenenc - Append a length-encoded integer to the packet 147 | * buffer. 148 | * 149 | * The actual number of bytes appended to the buffer depends on the value passed 150 | * in. 151 | * 152 | * builder - A pre-initialized trilogy_builder_t pointer 153 | * val - The value to append to the buffer 154 | * 155 | * Return values: 156 | * TRILOGY_OK - The value was appended to the packet buffer. 157 | * TRILOGY_SYSERR - A system error occurred, check errno. 158 | * TRILOGY_MAX_PACKET_EXCEEDED - Appending this value would exceed the maximum 159 | * packet size. 160 | */ 161 | int trilogy_builder_write_lenenc(trilogy_builder_t *builder, uint64_t val); 162 | 163 | /* trilogy_builder_write_buffer - Append opaque bytes to the packet buffer. 164 | * 165 | * builder - A pre-initialized trilogy_builder_t pointer 166 | * data - A pointer to the opaque data to be appended 167 | * len - The number of bytes to append from the location in memory the 168 | * `data` parameter points to. 169 | * 170 | * Return values: 171 | * TRILOGY_OK - The value was appended to the packet buffer. 172 | * TRILOGY_SYSERR - A system error occurred, check errno. 173 | * TRILOGY_MAX_PACKET_EXCEEDED - Appending this value would exceed the maximum 174 | * packet size. 175 | */ 176 | int trilogy_builder_write_buffer(trilogy_builder_t *builder, const void *data, size_t len); 177 | 178 | /* trilogy_builder_write_lenenc_buffer - Append opaque bytes to the packet buffer, 179 | * prepended by its length as a length-encoded integer. 180 | * 181 | * builder - A pre-initialized trilogy_builder_t pointer 182 | * data - A pointer to the opaque data to be appended 183 | * len - The number of bytes to append from the location in memory the 184 | * `data` parameter points to. 185 | * 186 | * Return values: 187 | * TRILOGY_OK - The value was appended to the packet buffer. 188 | * TRILOGY_SYSERR - A system error occurred, check errno. 189 | * TRILOGY_MAX_PACKET_EXCEEDED - Appending this value would exceed the maximum 190 | * packet size. 191 | */ 192 | int trilogy_builder_write_lenenc_buffer(trilogy_builder_t *builder, const void *data, size_t len); 193 | 194 | /* trilogy_builder_write_string - Append a C-string to the packet buffer. 195 | * 196 | * builder - A pre-initialized trilogy_builder_t pointer 197 | * data - A pointer to the C-string to append 198 | * 199 | * Return values: 200 | * TRILOGY_OK - The value was appended to the packet buffer. 201 | * TRILOGY_SYSERR - A system error occurred, check errno. 202 | * TRILOGY_MAX_PACKET_EXCEEDED - Appending this value would exceed the maximum 203 | * packet size. 204 | */ 205 | int trilogy_builder_write_string(trilogy_builder_t *builder, const char *data); 206 | 207 | /* trilogy_builder_set_max_packet_length - Set the maximum packet length for 208 | * the builder. Writing data to the builder that would cause the packet length 209 | * to exceed this value will cause the builder to error. 210 | * 211 | * builder - A pre-initialized trilogy_builder_t pointer 212 | * max_length - The new maximum packet length to set 213 | * 214 | * Return values: 215 | * TRILOGY_OK - The maximum packet length was set. 216 | * TRILOGY_MAX_PACKET_EXCEEDED - The current packet length is already 217 | * larger than the requested maximum. 218 | */ 219 | int trilogy_builder_set_max_packet_length(trilogy_builder_t *builder, size_t max_length); 220 | 221 | #endif 222 | --------------------------------------------------------------------------------