├── AndroidMySQLConnector ├── .gitignore ├── src │ └── main │ │ ├── res │ │ ├── values │ │ │ ├── strings.xml │ │ │ ├── colors.xml │ │ │ └── styles.xml │ │ ├── mipmap-hdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-mdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-xxxhdpi │ │ │ ├── ic_launcher.png │ │ │ └── ic_launcher_round.png │ │ ├── mipmap-anydpi-v26 │ │ │ ├── ic_launcher.xml │ │ │ └── ic_launcher_round.xml │ │ ├── drawable-v24 │ │ │ └── ic_launcher_foreground.xml │ │ └── drawable │ │ │ └── ic_launcher_background.xml │ │ ├── java │ │ └── com │ │ │ └── BoardiesITSolutions │ │ │ └── AndroidMySQLConnector │ │ │ ├── Exceptions │ │ │ ├── InvalidSQLPacketException.java │ │ │ ├── SQLColumnNotFoundException.java │ │ │ ├── MySQLException.java │ │ │ ├── MySQLConnException.java │ │ │ └── UnsupportedMySQLServerException.java │ │ │ ├── IIntConnectionInterface.java │ │ │ ├── SSLTrustManager.java │ │ │ ├── IConnectionInterface.java │ │ │ ├── IResultInterface.java │ │ │ ├── ResultSet.java │ │ │ ├── Helpers.java │ │ │ ├── PacketManager │ │ │ ├── SSLRequest.java │ │ │ ├── BasePacket.java │ │ │ ├── COM_Query.java │ │ │ ├── MySQLErrorPacket.java │ │ │ ├── MySQLOKPacket.java │ │ │ ├── COM_QueryResponse.java │ │ │ └── AuthResponse.java │ │ │ ├── SocketSender.java │ │ │ ├── MySQLRow.java │ │ │ ├── ColumnDefinition.java │ │ │ ├── MySQLIO.java │ │ │ ├── Statement.java │ │ │ └── Connection.java │ │ └── AndroidManifest.xml ├── proguard-rules.pro └── build.gradle ├── settings.gradle ├── Android-MySQL-Connector.zip ├── .idea ├── caches │ ├── gradle_models.ser │ └── build_file_checksums.ser ├── markdown-navigator │ └── profiles_settings.xml ├── vcs.xml ├── markdown-exported-files.xml ├── misc.xml ├── runConfigurations.xml ├── gradle.xml ├── codeStyles │ └── Project.xml └── markdown-navigator.xml ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── .gitignore ├── .github └── ISSUE_TEMPLATE │ ├── feature_request.md │ └── bug_report.md ├── CONTRIBUTE.md ├── gradle.properties ├── LICENSE ├── gradlew.bat ├── CODE_OF_CONDUCT.md ├── gradlew └── README.md /AndroidMySQLConnector/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | include ':AndroidMySQLConnector' 2 | -------------------------------------------------------------------------------- /Android-MySQL-Connector.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoardiesITSolutions/Android-MySQL-Connector/HEAD/Android-MySQL-Connector.zip -------------------------------------------------------------------------------- /.idea/caches/gradle_models.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoardiesITSolutions/Android-MySQL-Connector/HEAD/.idea/caches/gradle_models.ser -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoardiesITSolutions/Android-MySQL-Connector/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /.idea/caches/build_file_checksums.ser: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoardiesITSolutions/Android-MySQL-Connector/HEAD/.idea/caches/build_file_checksums.ser -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/values/strings.xml: -------------------------------------------------------------------------------- 1 | 2 | AndroidMySQLConnector 3 | 4 | -------------------------------------------------------------------------------- /.idea/markdown-navigator/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.iml 2 | .gradle 3 | /local.properties 4 | /.idea/libraries 5 | /.idea/modules.xml 6 | /.idea/workspace.xml 7 | .DS_Store 8 | /build 9 | /captures 10 | .externalNativeBuild 11 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/mipmap-hdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoardiesITSolutions/Android-MySQL-Connector/HEAD/AndroidMySQLConnector/src/main/res/mipmap-hdpi/ic_launcher.png -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/mipmap-mdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoardiesITSolutions/Android-MySQL-Connector/HEAD/AndroidMySQLConnector/src/main/res/mipmap-mdpi/ic_launcher.png -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/mipmap-xhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoardiesITSolutions/Android-MySQL-Connector/HEAD/AndroidMySQLConnector/src/main/res/mipmap-xhdpi/ic_launcher.png -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/mipmap-xxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoardiesITSolutions/Android-MySQL-Connector/HEAD/AndroidMySQLConnector/src/main/res/mipmap-xxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/mipmap-xxxhdpi/ic_launcher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoardiesITSolutions/Android-MySQL-Connector/HEAD/AndroidMySQLConnector/src/main/res/mipmap-xxxhdpi/ic_launcher.png -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/mipmap-hdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoardiesITSolutions/Android-MySQL-Connector/HEAD/AndroidMySQLConnector/src/main/res/mipmap-hdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/mipmap-mdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoardiesITSolutions/Android-MySQL-Connector/HEAD/AndroidMySQLConnector/src/main/res/mipmap-mdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/mipmap-xhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoardiesITSolutions/Android-MySQL-Connector/HEAD/AndroidMySQLConnector/src/main/res/mipmap-xhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/mipmap-xxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoardiesITSolutions/Android-MySQL-Connector/HEAD/AndroidMySQLConnector/src/main/res/mipmap-xxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/BoardiesITSolutions/Android-MySQL-Connector/HEAD/AndroidMySQLConnector/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png -------------------------------------------------------------------------------- /.idea/markdown-exported-files.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/values/colors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | #3F51B5 4 | #303F9F 5 | #FF4081 6 | 7 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | #Wed Jan 22 18:22:26 GMT 2020 2 | distributionBase=GRADLE_USER_HOME 3 | distributionPath=wrapper/dists 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip 7 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/mipmap-anydpi-v26/ic_launcher.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/Exceptions/InvalidSQLPacketException.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions; 2 | 3 | public class InvalidSQLPacketException extends Exception 4 | { 5 | public InvalidSQLPacketException(String message) 6 | { 7 | super(message); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/Exceptions/SQLColumnNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions; 2 | 3 | public class SQLColumnNotFoundException extends Exception 4 | { 5 | public SQLColumnNotFoundException(String message) 6 | { 7 | super(message); 8 | } 9 | } 10 | 11 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 9 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/IIntConnectionInterface.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector; 2 | 3 | import com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions.MySQLConnException; 4 | 5 | import java.io.IOException; 6 | 7 | public interface IIntConnectionInterface 8 | { 9 | void socketDataSent(); 10 | void handleException(MySQLConnException ex); 11 | } 12 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/values/styles.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/runConfigurations.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | 5 | --- 6 | 7 | **Is your feature request related to a problem? Please describe.** 8 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 9 | 10 | **Describe the solution you'd like** 11 | A clear and concise description of what you want to happen. 12 | 13 | **Describe alternatives you've considered** 14 | A clear and concise description of any alternative solutions or features you've considered. 15 | 16 | **Additional context** 17 | Add any other context or screenshots about the feature request here. 18 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/SSLTrustManager.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector; 2 | 3 | import java.security.cert.CertificateException; 4 | import java.security.cert.X509Certificate; 5 | 6 | import javax.net.ssl.X509TrustManager; 7 | 8 | public class SSLTrustManager implements X509TrustManager 9 | { 10 | 11 | public X509Certificate[] getAcceptedIssuers() { 12 | return null; 13 | } 14 | 15 | public void checkClientTrusted(X509Certificate[] chain, String authType) 16 | throws CertificateException 17 | { 18 | 19 | } 20 | 21 | public void checkServerTrusted(X509Certificate[] chain, String authType) 22 | throws CertificateException { 23 | 24 | 25 | } 26 | } -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | 5 | --- 6 | 7 | **Describe the bug** 8 | A clear and concise description of what the bug is. 9 | 10 | **To Reproduce** 11 | Steps to reproduce the behavior: 12 | 1. Go to '...' 13 | 2. Click on '....' 14 | 3. Scroll down to '....' 15 | 4. See error 16 | 17 | **Expected behavior** 18 | A clear and concise description of what you expected to happen. 19 | 20 | **Logcat** 21 | If applicable, provide logcat output (ensure no sensitive information is in the output (if it is obfuscate it) 22 | 23 | **Desktop (please complete the following information):** 24 | - Android Version: 25 | - MySQL Server Version 26 | 27 | **Additional context** 28 | Add any other context about the problem here. 29 | -------------------------------------------------------------------------------- /CONTRIBUTE.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | Contributions are of course very welcome and we wish to see this developed into a fully fledged 3 | MySQL connector for Android with the help of the Open Source community. 4 | 5 | We accept contributions via pull requests on GitHub, we also recommend reading [How to write the perfect pull request](https://github.com/blog/1943-how-to-write-the-perfect-pull-request). 6 | 7 | If you find any issues or wish to request a feature then please use GitHub issue tracker or use our [support portal](https://support.boardiesitsolutions.com). 8 | 9 | # Development Environment 10 | The project was developed using Android Studio and Gradle build system. You can of course use 11 | your own IDE, but please ensure that none of your own IDE configuration files are committed 12 | into the repository. -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | # IDE (e.g. Android Studio) users: 3 | # Gradle settings configured through the IDE *will override* 4 | # any settings specified in this file. 5 | # For more details on how to configure your build environment visit 6 | # http://www.gradle.org/docs/current/userguide/build_environment.html 7 | # Specifies the JVM arguments used for the daemon process. 8 | # The setting is particularly useful for tweaking memory settings. 9 | android.enableJetifier=true 10 | android.useAndroidX=true 11 | org.gradle.jvmargs=-Xmx1536m 12 | # When configured, Gradle will run in incubating parallel mode. 13 | # This option should only be used with decoupled projects. More details, visit 14 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 15 | # org.gradle.parallel=true 16 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/IConnectionInterface.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector; 2 | 3 | import com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions.InvalidSQLPacketException; 4 | import com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions.MySQLConnException; 5 | import com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions.MySQLException; 6 | 7 | import java.io.IOException; 8 | 9 | public interface IConnectionInterface 10 | { 11 | void actionCompleted(); 12 | void handleInvalidSQLPacketException(InvalidSQLPacketException ex); 13 | void handleMySQLException(MySQLException ex); 14 | void handleIOException(IOException ex); 15 | void handleMySQLConnException(MySQLConnException ex); 16 | void handleException(Exception exception); 17 | } 18 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/IResultInterface.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector; 2 | 3 | import com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions.InvalidSQLPacketException; 4 | import com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions.MySQLConnException; 5 | import com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions.MySQLException; 6 | 7 | import java.io.IOException; 8 | 9 | public interface IResultInterface 10 | { 11 | void executionComplete(ResultSet resultSet); 12 | void handleInvalidSQLPacketException(InvalidSQLPacketException ex); 13 | void handleMySQLException(MySQLException ex); 14 | void handleIOException(IOException ex); 15 | void handleMySQLConnException(MySQLConnException ex); 16 | void handleException(Exception ex); 17 | } 18 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/Exceptions/MySQLException.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions; 2 | 3 | public class MySQLException extends Exception 4 | { 5 | private String message; 6 | private int errorCode; 7 | private int sqlState; 8 | 9 | public MySQLException(String message, int sqlErrorCode, int sqlState) 10 | { 11 | super(message); 12 | this.message = message; 13 | this.errorCode = sqlErrorCode; 14 | this.sqlState = sqlState; 15 | } 16 | 17 | public String getMessage() 18 | { 19 | return this.message; 20 | } 21 | 22 | public int getSQLErrorCode() 23 | { 24 | return this.errorCode; 25 | } 26 | 27 | public int getSQLState() 28 | { 29 | return this.sqlState; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/Exceptions/MySQLConnException.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions; 2 | 3 | public class MySQLConnException extends Exception 4 | { 5 | private String message; 6 | private int errorCode; 7 | private int sqlState; 8 | 9 | public MySQLConnException(String message, int sqlErrorCode, int sqlState) 10 | { 11 | super(message); 12 | this.message = message; 13 | this.errorCode = sqlErrorCode; 14 | this.sqlState = sqlState; 15 | } 16 | 17 | public String getMessage() 18 | { 19 | return this.message; 20 | } 21 | 22 | public int getSQLErrorCode() 23 | { 24 | return this.errorCode; 25 | } 26 | 27 | public int getSQLState() 28 | { 29 | return this.sqlState; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /.idea/gradle.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 21 | 22 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/Exceptions/UnsupportedMySQLServerException.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions; 2 | 3 | 4 | import com.BoardiesITSolutions.AndroidMySQLConnector.Connection; 5 | 6 | public class UnsupportedMySQLServerException extends Exception 7 | { 8 | public UnsupportedMySQLServerException(Connection connection) 9 | { 10 | super("MySQL Server version " + connection.getMajorVersion() + "." 11 | + connection.getMinorVersion() + "." + connection.getSubMinorVersion() + " is not currently supported"); 12 | } 13 | 14 | public UnsupportedMySQLServerException(Connection connection, String message) 15 | { 16 | super(message + " MySQL Server Version " + connection.getMajorVersion() + "." + connection.getMinorVersion() + "." + connection.getSubMinorVersion()); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/ResultSet.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector; 2 | 3 | import java.util.List; 4 | 5 | public class ResultSet 6 | { 7 | private List columnDefinitions; 8 | private List rows; 9 | private int currentRow = 0; 10 | 11 | public ResultSet (List columnDefinitions, List rows) 12 | { 13 | this.columnDefinitions = columnDefinitions; 14 | this.rows = rows; 15 | } 16 | 17 | /** 18 | * Return a list of Column Definitions 19 | * @return 20 | */ 21 | public List getFields() 22 | { 23 | return this.columnDefinitions; 24 | } 25 | 26 | public MySQLRow getNextRow() 27 | { 28 | if (currentRow > this.rows.size()-1) 29 | { 30 | return null; 31 | } 32 | MySQLRow row = this.rows.get(this.currentRow); 33 | this.currentRow++; 34 | return row; 35 | } 36 | 37 | public int getNumRows() 38 | { 39 | return this.rows.size(); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Boardies IT Solutions 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 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 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/drawable-v24/ic_launcher_foreground.xml: -------------------------------------------------------------------------------- 1 | 7 | 12 | 13 | 19 | 22 | 25 | 26 | 27 | 28 | 34 | 35 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/Helpers.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector; 2 | 3 | import java.io.IOException; 4 | 5 | public class Helpers 6 | { 7 | public enum MYSQL_PACKET_TYPE {MYSQL_ERROR_PACKET, MYSQL_OK_PACKET, MYSQL_EOF_PACKET, NOT_MYSQL_PACKET, MYSQL_UNKNOWN_PACKET} 8 | 9 | /** 10 | * Checks the packet to see if it is a MySQL Error Packet or an OK packet 11 | * @param socketData The full socket data byte array that should be checked 12 | * @return MYSQL_PACKET_TYPE 13 | */ 14 | public static MYSQL_PACKET_TYPE getMySQLPacketType(byte[] socketData) throws IOException 15 | { 16 | //The packet type to check is the 4th byte, the first 3 bytes is the packet length, 17 | //the 4th byte is the MySQL sequence number and the 5th byte is the packet type 18 | if (socketData.length < 4) 19 | { 20 | IOException exception = new IOException("Unexpected SQL Packet Size. Got Size: " + socketData.length); 21 | exception.printStackTrace(); 22 | throw exception; 23 | } 24 | 25 | int packetType = socketData[4] & 0xff; 26 | if ((packetType & 0xff) == 0xff) 27 | { 28 | return MYSQL_PACKET_TYPE.MYSQL_ERROR_PACKET; 29 | } 30 | else if ((packetType & 0xfe) == 0xfe) 31 | { 32 | return MYSQL_PACKET_TYPE.MYSQL_EOF_PACKET; 33 | } 34 | else if (packetType == 0) 35 | { 36 | return MYSQL_PACKET_TYPE.MYSQL_OK_PACKET; 37 | } 38 | else 39 | { 40 | return MYSQL_PACKET_TYPE.NOT_MYSQL_PACKET; 41 | } 42 | } 43 | 44 | public static MYSQL_PACKET_TYPE getMySQLPacketTypeFromIntWithoutShift(int packetType) 45 | { 46 | packetType = packetType & 0xff; 47 | if ((packetType & 0xff) == 0xff) 48 | { 49 | return MYSQL_PACKET_TYPE.MYSQL_ERROR_PACKET; 50 | } 51 | else if ((packetType & 0xfe) == 0xfe) 52 | { 53 | return MYSQL_PACKET_TYPE.MYSQL_EOF_PACKET; 54 | } 55 | else 56 | { 57 | return MYSQL_PACKET_TYPE.MYSQL_OK_PACKET; 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/PacketManager/SSLRequest.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector.PacketManager; 2 | 3 | import com.BoardiesITSolutions.AndroidMySQLConnector.Connection; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.DataOutputStream; 7 | import java.io.IOException; 8 | import java.nio.ByteBuffer; 9 | import java.nio.charset.Charset; 10 | 11 | public class SSLRequest extends BasePacket 12 | { 13 | private ByteArrayOutputStream byteArrayOutputStream; 14 | private DataOutputStream dataOutPacket; 15 | private Charset charset; 16 | 17 | public SSLRequest(Connection mysqlConn) throws IOException 18 | { 19 | super(mysqlConn); 20 | this.byteArrayOutputStream = new ByteArrayOutputStream(); 21 | this.dataOutPacket = new DataOutputStream(byteArrayOutputStream); 22 | this.charset = charset.forName("UTF-8"); 23 | this.createSSLRequestPacket(); 24 | } 25 | 26 | private void createSSLRequestPacket() throws IOException 27 | { 28 | //Add the server capabilities flag 29 | //The lower two bytes of the server capabilities 30 | byte[] byteArray = new byte[2]; 31 | byteArray[0] = (byte) (this.mysqlConn.getClientCapabilities() & 0xff); 32 | byteArray[1] = (byte) (this.mysqlConn.getClientCapabilities() >>> 8 & 0xff); 33 | dataOutPacket.write(byteArray); //Capability Flags 34 | 35 | //Add the extended server capabilities flag 36 | //The upper two bytes of the server capabilities flag 37 | byteArray = new byte[2]; 38 | byteArray[0] = (byte) (this.mysqlConn.getClientCapabilities() >>> 16 & 0xff); 39 | byteArray[1] = (byte) (this.mysqlConn.getClientCapabilities() >>> 24 & 0xff); 40 | dataOutPacket.write(byteArray); 41 | 42 | ByteBuffer buffer = ByteBuffer.allocate(4); 43 | buffer.putInt(Integer.reverseBytes(16777215)); 44 | dataOutPacket.write(buffer.array()); //Max Packet Size 45 | buffer.clear(); 46 | 47 | //TODO Set the charset based on the server connection properties 48 | dataOutPacket.writeByte(0x21); //Charset (Hardcoded to UTF-8) 49 | dataOutPacket.write(new byte[23]); 50 | } 51 | 52 | @Override 53 | public ByteArrayOutputStream getPacketData() throws IOException 54 | { 55 | return this.createPacketWithPayload(this.byteArrayOutputStream); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/SocketSender.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector; 2 | 3 | import android.os.AsyncTask; 4 | import android.util.Log; 5 | import java.io.BufferedOutputStream; 6 | import java.io.IOException; 7 | import java.net.Socket; 8 | 9 | import static com.BoardiesITSolutions.AndroidMySQLConnector.Connection.CLIENT_SSL; 10 | 11 | public class SocketSender extends AsyncTask 12 | { 13 | IIntConnectionInterface iIntConnectionInterface; 14 | Connection mysqlConn; 15 | private static final String TAG = "SocketSender"; 16 | 17 | public SocketSender(Connection mysqlConn, IIntConnectionInterface iIntConnectionInterface) 18 | { 19 | this.iIntConnectionInterface = iIntConnectionInterface; 20 | this.mysqlConn = mysqlConn; 21 | } 22 | 23 | @Override 24 | protected Void doInBackground(byte[]... bytes) 25 | { 26 | try 27 | { 28 | byte[] byteArray = bytes[0]; 29 | if (byteArray == null) 30 | { 31 | //If we have no bytes to send - we're just establishing the initial socket connection 32 | Socket socket = new Socket(this.mysqlConn.getHostname(), this.mysqlConn.getPort()); 33 | //We've create the socket return the callback so we can process the socket data 34 | this.mysqlConn.setSocket(socket); 35 | this.mysqlConn.setMySQLIO(new MySQLIO(this.mysqlConn, socket)); 36 | } 37 | else 38 | { 39 | BufferedOutputStream sockOut; 40 | if ((this.mysqlConn.getSSLSocket() != null) && (this.mysqlConn.getServerCapabilities() & CLIENT_SSL) == CLIENT_SSL) 41 | { 42 | sockOut = new BufferedOutputStream(this.mysqlConn.getSSLSocket().getOutputStream()); 43 | } 44 | else 45 | { 46 | sockOut = new BufferedOutputStream(this.mysqlConn.getPlainSocket().getOutputStream()); 47 | } 48 | 49 | sockOut.write(byteArray); 50 | sockOut.flush(); 51 | } 52 | this.mysqlConn.getMysqlIO().reset(); 53 | iIntConnectionInterface.socketDataSent(); 54 | } 55 | catch (IOException ex) 56 | { 57 | Log.e(TAG, ex.toString()); 58 | } 59 | return null; 60 | } 61 | 62 | 63 | } 64 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS= 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/build.gradle: -------------------------------------------------------------------------------- 1 | 2 | 3 | buildscript { 4 | repositories { 5 | if (rootProject.ext.publishToMavenLocal) { 6 | 7 | mavenLocal() 8 | } 9 | } 10 | } 11 | 12 | apply plugin: 'com.android.library' 13 | if (rootProject.ext.publishToMavenLocal) 14 | { 15 | apply plugin: 'maven-publish' 16 | } 17 | else 18 | { 19 | apply plugin: 'com.github.dcendents.android-maven' 20 | } 21 | 22 | archivesBaseName="AndroidMySQLConnector" 23 | version '0.28' 24 | group 'com.BoardiesITSolutions' 25 | 26 | 27 | android { 28 | compileSdkVersion 29 29 | defaultConfig { 30 | //applicationId "com.BoardiesITSolutions.AndroidMySQLConnector" 31 | minSdkVersion 19 32 | targetSdkVersion 29 33 | versionCode 20 34 | versionName "0.28" 35 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 36 | } 37 | buildTypes { 38 | release { 39 | minifyEnabled false 40 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 41 | } 42 | } 43 | 44 | lintOptions { 45 | abortOnError false 46 | } 47 | 48 | 49 | if (rootProject.ext.publishToMavenLocal) { 50 | android.libraryVariants 51 | 52 | publishing { 53 | publications { 54 | maven(MavenPublication) 55 | { 56 | artifact getArtifactFullPath() 57 | } 58 | } 59 | } 60 | 61 | libraryVariants.all { variant -> 62 | variant.outputs.all { output -> 63 | def outputFile = output.outputFile 64 | if (outputFile != null && outputFile.name.endsWith('.aar')) { 65 | def fileName = "${archivesBaseName}-${version}.aar" 66 | //output.outputFile = new File(outputFile.parent, fileName) 67 | outputFileName = fileName 68 | } 69 | } 70 | } 71 | } 72 | 73 | repositories { 74 | if (rootProject.ext.publishToMavenLocal) 75 | { 76 | mavenLocal() 77 | } 78 | jcenter() 79 | maven { 80 | url "https://maven.google.com" 81 | } 82 | } 83 | } 84 | 85 | def getArtifactFullPath() { 86 | return ".//build/outputs/aar/${archivesBaseName}-${project.version}.aar" 87 | } 88 | 89 | dependencies { 90 | implementation fileTree(dir: 'libs', include: ['*.jar']) 91 | implementation 'androidx.appcompat:appcompat:1.1.0' 92 | } 93 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/MySQLRow.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector; 2 | 3 | import com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions.SQLColumnNotFoundException; 4 | import java.util.HashMap; 5 | import java.util.Iterator; 6 | import java.util.Map; 7 | import java.util.Set; 8 | 9 | public class MySQLRow 10 | { 11 | private HashMap columnAndRowValue; 12 | 13 | public MySQLRow() 14 | { 15 | this.columnAndRowValue = new HashMap<>(); 16 | } 17 | 18 | public void addRowValue(ColumnDefinition columnDefinition, Object rowValue) 19 | { 20 | this.columnAndRowValue.put(columnDefinition, rowValue); 21 | } 22 | 23 | public String getString(String column) throws SQLColumnNotFoundException 24 | { 25 | Set set = this.columnAndRowValue.entrySet(); 26 | Iterator iterator = set.iterator(); 27 | while (iterator.hasNext()) 28 | { 29 | Map.Entry entry = (Map.Entry)iterator.next(); 30 | ColumnDefinition col = (ColumnDefinition)entry.getKey(); 31 | if (col.getColumnName().equals(column)) 32 | { 33 | return (String)entry.getValue(); 34 | } 35 | } 36 | throw new SQLColumnNotFoundException("'"+column+"' was not found in result set"); 37 | } 38 | 39 | public int getInt(String column) throws SQLColumnNotFoundException 40 | { 41 | String value = this.getString(column); 42 | return Integer.parseInt(value); 43 | } 44 | 45 | public float getFloat(String column) throws SQLColumnNotFoundException 46 | { 47 | String value = this.getString(column); 48 | return Float.parseFloat(value); 49 | } 50 | 51 | public double getDouble(String column) throws SQLColumnNotFoundException 52 | { 53 | String value = this.getString(column); 54 | return Double.parseDouble(value); 55 | } 56 | 57 | public byte[] getBlob(String column) throws SQLColumnNotFoundException 58 | { 59 | Set set = this.columnAndRowValue.entrySet(); 60 | Iterator iterator = set.iterator(); 61 | while (iterator.hasNext()) 62 | { 63 | Map.Entry entry = (Map.Entry)iterator.next(); 64 | ColumnDefinition col = (ColumnDefinition)entry.getKey(); 65 | if (col.getColumnName().equals(column)) 66 | { 67 | return (byte[])entry.getValue(); 68 | } 69 | } 70 | throw new SQLColumnNotFoundException("'"+column+"' was not found in result set"); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/PacketManager/BasePacket.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector.PacketManager; 2 | 3 | import com.BoardiesITSolutions.AndroidMySQLConnector.Connection; 4 | 5 | import java.io.ByteArrayOutputStream; 6 | import java.io.DataOutputStream; 7 | import java.io.IOException; 8 | 9 | public abstract class BasePacket 10 | { 11 | private ByteArrayOutputStream byteArrayOutputStream; 12 | private int packetLength = 0; 13 | private int packetSequenceNumber = 0; 14 | 15 | protected Connection mysqlConn; 16 | 17 | 18 | public BasePacket(Connection mysqlConn) 19 | { 20 | this.mysqlConn = mysqlConn; 21 | this.byteArrayOutputStream = new ByteArrayOutputStream(); 22 | //this.dataOutPacket = new DataOutputStream(this.byteArrayOutputStream); 23 | } 24 | 25 | protected void setPacketLength(int packetLength) 26 | { 27 | this.packetLength = packetLength; 28 | } 29 | 30 | protected int getPacketLength() 31 | { 32 | return this.packetLength; 33 | } 34 | 35 | protected void setPacketSequenceNumber(int sequenceNumber) 36 | { 37 | this.packetSequenceNumber = sequenceNumber; 38 | } 39 | 40 | protected int getPacketSequenceNumber() 41 | { 42 | return this.packetSequenceNumber; 43 | } 44 | 45 | public final String readString(byte[] byteBuffer) { 46 | int position = 0; 47 | int i = 0; 48 | int len = 0; 49 | int maxLen = 21; 50 | 51 | while ((i < maxLen) && (byteBuffer[i] != 0)) { 52 | len++; 53 | i++; 54 | } 55 | 56 | String s = Connection.toString(byteBuffer, position, len); 57 | 58 | return s; 59 | } 60 | 61 | public ByteArrayOutputStream createPacketWithPayload(ByteArrayOutputStream payload) throws IOException 62 | { 63 | ByteArrayOutputStream byteArrayStream = new ByteArrayOutputStream(); 64 | DataOutputStream dataOutputStream = new DataOutputStream(byteArrayStream); 65 | 66 | int packetLength = payload.size(); 67 | 68 | byte[] packetLengthArray = new byte[3]; 69 | packetLengthArray[0] = (byte)(packetLength & 0xff); 70 | packetLengthArray[1] = (byte)((packetLength >>> 8) & 0xff); 71 | packetLengthArray[2] = (byte)((packetLength >>> 16) & 0xff); 72 | dataOutputStream.write(packetLengthArray); 73 | 74 | //Add the sequence number 75 | dataOutputStream.writeByte((byte)this.mysqlConn.getConnectionPacketSequence()); 76 | 77 | //Add the payload to the packet 78 | dataOutputStream.write(payload.toByteArray()); 79 | return byteArrayStream; 80 | } 81 | 82 | 83 | 84 | public abstract ByteArrayOutputStream getPacketData() throws IOException; 85 | } 86 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/PacketManager/COM_Query.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector.PacketManager; 2 | 3 | import com.BoardiesITSolutions.AndroidMySQLConnector.Connection; 4 | import java.io.ByteArrayOutputStream; 5 | import java.io.DataOutputStream; 6 | import java.io.IOException; 7 | 8 | public class COM_Query extends BasePacket 9 | { 10 | //Query Command Values 11 | public static final int COM_SLEEP = 0x00; 12 | public static final int COM_QUIT = 0x01; 13 | public static final int COM_INIT_DB = 0x02; 14 | public static final int COM_QUERY = 0x03; 15 | public static final int COM_FIELD_LIST = 0x04; //Deprecated as of MySQL 5.7.11 16 | public static final int COM_CREATE_DB = 0x05; 17 | public static final int COM_DROP_DB = 0x06; 18 | public static final int COM_REFRESH = 0x07; //Deprecated as of MySQL 5.7.11 19 | public static final int COM_SHUTDOWN = 0x08; //Deprecated as of MySQL 5.7.9 20 | public static final int COM_STATISTICS = 0x09; 21 | public static final int COM_PROCESS_INFO = 0x0a; //Deprecated as of MySQL 5.7.11 22 | public static final int COM_CONNECT = 0x0b; 23 | public static final int COM_PROCESS_KILL = 0x0c; //Deprecated as of MySQL 5.7.11 24 | public static final int COM_DEBUG = 0x0f; 25 | public static final int COM_PING = 0x0e; 26 | public static final int COM_TIME = 0x0f; 27 | public static final int COM_DELAYED_INSERT = 0x10; 28 | public static final int COM_CHANGE_USER = 0x11; 29 | public static final int COM_RESET_CONNECTION = 0x1f; 30 | public static final int COM_DAEMON = 0x1d; 31 | 32 | private ByteArrayOutputStream byteArrayOutputStream; 33 | private DataOutputStream dataOutPacket; 34 | 35 | public COM_Query(Connection mysqlConn, int commandCode, String command) throws IOException, UnsupportedOperationException 36 | { 37 | super(mysqlConn); 38 | if (isComQuerySupported(commandCode)) 39 | { 40 | this.byteArrayOutputStream = new ByteArrayOutputStream(); 41 | this.dataOutPacket = new DataOutputStream(byteArrayOutputStream); 42 | this.createPacket(commandCode, command); 43 | } 44 | else 45 | { 46 | throw new UnsupportedOperationException("Command code: " + commandCode + " is not currently supported by the client"); 47 | } 48 | } 49 | 50 | private void createPacket(int commandCode, String command) throws IOException 51 | { 52 | this.dataOutPacket.writeByte((byte)commandCode); 53 | if (command != null && commandCode != COM_QUIT) 54 | { 55 | this.dataOutPacket.write((command).getBytes("UTF-8")); 56 | } 57 | } 58 | 59 | private boolean isComQuerySupported(int commandCode) 60 | { 61 | switch (commandCode) 62 | { 63 | case COM_QUERY: 64 | case COM_INIT_DB: 65 | case COM_QUIT: 66 | return true; 67 | default: 68 | return false; 69 | } 70 | } 71 | 72 | @Override 73 | public ByteArrayOutputStream getPacketData() throws IOException 74 | { 75 | return this.createPacketWithPayload(this.byteArrayOutputStream); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/PacketManager/MySQLErrorPacket.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector.PacketManager; 2 | 3 | import android.os.Build; 4 | import androidx.annotation.RequiresApi; 5 | 6 | import com.BoardiesITSolutions.AndroidMySQLConnector.Connection; 7 | import com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions.InvalidSQLPacketException; 8 | import com.BoardiesITSolutions.AndroidMySQLConnector.Helpers; 9 | 10 | import java.io.ByteArrayOutputStream; 11 | import java.io.IOException; 12 | 13 | public class MySQLErrorPacket extends BasePacket 14 | { 15 | private int errorCode = 0; 16 | private int sqlState = 0; 17 | private String errorMsg; 18 | 19 | @RequiresApi(api = Build.VERSION_CODES.KITKAT) 20 | public MySQLErrorPacket(Connection mysqlConn) throws IOException, InvalidSQLPacketException 21 | { 22 | super(mysqlConn); 23 | this.processPacket(); 24 | } 25 | 26 | public int getErrorCode() 27 | { 28 | return this.errorCode; 29 | } 30 | public int getSqlState() 31 | { 32 | return this.sqlState; 33 | } 34 | public String getErrorMsg() 35 | { 36 | return this.errorMsg; 37 | } 38 | 39 | @RequiresApi(api = Build.VERSION_CODES.KITKAT) 40 | private void processPacket() throws IOException, InvalidSQLPacketException 41 | { 42 | this.setPacketLength(this.mysqlConn.getMysqlIO().fromByteArray((byte[]) this.mysqlConn.getMysqlIO().extractDataAsString(3))); 43 | 44 | this.setPacketSequenceNumber((byte)this.mysqlConn.getMysqlIO().extractDataAsString(1)); 45 | 46 | 47 | if (Helpers.getMySQLPacketType(this.mysqlConn.getMysqlIO().getSocketByteArray()) != Helpers.MYSQL_PACKET_TYPE.MYSQL_ERROR_PACKET) { 48 | throw new InvalidSQLPacketException("Error: Trying to process a MySQL Error Packet but we don't have the expected packet header for a MySQL Error Packet"); 49 | } 50 | 51 | //Shift one is this the packet type header - this is checked above but the check doesn't shift the byte position 52 | this.mysqlConn.getMysqlIO().shiftCurrentBytePosition(1); 53 | byte[] errorCodeArray = (byte[]) this.mysqlConn.getMysqlIO().extractDataAsString(2); 54 | errorCodeArray = this.mysqlConn.getMysqlIO().swapByteArray(errorCodeArray); 55 | this.errorCode = (this.mysqlConn.getMysqlIO().fromByteArray(errorCodeArray) << 8); 56 | 57 | byte sqlStateSplitter = (byte) this.mysqlConn.getMysqlIO().extractDataAsString(1); 58 | if ((sqlStateSplitter & 0xff) == 0x23) { 59 | //We'll have the SQL state here 60 | byte[] sqlStateArray = (byte[]) this.mysqlConn.getMysqlIO().extractDataAsString(5); 61 | this.sqlState = (this.mysqlConn.getMysqlIO().fromByteArray(sqlStateArray) << 8); 62 | } 63 | this.errorMsg = this.mysqlConn.getMysqlIO().extractDataAsString(false); 64 | } 65 | 66 | /** 67 | * This function is part of the base class but should not be needed for MySQL Error Packets 68 | * @return 69 | * @throws IOException 70 | */ 71 | @Override 72 | public ByteArrayOutputStream getPacketData() 73 | { 74 | throw new UnsupportedOperationException(); 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /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 contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. 6 | 7 | ## Our Standards 8 | 9 | Examples of behavior that contributes to creating a positive environment include: 10 | 11 | * Using welcoming and inclusive language 12 | * Being respectful of differing viewpoints and experiences 13 | * Gracefully accepting constructive criticism 14 | * Focusing on what is best for the community 15 | * Showing empathy towards other community members 16 | 17 | Examples of unacceptable behavior by participants include: 18 | 19 | * The use of sexualized language or imagery and unwelcome sexual attention or advances 20 | * Trolling, insulting/derogatory comments, and personal or political attacks 21 | * Public or private harassment 22 | * Publishing others' private information, such as a physical or electronic address, without explicit permission 23 | * Other conduct which could reasonably be considered inappropriate in a professional setting 24 | 25 | ## Our Responsibilities 26 | 27 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. 28 | 29 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. 30 | 31 | ## Scope 32 | 33 | This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. 34 | 35 | ## Enforcement 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at support@boardiesitsolutions.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. 38 | 39 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. 40 | 41 | ## Attribution 42 | 43 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] 44 | 45 | [homepage]: http://contributor-covenant.org 46 | [version]: http://contributor-covenant.org/version/1/4/ 47 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/PacketManager/MySQLOKPacket.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector.PacketManager; 2 | 3 | import android.util.Log; 4 | 5 | import com.BoardiesITSolutions.AndroidMySQLConnector.Connection; 6 | import com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions.InvalidSQLPacketException; 7 | import com.BoardiesITSolutions.AndroidMySQLConnector.Helpers; 8 | 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | 12 | public class MySQLOKPacket extends BasePacket 13 | { 14 | private int affectedRows = 0; 15 | private int lastInsertID = 0; 16 | 17 | public MySQLOKPacket(Connection mysqlConn) throws IOException, InvalidSQLPacketException 18 | { 19 | super(mysqlConn); 20 | processPacket(); 21 | } 22 | 23 | public int getAffectedRows() 24 | { 25 | return this.affectedRows; 26 | } 27 | 28 | public int getLastInsertID() 29 | { 30 | return this.lastInsertID; 31 | } 32 | 33 | private void processPacket() throws IOException, InvalidSQLPacketException 34 | { 35 | this.setPacketLength(this.mysqlConn.getMysqlIO().fromByteArray((byte[]) this.mysqlConn.getMysqlIO().extractDataAsString(3))); 36 | this.setPacketSequenceNumber((byte)this.mysqlConn.getMysqlIO().extractDataAsString(1)); 37 | 38 | 39 | Helpers.MYSQL_PACKET_TYPE packetType = Helpers.getMySQLPacketType(this.mysqlConn.getMysqlIO().getSocketByteArray()); 40 | //EOF Packets serve the same purpose - so check whether its OK or an EOF packet to process 41 | if (packetType != Helpers.MYSQL_PACKET_TYPE.MYSQL_OK_PACKET && packetType != Helpers.MYSQL_PACKET_TYPE.MYSQL_EOF_PACKET) { 42 | 43 | throw new InvalidSQLPacketException("Error: Trying to process a MySQL OK Packet but we don't have the expected packet header for a MySQL OK Packet"); 44 | } 45 | 46 | //Shift one is this the packet type header - this is checked above but the check doesn't shift the byte position 47 | this.mysqlConn.getMysqlIO().shiftCurrentBytePosition(1); 48 | 49 | //Now process the OK packet payload 50 | 51 | affectedRows = this.mysqlConn.getMysqlIO().getLenEncodedInt(); 52 | 53 | lastInsertID = this.mysqlConn.getMysqlIO().getLenEncodedInt(); 54 | 55 | int statusFlags = 0; 56 | int warningsCount = 0; 57 | if ((this.mysqlConn.getServerCapabilities() & Connection.CLIENT_PROTOCOL_41) == Connection.CLIENT_PROTOCOL_41) 58 | { 59 | statusFlags = this.mysqlConn.getMysqlIO().fromByteArray((byte[])this.mysqlConn.getMysqlIO().extractDataAsString(2)); 60 | 61 | warningsCount = this.mysqlConn.getMysqlIO().fromByteArray((byte[])this.mysqlConn.getMysqlIO().extractDataAsString(2)); 62 | } 63 | 64 | Log.d("MySQLOKPacket", "Affected Rows: " + affectedRows); 65 | Log.d("MySQLOKPacket", "Last Insert ID: " + lastInsertID); 66 | Log.d("MySQLOKPacket", "Status Flags: " + statusFlags); 67 | Log.d("MySQLOKPacket", "Warnings Count:" + warningsCount); 68 | 69 | } 70 | 71 | /** 72 | * This is part of the base class but is not needed for a MySQL OK Packet 73 | * @return 74 | * @throws IOException 75 | */ 76 | @Override 77 | public ByteArrayOutputStream getPacketData() throws IOException 78 | { 79 | throw new UnsupportedOperationException(); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /.idea/codeStyles/Project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 |
7 | 8 | 9 | 10 | xmlns:android 11 | 12 | ^$ 13 | 14 | 15 | 16 |
17 |
18 | 19 | 20 | 21 | xmlns:.* 22 | 23 | ^$ 24 | 25 | 26 | BY_NAME 27 | 28 |
29 |
30 | 31 | 32 | 33 | .*:id 34 | 35 | http://schemas.android.com/apk/res/android 36 | 37 | 38 | 39 |
40 |
41 | 42 | 43 | 44 | .*:name 45 | 46 | http://schemas.android.com/apk/res/android 47 | 48 | 49 | 50 |
51 |
52 | 53 | 54 | 55 | name 56 | 57 | ^$ 58 | 59 | 60 | 61 |
62 |
63 | 64 | 65 | 66 | style 67 | 68 | ^$ 69 | 70 | 71 | 72 |
73 |
74 | 75 | 76 | 77 | .* 78 | 79 | ^$ 80 | 81 | 82 | BY_NAME 83 | 84 |
85 |
86 | 87 | 88 | 89 | .* 90 | 91 | http://schemas.android.com/apk/res/android 92 | 93 | 94 | ANDROID_ATTRIBUTE_ORDER 95 | 96 |
97 |
98 | 99 | 100 | 101 | .* 102 | 103 | .* 104 | 105 | 106 | BY_NAME 107 | 108 |
109 |
110 |
111 |
112 |
113 |
-------------------------------------------------------------------------------- /.idea/markdown-navigator.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 36 | 37 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | ############################################################################## 4 | ## 5 | ## Gradle start up script for UN*X 6 | ## 7 | ############################################################################## 8 | 9 | # Attempt to set APP_HOME 10 | # Resolve links: $0 may be a link 11 | PRG="$0" 12 | # Need this for relative symlinks. 13 | while [ -h "$PRG" ] ; do 14 | ls=`ls -ld "$PRG"` 15 | link=`expr "$ls" : '.*-> \(.*\)$'` 16 | if expr "$link" : '/.*' > /dev/null; then 17 | PRG="$link" 18 | else 19 | PRG=`dirname "$PRG"`"/$link" 20 | fi 21 | done 22 | SAVED="`pwd`" 23 | cd "`dirname \"$PRG\"`/" >/dev/null 24 | APP_HOME="`pwd -P`" 25 | cd "$SAVED" >/dev/null 26 | 27 | APP_NAME="Gradle" 28 | APP_BASE_NAME=`basename "$0"` 29 | 30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 31 | DEFAULT_JVM_OPTS="" 32 | 33 | # Use the maximum available, or set MAX_FD != -1 to use that value. 34 | MAX_FD="maximum" 35 | 36 | warn () { 37 | echo "$*" 38 | } 39 | 40 | die () { 41 | echo 42 | echo "$*" 43 | echo 44 | exit 1 45 | } 46 | 47 | # OS specific support (must be 'true' or 'false'). 48 | cygwin=false 49 | msys=false 50 | darwin=false 51 | nonstop=false 52 | case "`uname`" in 53 | CYGWIN* ) 54 | cygwin=true 55 | ;; 56 | Darwin* ) 57 | darwin=true 58 | ;; 59 | MINGW* ) 60 | msys=true 61 | ;; 62 | NONSTOP* ) 63 | nonstop=true 64 | ;; 65 | esac 66 | 67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 68 | 69 | # Determine the Java command to use to start the JVM. 70 | if [ -n "$JAVA_HOME" ] ; then 71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 72 | # IBM's JDK on AIX uses strange locations for the executables 73 | JAVACMD="$JAVA_HOME/jre/sh/java" 74 | else 75 | JAVACMD="$JAVA_HOME/bin/java" 76 | fi 77 | if [ ! -x "$JAVACMD" ] ; then 78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 79 | 80 | Please set the JAVA_HOME variable in your environment to match the 81 | location of your Java installation." 82 | fi 83 | else 84 | JAVACMD="java" 85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 86 | 87 | Please set the JAVA_HOME variable in your environment to match the 88 | location of your Java installation." 89 | fi 90 | 91 | # Increase the maximum file descriptors if we can. 92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 93 | MAX_FD_LIMIT=`ulimit -H -n` 94 | if [ $? -eq 0 ] ; then 95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 96 | MAX_FD="$MAX_FD_LIMIT" 97 | fi 98 | ulimit -n $MAX_FD 99 | if [ $? -ne 0 ] ; then 100 | warn "Could not set maximum file descriptor limit: $MAX_FD" 101 | fi 102 | else 103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 104 | fi 105 | fi 106 | 107 | # For Darwin, add options to specify how the application appears in the dock 108 | if $darwin; then 109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 110 | fi 111 | 112 | # For Cygwin, switch paths to Windows format before running java 113 | if $cygwin ; then 114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 116 | JAVACMD=`cygpath --unix "$JAVACMD"` 117 | 118 | # We build the pattern for arguments to be converted via cygpath 119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 120 | SEP="" 121 | for dir in $ROOTDIRSRAW ; do 122 | ROOTDIRS="$ROOTDIRS$SEP$dir" 123 | SEP="|" 124 | done 125 | OURCYGPATTERN="(^($ROOTDIRS))" 126 | # Add a user-defined pattern to the cygpath arguments 127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 129 | fi 130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 131 | i=0 132 | for arg in "$@" ; do 133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 135 | 136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 138 | else 139 | eval `echo args$i`="\"$arg\"" 140 | fi 141 | i=$((i+1)) 142 | done 143 | case $i in 144 | (0) set -- ;; 145 | (1) set -- "$args0" ;; 146 | (2) set -- "$args0" "$args1" ;; 147 | (3) set -- "$args0" "$args1" "$args2" ;; 148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;; 149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 154 | esac 155 | fi 156 | 157 | # Escape application args 158 | save () { 159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 160 | echo " " 161 | } 162 | APP_ARGS=$(save "$@") 163 | 164 | # Collect all arguments for the java command, following the shell quoting and substitution rules 165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 166 | 167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong 168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then 169 | cd "$(dirname "$0")" 170 | fi 171 | 172 | exec "$JAVACMD" "$@" 173 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/ColumnDefinition.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector; 2 | 3 | public class ColumnDefinition 4 | { 5 | public enum ColumnType { 6 | DECIMAL, TINY, SHORT, LONG, FLOAT, DOUBLE, NULL, TIMESTAMP, LONGLONG, INT24, DATE, TIME, DATETIME, YEAR, 7 | NEWDATE, VARCHAR, BIT, TIMESTAMP2, DATETIME2, TIME2, NEWDECIMAL, ENUM, SET, TINY_BLOB, MEDIUM_BLOB, 8 | LONG_BLOG, BLOB, VAR_STRING, STRING, GEOMETRY 9 | } 10 | 11 | /** 12 | * Column Flags Bitmask 13 | */ 14 | public static final int FLAG_NOT_NULL = 0x01; 15 | public static final int FLAG_PRIMARY_KEY = 0x02; 16 | public static final int FLAG_UNIQUE_KEY = 0x04; 17 | public static final int FLAG_MULTIPLE_KEY = 0x08; 18 | public static final int FLAG_BLOG_SET = 0x10; 19 | public static final int FLAG_UNSIGNED = 0x20; 20 | public static final int FLAG_ZERO_FILL = 0x40; 21 | public static final int FLAG_BINARY = 0x80; 22 | public static final int FLAG_ENUM = 0x100; 23 | public static final int FLAG_AUTO_INCREMENT = 0x200; 24 | public static final int FLAG_TIMESTAMP = 0x400; 25 | public static final int FLAG_SET = 0x800; 26 | 27 | private String catalog; 28 | private String database; 29 | private String table; 30 | private String column; 31 | private int characterSet; 32 | private ColumnType columnType; 33 | private int flags; 34 | private int decimals; 35 | 36 | public ColumnDefinition(String catalog, String database, String table, String column, int characterSet, 37 | int columnType, int flags, int decimals) 38 | { 39 | this.catalog = catalog; 40 | this.database = database; 41 | this.table = table; 42 | this.column = column; 43 | this.characterSet = characterSet; 44 | this.setColumnType(columnType); 45 | this.flags = flags; 46 | this.decimals = decimals; 47 | } 48 | 49 | public String getDatabase() 50 | { 51 | return this.database; 52 | } 53 | public String getTable() 54 | { 55 | return this.table; 56 | } 57 | public String getColumnName() 58 | { 59 | return this.column; 60 | } 61 | public ColumnType getColumnType() 62 | { 63 | return this.columnType; 64 | } 65 | public int getFlags() 66 | { 67 | return this.flags; 68 | } 69 | public int getDecimals() 70 | { 71 | return this.decimals; 72 | } 73 | 74 | private void setColumnType(int columnType) 75 | { 76 | switch (columnType) 77 | { 78 | case 0x00: 79 | this.columnType = ColumnType.DECIMAL; 80 | break; 81 | case 0x01: 82 | this.columnType = ColumnType.TINY; 83 | break; 84 | case 0x02: 85 | this.columnType = ColumnType.SHORT; 86 | break; 87 | case 0x03: 88 | this.columnType = ColumnType.LONG; 89 | break; 90 | case 0x04: 91 | this.columnType = ColumnType.FLOAT; 92 | break; 93 | case 0x05: 94 | this.columnType = ColumnType.DOUBLE; 95 | break; 96 | case 0x06: 97 | this.columnType = ColumnType.NULL; 98 | break; 99 | case 0x07: 100 | this.columnType = ColumnType.TIMESTAMP; 101 | break; 102 | case 0x08: 103 | this.columnType = ColumnType.LONGLONG; 104 | break; 105 | case 0x09: 106 | this.columnType = ColumnType.INT24; 107 | break; 108 | case 0x0a: 109 | this.columnType = ColumnType.DATE; 110 | break; 111 | case 0x0b: 112 | this.columnType = ColumnType.TIME; 113 | break; 114 | case 0x0c: 115 | this.columnType = ColumnType.DATETIME; 116 | break; 117 | case 0x0d: 118 | this.columnType = ColumnType.YEAR; 119 | break; 120 | case 0x0e: 121 | this.columnType = ColumnType.NEWDATE; 122 | break; 123 | case 0x0f: 124 | this.columnType = ColumnType.VARCHAR; 125 | break; 126 | case 0x10: 127 | this.columnType = ColumnType.BIT; 128 | break; 129 | case 0x11: 130 | this.columnType = ColumnType.TIMESTAMP2; 131 | break; 132 | case 0x12: 133 | this.columnType = ColumnType.DATETIME2; 134 | break; 135 | case 0x13: 136 | this.columnType = ColumnType.TIME2; 137 | break; 138 | case 0xf6: 139 | this.columnType = ColumnType.NEWDECIMAL; 140 | break; 141 | case 0xf7: 142 | this.columnType = ColumnType.ENUM; 143 | break; 144 | case 0xf8: 145 | this.columnType = ColumnType.SET; 146 | break; 147 | case 0xf9: 148 | this.columnType = ColumnType.TINY_BLOB; 149 | break; 150 | case 0xfa: 151 | this.columnType = ColumnType.MEDIUM_BLOB; 152 | break; 153 | case 0xfb: 154 | this.columnType = ColumnType.LONG_BLOG; 155 | break; 156 | case 0xfc: 157 | this.columnType = ColumnType.BLOB; 158 | break; 159 | case 0xfd: 160 | this.columnType = ColumnType.VAR_STRING; 161 | break; 162 | case 0xfe: 163 | this.columnType = ColumnType.STRING; 164 | break; 165 | case 0xff: 166 | this.columnType = ColumnType.GEOMETRY; 167 | break; 168 | 169 | } 170 | } 171 | 172 | public boolean isPrimaryKey() 173 | { 174 | return (this.flags & FLAG_PRIMARY_KEY) == FLAG_PRIMARY_KEY; 175 | } 176 | 177 | } -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/res/drawable/ic_launcher_background.xml: -------------------------------------------------------------------------------- 1 | 2 | 7 | 10 | 15 | 20 | 25 | 30 | 35 | 40 | 45 | 50 | 55 | 60 | 65 | 70 | 75 | 80 | 85 | 90 | 95 | 100 | 105 | 110 | 115 | 120 | 125 | 130 | 135 | 140 | 145 | 150 | 155 | 160 | 165 | 170 | 171 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/PacketManager/COM_QueryResponse.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector.PacketManager; 2 | 3 | import android.util.Log; 4 | 5 | import com.BoardiesITSolutions.AndroidMySQLConnector.ColumnDefinition; 6 | import com.BoardiesITSolutions.AndroidMySQLConnector.Connection; 7 | import com.BoardiesITSolutions.AndroidMySQLConnector.Helpers; 8 | import com.BoardiesITSolutions.AndroidMySQLConnector.MySQLRow; 9 | 10 | import java.io.ByteArrayOutputStream; 11 | import java.io.IOException; 12 | import java.util.ArrayList; 13 | import java.util.List; 14 | 15 | public class COM_QueryResponse extends BasePacket 16 | { 17 | private List columnDefinitions; 18 | private List rows; 19 | 20 | public COM_QueryResponse(Connection mysqlConn) 21 | { 22 | super(mysqlConn); 23 | this.columnDefinitions = new ArrayList<>(); 24 | this.rows = new ArrayList<>(); 25 | this.processPacketData(); 26 | } 27 | 28 | private void processPacketData() 29 | { 30 | this.setPacketLength(this.mysqlConn.getMysqlIO().fromByteArray((byte[]) this.mysqlConn.getMysqlIO().extractDataAsString(3))); 31 | this.setPacketSequenceNumber((byte)this.mysqlConn.getMysqlIO().extractDataAsString(1)); 32 | 33 | int numberOfFields = (byte)this.mysqlConn.getMysqlIO().extractDataAsString(1); 34 | 35 | for (int currentColumnCount = 0; currentColumnCount < numberOfFields; currentColumnCount++) 36 | { 37 | this.mysqlConn.getMysqlIO().shiftCurrentBytePosition(4); 38 | 39 | int catalogLength = (byte)this.mysqlConn.getMysqlIO().getLenEncodedInt(); 40 | String catalog = this.mysqlConn.getMysqlIO().extractDataAsString(false, catalogLength); 41 | 42 | int databaseLength = (byte)this.mysqlConn.getMysqlIO().getLenEncodedInt(); 43 | String database = this.mysqlConn.getMysqlIO().extractDataAsString(false, databaseLength); 44 | 45 | int tableLength = (byte)this.mysqlConn.getMysqlIO().getLenEncodedInt(); 46 | String table = this.mysqlConn.getMysqlIO().extractDataAsString(false, tableLength); 47 | 48 | int origTableLength = (byte)this.mysqlConn.getMysqlIO().getLenEncodedInt(); 49 | String origTable = this.mysqlConn.getMysqlIO().extractDataAsString(false, origTableLength); 50 | 51 | int columnNameLength = (byte)this.mysqlConn.getMysqlIO().getLenEncodedInt(); 52 | String columnName = this.mysqlConn.getMysqlIO().extractDataAsString(false, columnNameLength); 53 | 54 | int origColumnNameLength = (byte)this.mysqlConn.getMysqlIO().getLenEncodedInt(); 55 | String origColumnName = this.mysqlConn.getMysqlIO().extractDataAsString(false, origColumnNameLength); 56 | 57 | int nextLength = (byte)this.mysqlConn.getMysqlIO().getLenEncodedInt(); 58 | 59 | int characterSet = this.mysqlConn.getMysqlIO().fromByteArray((byte[])this.mysqlConn.getMysqlIO().extractDataAsString(2)); 60 | 61 | 62 | //int columnLength = this.mysqlConn.getMysqlIO().fromByteArray((byte[])this.mysqlConn.getMysqlIO().extractDataAsString(4)); 63 | 64 | this.mysqlConn.getMysqlIO().shiftCurrentBytePosition(4); 65 | 66 | //int columnLength = this.mysqlConn.getMysqlIO().getLenEncodedInt(); 67 | 68 | //int columnType = (byte)this.mysqlConn.getMysqlIO().extractDataAsString(1); 69 | int columnType = this.mysqlConn.getMysqlIO().extractData(1)[0] & 0xff; 70 | 71 | Log.d("QueryResponse", "Column Name: " + columnName + " Type: " + columnType); 72 | /*if (columnType == -4) 73 | { 74 | columnType = 0xfc; 75 | }*/ 76 | 77 | int flags = this.mysqlConn.getMysqlIO().fromByteArray((byte[])this.mysqlConn.getMysqlIO().extractDataAsString(2)); 78 | 79 | int decimals = (byte)this.mysqlConn.getMysqlIO().extractDataAsString(1); 80 | 81 | 82 | //2 Byte NULL fillers so shift on another 2 83 | this.mysqlConn.getMysqlIO().shiftCurrentBytePosition(2); 84 | //break; 85 | 86 | this.columnDefinitions.add(new ColumnDefinition(catalog, database, table, columnName, characterSet, 87 | columnType, flags, decimals)); 88 | } 89 | 90 | //MySQL 5.1 appears to add an extra packet between the column definitions and the rows so we'll pass 91 | //it in case we need it - don't think we do though! 92 | if (this.mysqlConn.isConnectedVersionLessThan(5,6,60)) 93 | { 94 | int packetLength = this.mysqlConn.getMysqlIO().fromByteArray((byte[]) this.mysqlConn.getMysqlIO().extractDataAsString(3)); 95 | int packetNumber = (byte) this.mysqlConn.getMysqlIO().extractDataAsString(1); 96 | int eofMarker = (byte) this.mysqlConn.getMysqlIO().extractDataAsString(1); 97 | int warnings = this.mysqlConn.getMysqlIO().fromByteArray((byte[]) this.mysqlConn.getMysqlIO().extractDataAsString(2)); 98 | int serverStatus = this.mysqlConn.getMysqlIO().fromByteArray((byte[]) this.mysqlConn.getMysqlIO().extractDataAsString(2)); 99 | } 100 | do 101 | { 102 | this.mysqlConn.getMysqlIO().shiftCurrentBytePosition(4); 103 | MySQLRow row = new MySQLRow(); 104 | int packetType = this.mysqlConn.getMysqlIO().readCurrentByteWithoutShift(); 105 | if (Helpers.getMySQLPacketTypeFromIntWithoutShift(packetType) == Helpers.MYSQL_PACKET_TYPE.MYSQL_EOF_PACKET) 106 | { 107 | //We've got an EOF packet so we're at the end or an OK packet so we've got everything we need 108 | break; 109 | } 110 | int currentColumn = 0; 111 | for (; currentColumn < numberOfFields; currentColumn++) 112 | { 113 | ColumnDefinition columnDefinition = columnDefinitions.get(currentColumn); 114 | //Log.d("COMQueryResp", "Getting data for " + columnDefinition.getColumnName() + " with type: " + columnDefinition.getColumnType().toString()); 115 | int lengthOfValue = this.mysqlConn.getMysqlIO().getLenEncodedInt(); 116 | 117 | //Believe if the length of value is less than 0 (-5) then the value is NULL 118 | Object value = null; 119 | if (lengthOfValue > 0) 120 | { 121 | if (columnDefinition.getColumnType() == ColumnDefinition.ColumnType.BLOB) 122 | { 123 | value = this.mysqlConn.getMysqlIO().extractData(lengthOfValue); 124 | } 125 | else 126 | { 127 | value = (String)this.mysqlConn.getMysqlIO().extractDataAsString(false, lengthOfValue); 128 | } 129 | 130 | } 131 | else 132 | { 133 | value = null; //If we got less than 0, it should be a -1 value which means it was a NULL value 134 | } 135 | 136 | row.addRowValue(columnDefinition, value); 137 | } 138 | this.rows.add(row); 139 | 140 | }while(true); 141 | } 142 | 143 | public List getColumnDefinitions() 144 | { 145 | return this.columnDefinitions; 146 | } 147 | 148 | public List getRows() 149 | { 150 | return this.rows; 151 | } 152 | 153 | /** 154 | * This isn't neede for COM_Query Response, so don't call it 155 | * @return 156 | * @throws IOException 157 | * @throws UnsupportedOperationException 158 | */ 159 | @Override 160 | public ByteArrayOutputStream getPacketData() throws IOException, UnsupportedOperationException 161 | { 162 | throw new UnsupportedOperationException(); 163 | } 164 | } 165 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/PacketManager/AuthResponse.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector.PacketManager; 2 | 3 | import android.util.Log; 4 | 5 | import com.BoardiesITSolutions.AndroidMySQLConnector.Connection; 6 | 7 | import java.io.ByteArrayOutputStream; 8 | import java.io.DataOutputStream; 9 | import java.io.IOException; 10 | import java.io.UnsupportedEncodingException; 11 | import java.nio.ByteBuffer; 12 | import java.nio.charset.Charset; 13 | import java.security.MessageDigest; 14 | import java.security.NoSuchAlgorithmException; 15 | 16 | import static com.BoardiesITSolutions.AndroidMySQLConnector.Connection.CLIENT_CONNECT_ATTRS; 17 | import static com.BoardiesITSolutions.AndroidMySQLConnector.Connection.CLIENT_CONNECT_WITH_DB; 18 | import static com.BoardiesITSolutions.AndroidMySQLConnector.Connection.CLIENT_PLUGIN_AUTH; 19 | import static com.BoardiesITSolutions.AndroidMySQLConnector.Connection.CLIENT_SECURE_CONNECTION; 20 | 21 | public class AuthResponse extends BasePacket 22 | { 23 | private ByteArrayOutputStream byteArrayOutputStream; 24 | private DataOutputStream dataOutPacket; 25 | private Charset charset; 26 | public AuthResponse(Connection mysqlConn) throws IOException 27 | { 28 | super(mysqlConn); 29 | this.byteArrayOutputStream = new ByteArrayOutputStream(); 30 | this.dataOutPacket = new DataOutputStream(this.byteArrayOutputStream); 31 | this.charset = mysqlConn.getCharset(); 32 | this.createAuthenticationPacket(); 33 | } 34 | 35 | @Override 36 | public ByteArrayOutputStream getPacketData() throws IOException 37 | { 38 | return this.createPacketWithPayload(this.byteArrayOutputStream); 39 | } 40 | 41 | private void createAuthenticationPacket() throws IOException 42 | { 43 | try { 44 | //If the database is not set, update the client capabilities flag, so that the MySQL Server knows not to expect 45 | //a database in the auth request 46 | if ((this.mysqlConn.getDatabase() == null) || this.mysqlConn.getDatabase().length() == 0) { 47 | this.mysqlConn.updateClientCapabilities(~CLIENT_CONNECT_WITH_DB); 48 | } 49 | 50 | //Add the server capabilities flag 51 | //The lower two bytes of the server capabilities 52 | byte[] byteArray = new byte[2]; 53 | byteArray[0] = (byte) (this.mysqlConn.getClientCapabilities() & 0xff); 54 | byteArray[1] = (byte) (this.mysqlConn.getClientCapabilities() >>> 8 & 0xff); 55 | dataOutPacket.write(byteArray); //Capability Flags 56 | 57 | //Add the extended server capabilities flag 58 | //The upper two bytes of the server capabilities flag 59 | byteArray = new byte[2]; 60 | byteArray[0] = (byte) (this.mysqlConn.getClientCapabilities() >>> 16 & 0xff); 61 | byteArray[1] = (byte) (this.mysqlConn.getClientCapabilities() >>> 24 & 0xff); 62 | dataOutPacket.write(byteArray); 63 | 64 | 65 | //Add the max packet size 66 | 67 | ByteBuffer buffer = ByteBuffer.allocate(4); 68 | buffer.putInt(Integer.reverseBytes(16777215)); 69 | dataOutPacket.write(buffer.array()); //Max Packet Size 70 | buffer.clear(); 71 | 72 | Log.d("AuthResponse", "Going to write server language: " + String.format("0x%02X", mysqlConn.getServerLanguage())); 73 | 74 | dataOutPacket.writeByte(mysqlConn.getServerLanguage()); 75 | 76 | //There is a 23 byte filler 77 | dataOutPacket.write(new byte[23]); 78 | 79 | //Add the username - ensure a null terminator is added on the end 80 | byte[] user = (this.mysqlConn.getUsername() + "\0").getBytes(charset); 81 | dataOutPacket.write(user); 82 | 83 | if (!this.mysqlConn.isConnectedVersionLessThan(5,5,0)) 84 | { 85 | dataOutPacket.writeByte((byte) this.mysqlConn.getAuthPluginDataLength() - 1); 86 | } 87 | //Has the password, only using mysql_native_password currently 88 | if (this.mysqlConn.isConnectedVersionLessThan(5,5,0) || this.mysqlConn.getAuthPluginName().equals("mysql_native_password")) 89 | { 90 | if ((this.mysqlConn.getServerCapabilities() & CLIENT_SECURE_CONNECTION) == CLIENT_SECURE_CONNECTION) 91 | { 92 | try 93 | { 94 | 95 | String authSalt1 = this.mysqlConn.getAuthSalt(); 96 | String authSalt2 = this.mysqlConn.getAuthSalt2(); 97 | String seed = this.mysqlConn.getAuthSalt() + this.mysqlConn.getAuthSalt2(); 98 | //String updateSeed = this.seed.substring(0, 20); 99 | byte[] bytes = seed.getBytes(charset); 100 | String temp = readString(bytes); 101 | byte[] password = scramblePassword(this.mysqlConn.getPassword(), temp); 102 | 103 | //If before MySQL 5.5.0 we need to add the password length - this will always be 20 104 | if (this.mysqlConn.isConnectedVersionLessThan(5,5,0)) { 105 | dataOutPacket.writeByte(0x14); 106 | } 107 | dataOutPacket.write(password); 108 | 109 | //The documentation states the password should be NULL terminated, however, if its null 110 | //terminated and we're connecting to a default database we don't authenticate. We don't get 111 | //any error either we receive an EOF packet but running show processlist from the server itself 112 | //shows unauthenticated user. Therefore, if we're not connecting to a default database 113 | //add the null terminator to the password, if we are using a default database don't add the null terminator 114 | //but this also only appears to be required on certain MySQL Versions. 115 | if ((this.mysqlConn.getDatabase() == null) || this.mysqlConn.getDatabase().length() == 0) 116 | { 117 | if (!mysqlConn.getServerVersion().startsWith("5.7")) 118 | { 119 | dataOutPacket.writeByte(0); //Write the null terminator 120 | } 121 | } 122 | 123 | } 124 | catch (NoSuchAlgorithmException ex) 125 | { 126 | Log.e("AuthResponse", ex.toString()); 127 | } 128 | } 129 | } 130 | else 131 | { 132 | Log.e("AuthResponse", "Only mysql_native_password is currently supported"); 133 | throw new UnsupportedOperationException("mysql_native_password is currently only supported. Please use this authentication method"); 134 | } 135 | 136 | //Add the database if we have a database available 137 | if ((this.mysqlConn.getClientCapabilities() & CLIENT_CONNECT_WITH_DB) == CLIENT_CONNECT_WITH_DB) 138 | { 139 | byte[] database = (this.mysqlConn.getDatabase() + "\0").getBytes(charset); 140 | dataOutPacket.write(database); 141 | } 142 | 143 | if ((this.mysqlConn.getServerCapabilities() & CLIENT_PLUGIN_AUTH) == CLIENT_PLUGIN_AUTH) 144 | { 145 | if (!this.mysqlConn.isConnectedVersionLessThan(5,5,0)) 146 | { 147 | String temp = this.mysqlConn.getAuthPluginName() + "\0"; 148 | dataOutPacket.write(temp.getBytes(charset)); 149 | } 150 | } 151 | 152 | //Add the client attributes to the response 153 | if (this.mysqlConn.doesVersionMeetMinimumRequired(5,5,0) 154 | && (this.mysqlConn.getServerCapabilities() & CLIENT_CONNECT_ATTRS) == CLIENT_CONNECT_ATTRS) 155 | { 156 | int totalAttrLength = 0; 157 | 158 | byte[] clientVersionName = "_client_version".getBytes(charset); 159 | byte[] clientVersionValue = "1.0.0.0".getBytes(charset); 160 | totalAttrLength += clientVersionName.length + clientVersionValue.length; 161 | 162 | byte[] vendorName = "_runtime_vendor".getBytes(charset); 163 | byte[] vendorValue = "Boardies IT Solutions".getBytes(charset); 164 | totalAttrLength += vendorName.length + vendorValue.length; 165 | 166 | byte[] clientName = "_client_name".getBytes(charset); 167 | byte[] clientValue = "Android MySQL Connector".getBytes(charset); 168 | totalAttrLength += clientName.length + clientValue.length; 169 | 170 | //Add 6 (number of attributes * 2 (2 as each key/value contains a length a byte 171 | dataOutPacket.writeByte(totalAttrLength + 6); 172 | 173 | dataOutPacket.writeByte(clientVersionName.length); 174 | dataOutPacket.write(clientVersionName); 175 | dataOutPacket.writeByte(clientVersionValue.length); 176 | dataOutPacket.write(clientVersionValue); 177 | 178 | dataOutPacket.writeByte(vendorName.length); 179 | dataOutPacket.write(vendorName); 180 | dataOutPacket.writeByte(vendorValue.length); 181 | dataOutPacket.write(vendorValue); 182 | 183 | dataOutPacket.writeByte(clientName.length); 184 | dataOutPacket.write(clientName); 185 | dataOutPacket.writeByte(clientValue.length); 186 | dataOutPacket.write(clientValue); 187 | 188 | } 189 | } 190 | catch (IOException ex) 191 | { 192 | Log.e("AuthResponse", ex.toString()); 193 | ex.printStackTrace(); 194 | throw ex; 195 | } 196 | catch (Exception ex) 197 | { 198 | Log.e("AuthResponse", ex.toString()); 199 | ex.printStackTrace(); 200 | } 201 | } 202 | private byte[] scramblePassword(String password, String seed) throws NoSuchAlgorithmException, UnsupportedEncodingException 203 | { 204 | MessageDigest md = MessageDigest.getInstance("SHA-1"); 205 | 206 | byte[] passwordHashStage1 = md.digest(password.getBytes("UTF-8")); 207 | md.reset(); 208 | 209 | byte[] passwordHashStage2 = md.digest(passwordHashStage1); 210 | md.reset(); 211 | 212 | byte[] seedAsBytes = seed.getBytes(charset); 213 | md.update(seedAsBytes); 214 | md.update(passwordHashStage2); 215 | 216 | byte[] toBeXored = md.digest(); 217 | int numToXor = toBeXored.length; 218 | 219 | for (int i = 0; i < numToXor; i++) { 220 | toBeXored[i] = (byte) (toBeXored[i] ^ passwordHashStage1[i]); 221 | } 222 | return toBeXored; 223 | } 224 | } 225 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/MySQLIO.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector; 2 | 3 | import android.os.Build; 4 | import androidx.annotation.RequiresApi; 5 | import android.util.Log; 6 | 7 | import java.io.BufferedInputStream; 8 | import java.io.BufferedOutputStream; 9 | import java.io.ByteArrayOutputStream; 10 | import java.io.IOException; 11 | import java.net.Socket; 12 | import java.util.ArrayList; 13 | import java.util.Arrays; 14 | import java.util.List; 15 | 16 | import javax.net.ssl.SSLSocket; 17 | 18 | import static com.BoardiesITSolutions.AndroidMySQLConnector.Connection.CLIENT_SSL; 19 | 20 | public class MySQLIO 21 | { 22 | private Connection connection; 23 | private BufferedInputStream sockStream; 24 | private byte[] fullData; 25 | private int currentBytesRead = 0; 26 | 27 | public static boolean breakSocketGetData = true; 28 | 29 | public MySQLIO(Connection connection, Socket mysqlSock) throws IOException 30 | { 31 | this.connection = connection; 32 | this.sockStream = new BufferedInputStream(mysqlSock.getInputStream()); 33 | } 34 | 35 | public void closeMySQLIO() 36 | { 37 | if (this.sockStream != null) 38 | { 39 | try 40 | { 41 | this.sockStream.close(); 42 | } 43 | catch (IOException e) 44 | { 45 | e.printStackTrace(); 46 | } 47 | } 48 | } 49 | 50 | public void updateSocketStream(SSLSocket sslSocket) throws IOException 51 | { 52 | this.sockStream = new BufferedInputStream(sslSocket.getInputStream(), 16384); 53 | } 54 | 55 | public int getCurrentBytesRead() 56 | { 57 | return this.currentBytesRead; 58 | } 59 | 60 | public int getSocketDataLength() 61 | { 62 | return this.fullData.length; 63 | } 64 | 65 | public byte[] getSocketByteArray() 66 | { 67 | return this.fullData; 68 | } 69 | 70 | private void getSocketData() throws IOException 71 | { 72 | 73 | Log.d("MySQLIO", "Reading Socket Data"); 74 | byte[] data = new byte[1024]; 75 | ByteArrayOutputStream baos = new ByteArrayOutputStream(); 76 | int payloadLength = -1; 77 | Log.d("MySQLIO", "Created baos"); 78 | int totalBytesRead = 0; 79 | try { 80 | int bytesRead = -1; 81 | while ((bytesRead = sockStream.read(data)) != -1) { 82 | totalBytesRead += bytesRead; 83 | Log.d("MySQLIO", "Read from socket. Bytes Read: " + bytesRead); 84 | baos.write(data, 0, bytesRead); 85 | Log.d("MySQLIO", "BAOS written: Bytes Written: " + bytesRead); 86 | if (bytesRead < 1024) { 87 | Log.d("MySQLIO", "Receiving less data than buffer filled. Checking for EOF"); 88 | byte[] temp = baos.toByteArray(); 89 | if ((int)temp[temp.length-5] == 0xfe || (int)temp[temp.length-1] == 0) 90 | { 91 | Log.d("MySQLIO", "EOF detected, breaking loop and processing response"); 92 | break; 93 | } 94 | } 95 | } 96 | Log.d("MySQLIO", "Loop completed"); 97 | if (baos.size() == 0) { 98 | Log.d("MySQLIO", "No data in response"); 99 | } 100 | fullData = baos.toByteArray(); 101 | Log.d("MySQLIO", "Full data written to: now size: " + fullData.length); 102 | currentBytesRead = 0; 103 | } 104 | catch (Exception ex) 105 | { 106 | fullData = baos.toByteArray(); 107 | currentBytesRead = 0; 108 | } 109 | } 110 | 111 | private int extractPayloadLengthFromBuffer(byte[] buffer) 112 | { 113 | byte[] value = Arrays.copyOfRange(buffer, 0, 3); 114 | return fromByteArray(value)+4; 115 | } 116 | 117 | /** 118 | * Reset the MySQL IO and retrieve the next datastream from the socket 119 | */ 120 | public void reset(boolean dontExpectResponse) throws IOException 121 | { 122 | Log.d("MySQLIO", "Resetting Socket: Don't Expect Response: " + ((dontExpectResponse) ? "true" : "false")); 123 | this.fullData = null; 124 | this.currentBytesRead = 0; 125 | if (!dontExpectResponse) 126 | { 127 | Log.d("MySQLIO", "socket data has reset. Getting socket data"); 128 | this.getSocketData(); 129 | } 130 | } 131 | 132 | public void reset() throws IOException 133 | { 134 | reset(false); 135 | } 136 | 137 | public byte readCurrentByteWithoutShift() 138 | { 139 | return this.fullData[currentBytesRead]; 140 | } 141 | 142 | public byte[] extractData(int length) 143 | { 144 | byte[] temp = Arrays.copyOfRange(fullData, currentBytesRead, currentBytesRead+length); 145 | this.shiftCurrentBytePosition(length); 146 | return temp; 147 | } 148 | 149 | public String extractDataAsString(boolean returnAsHex, int length) 150 | { 151 | byte[] temp = Arrays.copyOfRange(fullData, currentBytesRead, currentBytesRead+length); 152 | if (returnAsHex) 153 | { 154 | 155 | StringBuilder stringBuilder = new StringBuilder(); 156 | 157 | for (int i = 0; i < temp.length; i++) 158 | { 159 | stringBuilder.append(String.format("%02X", temp[i])); 160 | } 161 | this.shiftCurrentBytePosition(length); 162 | return stringBuilder.toString(); 163 | } 164 | else 165 | { 166 | this.shiftCurrentBytePosition(length); 167 | return new String(temp, connection.getCharset()); 168 | } 169 | } 170 | 171 | /** 172 | * If returned as hex is false, then it returns the ASCII equivalent 173 | * @param returnAsHex 174 | * @return 175 | */ 176 | @RequiresApi(api = Build.VERSION_CODES.KITKAT) 177 | public String extractDataAsString(boolean returnAsHex) 178 | { 179 | if (returnAsHex) 180 | { 181 | StringBuilder stringBuilder = new StringBuilder(); 182 | for (; currentBytesRead < fullData.length-1; currentBytesRead++) { 183 | if (this.fullData[currentBytesRead] == 0) { 184 | currentBytesRead++; 185 | break; 186 | } 187 | stringBuilder.append(String.format("%02X", fullData[currentBytesRead])); 188 | } 189 | return stringBuilder.toString(); 190 | } 191 | else 192 | { 193 | List bytes = new ArrayList<>(); 194 | //Loop over the full data to find the null terminate creating a list of bytes 195 | for (; currentBytesRead < fullData.length; currentBytesRead++) 196 | { 197 | if (this.fullData[currentBytesRead] == 0) 198 | { 199 | currentBytesRead++; 200 | break; 201 | } 202 | bytes.add(fullData[currentBytesRead]); 203 | } 204 | //Convert the byte list to a byte array so we can convert it to a string 205 | byte[] byteArray = new byte[bytes.size()]; 206 | for (int i = 0; i < bytes.size(); i++) 207 | { 208 | 209 | byteArray[i] = bytes.get(i); 210 | } 211 | return new String(byteArray, connection.getCharset()); 212 | } 213 | } 214 | 215 | public Object extractDataAsString(int length) throws IndexOutOfBoundsException 216 | { 217 | if (length == 1) 218 | { 219 | byte value = this.fullData[currentBytesRead]; 220 | this.shiftCurrentBytePosition(length); 221 | return value; 222 | } 223 | else 224 | { 225 | byte[] value = Arrays.copyOfRange(fullData, currentBytesRead, currentBytesRead+length); 226 | /*for (int i = 0; i < value.length; i++) 227 | { 228 | if (value[i] == -1) 229 | { 230 | value[i] = (byte)0xff; 231 | } 232 | }*/ 233 | this.shiftCurrentBytePosition(length); 234 | return value; 235 | } 236 | } 237 | 238 | public void shiftCurrentBytePosition(int length) 239 | { 240 | if ((currentBytesRead + length) > fullData.length) 241 | { 242 | throw new IndexOutOfBoundsException(); 243 | } 244 | currentBytesRead += length; 245 | } 246 | 247 | private String unHex(String arg) { 248 | 249 | String str = ""; 250 | for(int i=0;i 3 | 4 | 5 | 6 | **Note:** When chosing a release you are better off using the tags appended with MySQL8 as these are more likely to work with other versions of MySQL (all the way down to 5.1) There has been numerous fixes in this branch and there is some preliminary support for MySQL8 servers as long as the server is using mysql_native_password instead of Sha2Caching. 7 | 8 | # Introduction 9 | This is (as far as we are aware) the first and only native MySQL connector for Android. 10 | It connects directly to your database instead of relying on some sort of web service to 11 | act as a middleware between Android and the MySQL Server. 12 | 13 | Using a webservice is still the recommended way of connecting to your database as it 14 | ensures only clients that are supposed to are connecting to your database and avoids the requirement 15 | of needing to expose your MySQL server to the Internet. 16 | 17 | However, if you want, or have a need to connect directly to your database, then this is the library 18 | you need. This is a fairly basic MySQL connector compared to the official connectors that are available 19 | on other platforms. Therefore, below is a list of the known limitations of the library: 20 |
    21 |
  • Doesn't support compression, if the MySQL server reports that it supports compression, the connector will turn it off
  • 22 |
  • Doesn't support prepared statements
  • 23 |
  • Only supports UTF8 or Latin 1 encodings
  • 24 |
  • If authentication is required, then only mysql_native_password is supported
  • 25 |
26 | 27 | The library has been tested on the following MySQL Servers 28 |
    29 |
  • 5.1.72
  • 30 |
  • 5.5.59
  • 31 |
  • 5.6.39
  • 32 |
  • 5.7.22
  • 33 |
34 | 35 | We've so far only tested on the the highest minor version of each MySQL major version. You'll notice 36 | that this doesn't include MySQL 8.0. This is due to MySQL 8 changing the default authentication mechanism, 37 | this is something we plan on adding in the future. We haven't yet tested with MariaDB however, the equivalent 38 | MeriaDB and MySQL version should be compatible and therefore should work in the same way. 39 | 40 | # Adding Dependency 41 | Adding the library to your project couldn't be simpler. Add the following to your apps build.gradle 42 | ``` 43 | repositories { 44 | maven { url 'https://jitpack.io' } 45 | } 46 | 47 | dependencies { 48 | implementation 'com.github.BoardiesITSolutions:Android-MySQL-Connector:TAG' 49 | } 50 | ``` 51 | 52 | The `TAG` above will be the tagged version number of the library. 53 | 54 | # Using the Library 55 | Due to the way Android works, using the library is a little different compared to using the official 56 | connector on other platforms. This is due to to Android completely restricting any network activity on the main 57 | thread. Obviously you shouldn't do any network activity on the main thread on any platform, but most platforms don't stop 58 | you if you want to, however, Android will throw an exception if any network activity is done on the main thread. Therefore 59 | for each action you wish to take, such as connecting, switching databases, performing a query, the relevant method 60 | or constructor will take an interface which will get called once the action has completed which you can then handle. 61 | 62 | ## Connecting to a MySQL Server 63 | To connect to a MySQL server you first need to create the MySQL Connection object. You can do this as follows: 64 | ``` 65 | Connection mysqlConnection = new Connection("", "", "", , "", new IConnectionInterface() 66 | ``` 67 | 68 | In the above example, is an optional parameter. By setting this, when the connection is established, the database name will be the default 69 | database used. The IConnectionInterface parameter handles connection specific events, such as successfully connected or exception handlers. 70 | 71 | For example, when creating a new IConnectionInterface, you will need to provide the following methods: 72 | **actionCompleted** 73 | This means that the connection was successfully established and authenticated and the connection is ready for use 74 | 75 | **handleInvalidSQLException** 76 | You shouldn't really ever get this, but you might if you are connecting to a MySQL server that either is configured slightly differently 77 | as to what has bee tested or isn't compatible with the connector, and has caused the connector to receive a network packet from the MySQL 78 | server that the connector wasn't expecting. 79 | 80 | **handleMySQLException** 81 | This will happen if a generic MySQL exception occurs within the connector 82 | 83 | **handleIOException** 84 | This is used if an internal socket error occurs within the connector, for example, if server aborted the connection but the library 85 | didn't realise and tried to perform an operation on the MySQL socket which is now closed 86 | 87 | **handleMySQLConnException** 88 | This will be an exception related to a connection error, such as authentication failure 89 | 90 | **handleException** 91 | This is a generic exception handler if any of the above doesn't match the exception 92 | 93 | That's it, you have successfully connected, now we can execute some queries. Remember though, keep 94 | your connection object available in your class as this will be required in order to run queries on the DB. 95 | 96 | ## Switching Database 97 | You can change the default database to use in your established MySQL Connection. To do this, do not execute 98 | a standard USE DATABASE query as it won't work. 99 | 100 | You need to use your connection object and call the method `switchDatabase(db, new IConnectionInterface())`. 101 | `db` being a String of your database name, and again, pass in the IConnectionInterface. If it switched successfully 102 | you will receive a call back to the actionCompleted method. 103 | 104 | ## Execute Statement (Such as INSERT or UPDATE where no resultset is returned) 105 | First of all you need to create a statement object from your connection object. 106 | You can do this using the following code snippet 107 | ``` 108 | Statement statement = connection.createStatement(); 109 | ``` 110 | 111 | Then in order to execute your statement you then do the following 112 | ``` 113 | statement.execute(query, new IConnectionInterface()); 114 | ``` 115 | 116 | `query` is your statement that you want to execute such as an INSERT or UPDATE statement. Again the second 117 | parameter is the IConnectionInterface and if the statement execute successfully, then actionCompleted will be called. 118 | 119 | ## Execute Query (Such as SELECT) 120 | The execute query function allows you to execute a query such as SELECT or DESCRIBE, basically any query 121 | which return a result set. Same with executing a statement, you need to create the statement object from your 122 | connection object. This can be done using the following code snippet: 123 | ``` 124 | Statement statement = connection.createStatement(); 125 | ``` 126 | 127 | Then you need to call the executeQuery withn your statement object, but this time passing in a new 128 | IResultInterface as shown below: 129 | ``` 130 | statement.executeQuery(query, new IResultInterface()); 131 | ``` 132 | 133 | The IResultInterface is pretty similar to the IConnectionInterface, you'll have the same exception handlers, 134 | the main difference that if the query executes successfully, you'll receive executionComplete which will have 135 | a parameter to the result set. 136 | 137 | # Processing a result set 138 | When you execute a query such as SELECT or DESCRIBE, you will get a call back which will provide a result set object. 139 | This result set object contains all the information about what was returned such as the columns and the rows. 140 | 141 | ## Get total number of rows returned 142 | To get the total number of rows you can call `resultset.getNumRows();`. 143 | 144 | ## Get column definitions 145 | The column definitions are stored in a `List`. You can get this using the following code snipper: 146 | 147 | ``` 148 | List columns = result.getFields(); 149 | ``` 150 | 151 | You can then loop over the list to get ColumnDefinition for each column returned in the result set. Within the column 152 | definition class, you can use the following methods: 153 | **getDatabase** 154 | The database name where the column was retrieved from 155 | 156 | **getTable** 157 | The table name where the column was retrieved from 158 | 159 | **getColumnName** 160 | The name of the column 161 | 162 | **getColumnType** 163 | Returns an enum of type `ColumnType` 164 | 165 | **isPrimaryKey** 166 | Returns a boolean as to whether or not the column is a primary key 167 | 168 | ## Iterating through each row 169 | You can iterate through each row, you first need to create an empty `MySQLRow` variable that can be used 170 | to be set within a while loop to get each row. You can call `getNextRow()` on the result set to get a MySQLRow. 171 | Once there are no rows left, null is returned. The following code snippet provides an example: 172 | 173 | ``` 174 | MySQLRow row; 175 | while ((row = result.getNextRow()) != null) 176 | { 177 | //Read the row contents here 178 | } 179 | ``` 180 | 181 | Once you have your MySQLRow you can then call the following methods to return the value of the field. 182 | Each of the following methods, take a String parameter which is the column name that should be retrieved. 183 | * getString(String column) 184 | * getInt(String column) 185 | * getFloat(String column) 186 | * getDouble(String column) 187 | * getBlob(String column) (Returns a byte[] array) 188 | 189 | # Escaping Strings 190 | When sending dynamic paraters in your MySQL query, the string should be escaped to avoid SQL injection attacks. 191 | This can be done by using the following code snippet: 192 | 193 | ``` 194 | String var = connection.escape_string(variable); 195 | ``` 196 | 197 | # Best Practices 198 | Currently the connection object can't be passed between Android activities, so if you do need to create a new Activity 199 | and perform database action, you should close the DB connection in your current activity, pass the connection details 200 | to your new activity, and then create a new connection object in your new activity. 201 | 202 | Also, to avoid leaving the DB connection open for no reason, in your activities onPause and onDestroy methods 203 | you should close the DB connection and then create a new instance to restablish the connection in the onCreate and/or onResume 204 | 205 | You can close the DB by calling `connection.close()`. 206 | 207 | # Examples 208 | Below you will find some examples on some of the common actions you might do with the MySQL Connector. 209 | 210 | Each of the below examples creates a Connection object called mysqlConnection. This can be done using the following: 211 | ``` 212 | Connection mysqlConnection = new Connection("localhost", "root", 213 | "letmein", 3306, "my_database", new IConnectionInterface() 214 | { 215 | @Override 216 | public void actionCompleted() 217 | { 218 | //You are now connected to the database 219 | } 220 | 221 | @Override 222 | public void handleInvalidSQLPacketException(InvalidSQLPacketException e) 223 | { 224 | //Handle the error 225 | } 226 | 227 | @Override 228 | public void handleMySQLException(MySQLException e) 229 | { 230 | //Handle the error 231 | } 232 | 233 | @Override 234 | public void handleIOException(IOException e) 235 | { 236 | //Handle the error 237 | } 238 | 239 | @Override 240 | public void handleMySQLConnException(MySQLConnException e) 241 | { 242 | //Handle the error 243 | } 244 | 245 | @Override 246 | public void handleException(Exception e) 247 | { 248 | //Handle the error 249 | } 250 | }); 251 | //The below line isn't required, however, the MySQL action, whether connecting or executing a statement 252 | //(basically anything that uses the this connection object) does its action in a thread, so the call 253 | //back you receive will still be in its thread. If you are performing a GUI operation in your callback you need to 254 | //switch the main thread. You can either do this yourself when required, or pass true as the first parameter 255 | //so that when you receive the call back it is already switched to the main thread 256 | mysqlConnection.returnCallbackToMainThread(true, MainActivity.this); 257 | ``` 258 | 259 | ## Switching Default Database 260 | When you need to change the default database, (if a default database is set you do not need to prepend the database with the table name). You cannot use the the statement `USE new_database`, you have to use the method `switchDatabase` within the connection object as follows: 261 | 262 | ``` 263 | mysqlConnection.switchDatabase("your_db_name", new IConnectionInterface() { 264 | @Override 265 | public void actionCompleted() { 266 | //Database switched successfully 267 | } 268 | 269 | @Override 270 | public void handleInvalidSQLPacketException(InvalidSQLPacketException e) { 271 | errorHandler.handleInvalidSQLPacketException(e); 272 | } 273 | 274 | @Override 275 | public void handleMySQLException(MySQLException e) { 276 | errorHandler.handleMySQLException(e); 277 | } 278 | 279 | @Override 280 | public void handleIOException(IOException e) { 281 | errorHandler.handleIOException(e); 282 | } 283 | 284 | @Override 285 | public void handleMySQLConnException(MySQLConnException e) { 286 | errorHandler.handleMySQLConnException(e); 287 | } 288 | 289 | @Override 290 | public void handleException(Exception e) { 291 | errorHandler.handleGeneralException(e); 292 | } 293 | }); 294 | ``` 295 | 296 | ## Executing statement (SQL statements that do not return a result set, e.g. INSERT, UPDATE, DELETE, TRUNCATE, CREATE, DROP ALTER) 297 | ``` 298 | Statement statement = mysqlConnection.createStatement(); 299 | statement.execute("CREATE DATABASE my_new_db", new IConnectionInterface() 300 | { 301 | @Override 302 | public void actionCompleted() 303 | { 304 | //action completed successfully 305 | } 306 | 307 | @Override 308 | public void handleInvalidSQLPacketException(InvalidSQLPacketException e) 309 | { 310 | errorHandler.handleInvalidSQLPacketException(e); 311 | } 312 | 313 | @Override 314 | public void handleMySQLException(MySQLException e) 315 | { 316 | errorHandler.handleMySQLException(e); 317 | } 318 | 319 | @Override 320 | public void handleIOException(IOException e) 321 | { 322 | errorHandler.handleIOException(e); 323 | } 324 | 325 | @Override 326 | public void handleMySQLConnException(MySQLConnException e) 327 | { 328 | errorHandler.handleMySQLConnException(e); 329 | } 330 | 331 | @Override 332 | public void handleException(Exception e) 333 | { 334 | errorHandler.handleGeneralException(e); 335 | } 336 | }); 337 | ``` 338 | ## Executing query (Statement which returns a MySQL Result Set, e.g. SELECT, SHOW, DESCRIBE) 339 | Performing a query to return a result set is pretty much the same as above, the only difference is that instead of passing an `IConnectionInterface`, you instead pass an `IResultInterface()` and the `executionComplete` method provides you with a `ResultSet` object 340 | 341 | ``` 342 | Statement statement = mysqlConnection.createStatement(); 343 | statement.executeQuery("SELECT * FROM my_table", new IResultInterface() 344 | { 345 | @Override 346 | public void executionComplete(ResultSet resultSet) 347 | { 348 | addHistoryRecord(new QueryHistory(QueryHistory.Status.OK, txtQuery.getText().toString(), "Fetched " + resultSet.getNumRows() + " Row(s)", statement.getQueryTimeInMilliseconds())); 349 | processResultset(resultSet); 350 | } 351 | 352 | @Override 353 | public void handleInvalidSQLPacketException(InvalidSQLPacketException e) 354 | { 355 | Log.e("DBViewFragment", e.toString()); 356 | addHistoryRecord(new QueryHistory(QueryHistory.Status.ERROR, txtQuery.getText().toString(), e.toString(), statement.getQueryTimeInMilliseconds())); 357 | errorHandler.handleInvalidSQLPacketException(e); 358 | 359 | } 360 | 361 | @Override 362 | public void handleMySQLException(MySQLException e) 363 | { 364 | Log.e("DBViewFragement", e.toString()); 365 | addHistoryRecord(new QueryHistory(QueryHistory.Status.ERROR, txtQuery.getText().toString(), e.toString(), statement.getQueryTimeInMilliseconds())); 366 | errorHandler.handleMySQLException(e); 367 | } 368 | 369 | @Override 370 | public void handleIOException(IOException e) 371 | { 372 | Log.e("DBViewFragement",e.toString()); 373 | addHistoryRecord(new QueryHistory(QueryHistory.Status.ERROR, txtQuery.getText().toString(), e.toString(), statement.getQueryTimeInMilliseconds())); 374 | errorHandler.handleIOException(e); 375 | } 376 | 377 | @Override 378 | public void handleMySQLConnException(MySQLConnException e) 379 | { 380 | Log.e("DBViewFragement", e.toString()); 381 | addHistoryRecord(new QueryHistory(QueryHistory.Status.ERROR, txtQuery.getText().toString(), e.toString(), statement.getQueryTimeInMilliseconds())); 382 | errorHandler.handleMySQLConnException(e); 383 | } 384 | 385 | @Override 386 | public void handleException(Exception e) 387 | { 388 | Log.e("DBViewFragement", e.toString()); 389 | addHistoryRecord(new QueryHistory(QueryHistory.Status.ERROR, txtQuery.getText().toString(), e.toString(), statement.getQueryTimeInMilliseconds())); 390 | errorHandler.handleGeneralException(e); 391 | } 392 | }); 393 | ``` 394 | 395 | ## Troubleshooting 396 | ### Cannot Resolve Symbol Error 397 | If you have added the library and the gradle sync works successfully but you are getting errors like `cannot resolve symbol` make sure that the imports at the top of the class file have been referenced. 398 | 399 | This can be done in one of two ways: 400 | 1. Put the cursor somewhere in the area of code which is showing red and hit Ctrl + Alt + Enter. Android Studio will provide tips on how to fix - one of them being to import the class. Select import and the error should go away. 401 | 2. You can add the following two lines: 402 | ``` 403 | import com.BoardiesITSolutions.AndroidMySQLConnector.*; 404 | import com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions.*; 405 | ``` 406 | which will import all available classes from the library. However, this tends to be bad practice as only what's needed from the library by your class should be imported. You can get round this by adding the two lines above, then when your class is finished, you can then do Ctrl + Alt + O which will organise your imports into what ones are required instead of a wildcard import. 407 | 408 | ### javax.net.ssl.SSLHandshakeException: Handshake failed 409 | If you see this error then connect to your DB via command line and run the following query: 410 | ``` 411 | SHOW SESSION STATUS LIKE 'Ssl_version'; 412 | ``` 413 | 414 | If it shows TLSv1 then this won't be supported by the Android MySQL Connector library. TLS 1.0 is a deprecated version of TLS and Java and/or Android no longer supports this TLS version. 415 | 416 | If MySQL supports it, you can add `tls_version=TLSv1.1` or `tls_version=TLSv1.2` (Check your mysql version documentation to determine the supported TLS) to your `/etc/main.cf` file and restart MySQL. This needs to be added under the `mysqld` section of the config file. 417 | 418 | If you are using Amazon RDS then you will need to use at least MySQL 5.7.16. Previous version of Amazon RDS for MySQL only support TLS 1.0 so the library won't be able to connect. 419 | -------------------------------------------------------------------------------- /AndroidMySQLConnector/src/main/java/com/BoardiesITSolutions/AndroidMySQLConnector/Connection.java: -------------------------------------------------------------------------------- 1 | package com.BoardiesITSolutions.AndroidMySQLConnector; 2 | 3 | import android.annotation.TargetApi; 4 | import android.os.Build; 5 | import androidx.annotation.RequiresApi; 6 | import androidx.appcompat.app.AppCompatActivity; 7 | import android.util.Log; 8 | 9 | 10 | import com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions.InvalidSQLPacketException; 11 | import com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions.MySQLConnException; 12 | import com.BoardiesITSolutions.AndroidMySQLConnector.Exceptions.UnsupportedMySQLServerException; 13 | import com.BoardiesITSolutions.AndroidMySQLConnector.PacketManager.AuthResponse; 14 | import com.BoardiesITSolutions.AndroidMySQLConnector.PacketManager.COM_Query; 15 | import com.BoardiesITSolutions.AndroidMySQLConnector.PacketManager.MySQLErrorPacket; 16 | import com.BoardiesITSolutions.AndroidMySQLConnector.PacketManager.MySQLOKPacket; 17 | import com.BoardiesITSolutions.AndroidMySQLConnector.PacketManager.SSLRequest; 18 | 19 | import java.io.IOException; 20 | import java.net.Socket; 21 | import java.nio.ByteBuffer; 22 | import java.nio.charset.Charset; 23 | import java.security.KeyManagementException; 24 | import java.security.NoSuchAlgorithmException; 25 | import java.util.ArrayList; 26 | import java.util.List; 27 | 28 | import javax.net.ServerSocketFactory; 29 | import javax.net.ssl.SSLContext; 30 | import javax.net.ssl.SSLHandshakeException; 31 | import javax.net.ssl.SSLSocket; 32 | import javax.net.ssl.SSLSocketFactory; 33 | import javax.net.ssl.TrustManager; 34 | 35 | public class Connection 36 | { 37 | private String hostname; 38 | private String username; 39 | private String password; 40 | private String database; 41 | private int port = 3306; 42 | 43 | //Connection Details 44 | private int majorVersion; 45 | private int minorVersion; 46 | private int subMinorVersion; 47 | private int protocolVersion; 48 | private String serverVersion; 49 | private int connectionID; 50 | private String authSalt; 51 | private String authSalt2; 52 | private String seed; 53 | protected int baseServerCapabilities; 54 | protected int serverCapabilities; 55 | private int clientCapabilities; 56 | private int serverLanguage; 57 | private int serverStatus; 58 | protected int extendedServerCapabilities; 59 | private int authPluginDataLength = 0; 60 | private String authPluginName; 61 | private int packetSequenceNumber = 1; 62 | private int lastInsertID = 0; 63 | private Charset charset; 64 | 65 | //Server/Client Connection Options 66 | public static final int CLIENT_PLUGIN_AUTH = 0x00080000; 67 | public static final int CLIENT_MULTI_SET = 0x00010000; 68 | public static final int CLIENT_SSL = 0x00000800; 69 | public static final int CLIENT_LONG_PASSWORD = 0x00000001; 70 | public static final int CLIENT_MULTI_STATEMENTS = 0x00010000; 71 | public static final int CLIENT_SECURE_CONNECTION = 0x00008000; 72 | public static final int CLIENT_CONNECT_ATTRS = 0x00100000; 73 | public static final int CLIENT_CONNECT_WITH_DB = 0x00000008; 74 | public static final int CLIENT_PROTOCOL_41 = 0x00000200; 75 | public static final int CLIENT_COMPRESS = 0x00000020; 76 | public static final int CLIENT_NO_SCHEMA = 0x00000010; 77 | public static final int CLIENT_IGNORE_SIGPIPE = 0x00001000; 78 | public static final int CLIENT_INTERACTIVE = 0x00000400; 79 | public static final int CLIENT_ODBC = 0x00000040; 80 | public static final int CLIENT_IGNORE_SPACE = 0x00000100; 81 | public static final int CLIENT_PS_MULTI_RESULTS = 0x00040000; 82 | public static final int CLIENT_CAN_HANDLE_EXPIRED_PASSWORDS = 0x00400000; 83 | public static final int CLIENT_SESSION_TRACK = 0x00800000; 84 | 85 | //Character Sets 86 | private final int LATIN1_SWEDISH_CI = 0x08; 87 | private final int UTF8_GENERAL_CI = 0x21; 88 | private final int UTF8_UNICODE_CI =0xc0; 89 | private final int BINARY = 0x3f; 90 | 91 | private AppCompatActivity activity; 92 | private Socket mysqlSocket; 93 | private SSLSocket mysqlSSLSocket; 94 | private MySQLIO mysqlIO = null; 95 | private IConnectionInterface iConnectionInterface; 96 | private boolean returnCallbackToMainThread = false; 97 | 98 | @TargetApi(Build.VERSION_CODES.KITKAT) 99 | @RequiresApi(api = Build.VERSION_CODES.KITKAT) 100 | public Connection(String hostname, String username, String password, int port, String database, IConnectionInterface iConnectionInterface) 101 | { 102 | //this.resetPacketSequenceNumber(); 103 | this.iConnectionInterface = iConnectionInterface; 104 | this.hostname = hostname; 105 | this.username = username; 106 | this.password = password; 107 | this.database = database; 108 | this.port = port; 109 | 110 | this.connect(); 111 | } 112 | 113 | public Connection(String hostname, String username, String password, int port, IConnectionInterface iConnectionInterface) 114 | { 115 | //this.resetPacketSequenceNumber(); 116 | this.iConnectionInterface = iConnectionInterface; 117 | this.hostname = hostname; 118 | this.username = username; 119 | this.password = password; 120 | this.port = port; 121 | 122 | this.connect(); 123 | } 124 | 125 | public Charset getCharset() { 126 | //If the charset hasn't been set yet - probably because we haven't yet read the server 127 | //language from the welcome packet, default it to uft-8 128 | if (charset != null) 129 | { 130 | return charset; 131 | } 132 | else 133 | { 134 | return Charset.forName("UTF-8"); 135 | } 136 | } 137 | 138 | public int getServerLanguage() 139 | { 140 | return serverLanguage; 141 | } 142 | 143 | public void returnCallbackToMainThread(boolean returnCallbackToMainThread, AppCompatActivity activity) 144 | { 145 | this.activity = activity; 146 | this.returnCallbackToMainThread = returnCallbackToMainThread; 147 | } 148 | 149 | public boolean getReturnCallbackToMainThread() 150 | { 151 | return this.returnCallbackToMainThread; 152 | } 153 | 154 | public AppCompatActivity getActivity() 155 | { 156 | return activity; 157 | } 158 | 159 | public int getMajorVersion() 160 | { 161 | return this.majorVersion; 162 | } 163 | public int getMinorVersion() 164 | { 165 | return this.minorVersion; 166 | } 167 | public int getSubMinorVersion() 168 | { 169 | return this.subMinorVersion; 170 | } 171 | 172 | public void setLastInsertID(int lastInsertID) 173 | { 174 | this.lastInsertID = lastInsertID; 175 | } 176 | 177 | 178 | /** 179 | * Get the last insert id from the last command that was executed 180 | */ 181 | public int getLastInsertID() 182 | { 183 | return this.lastInsertID; 184 | } 185 | 186 | public MySQLIO getMysqlIO() 187 | { 188 | return this.mysqlIO; 189 | } 190 | 191 | public String getServerVersion() 192 | { 193 | return this.serverVersion; 194 | } 195 | 196 | public String getUsername() 197 | { 198 | return this.username; 199 | } 200 | public String getPassword() 201 | { 202 | return this.password; 203 | } 204 | public String getDatabase() 205 | { 206 | return this.database; 207 | } 208 | public int getServerCapabilities() 209 | { 210 | return this.serverCapabilities; 211 | } 212 | public int getClientCapabilities() 213 | { 214 | return this.clientCapabilities; 215 | } 216 | 217 | public int getAuthPluginDataLength() 218 | { 219 | return this.authPluginDataLength; 220 | } 221 | 222 | public String getAuthSalt() 223 | { 224 | return this.authSalt; 225 | } 226 | public String getAuthSalt2() 227 | { 228 | return this.authSalt2; 229 | } 230 | public String getAuthPluginName() 231 | { 232 | return this.authPluginName; 233 | } 234 | 235 | public int getConnectionPacketSequence() 236 | { 237 | Log.d("Connection", "Packet Sequence Number Returned: " + this.packetSequenceNumber); 238 | return this.packetSequenceNumber; 239 | } 240 | 241 | public Socket getPlainSocket() 242 | { 243 | return this.mysqlSocket; 244 | } 245 | public SSLSocket getSSLSocket() 246 | { 247 | return this.mysqlSSLSocket; 248 | } 249 | 250 | public void setSocket(Socket socket) 251 | { 252 | this.mysqlSocket = socket; 253 | } 254 | public void setMySQLIO(MySQLIO mysqlIO) 255 | { 256 | this.mysqlIO = mysqlIO; 257 | } 258 | 259 | public String getHostname() 260 | { 261 | return this.hostname; 262 | } 263 | public int getPort() 264 | { 265 | return this.port; 266 | } 267 | 268 | @TargetApi(Build.VERSION_CODES.KITKAT) 269 | @RequiresApi(api = Build.VERSION_CODES.KITKAT) 270 | private void connect() 271 | { 272 | this.packetSequenceNumber = 1; 273 | //mysqlSocket = new Socket(this.hostname, this.port); 274 | SocketSender socketSender = new SocketSender(this, new IIntConnectionInterface() 275 | { 276 | @Override 277 | public void socketDataSent() 278 | { 279 | try 280 | { 281 | processWelcomePacket(); 282 | } 283 | catch (MySQLConnException e) { 284 | e.printStackTrace(); 285 | } 286 | catch (IOException e) { 287 | e.printStackTrace(); 288 | } 289 | } 290 | 291 | @Override 292 | public void handleException(MySQLConnException ex) 293 | { 294 | Log.e("Connection", ex.toString()); 295 | } 296 | }); 297 | socketSender.execute((byte[]) null); 298 | } 299 | 300 | private void processWelcomePacket() throws MySQLConnException, IOException { 301 | try { 302 | mysqlSocket.setSoTimeout(5000); 303 | 304 | //mysqlIO = new MySQLIO(Connection.this, mysqlSocket); 305 | if (Helpers.getMySQLPacketType(mysqlIO.getSocketByteArray()) == Helpers.MYSQL_PACKET_TYPE.MYSQL_ERROR_PACKET) 306 | { 307 | MySQLErrorPacket mySQLErrorPacket = new MySQLErrorPacket(Connection.this); 308 | throw new MySQLConnException(mySQLErrorPacket.getErrorMsg(), mySQLErrorPacket.getErrorCode(), mySQLErrorPacket.getSqlState()); 309 | } 310 | 311 | mysqlIO.shiftCurrentBytePosition(4); 312 | 313 | protocolVersion = (byte) (mysqlIO.extractDataAsString(1)); 314 | 315 | serverVersion = mysqlIO.extractDataAsString(false); 316 | 317 | //Pass the server version in to the major, minor and subminor versions - these could 318 | //be used if things need to be done differently based on the server we're connecting to 319 | parseVersionNumber(); 320 | 321 | byte[] temp = (byte[]) mysqlIO.extractDataAsString(4); 322 | temp = mysqlIO.swapByteArray(temp); 323 | connectionID = mysqlIO.fromByteArray(temp); 324 | byte[] salt1 = (byte[]) mysqlIO.extractDataAsString(8); 325 | authSalt = Connection.toString(salt1, 0, salt1.length); 326 | //There is a null terminator at the end of the salt, shift by one as we don't need it 327 | mysqlIO.shiftCurrentBytePosition(1); 328 | 329 | 330 | byte[] serverCapabilities = (byte[]) mysqlIO.extractDataAsString(2); 331 | baseServerCapabilities = (serverCapabilities[0] & 0xff) | ((serverCapabilities[1] & 0xff) << 8); 332 | Connection.this.serverCapabilities = baseServerCapabilities; 333 | 334 | //serverLanguage = String.format("%02X", (byte) mysqlIO.extractDataAsString(1)); 335 | serverLanguage = ((byte)mysqlIO.extractDataAsString(1)) & 0xff; 336 | 337 | setCharset(); 338 | serverStatus = mysqlIO.fromByteArray((byte[]) mysqlIO.extractDataAsString(2)); 339 | 340 | byte[] extendedServerCapabilitiesArray = (byte[]) mysqlIO.extractDataAsString(2); 341 | 342 | int extendedServerCapabilities = (extendedServerCapabilitiesArray[0] & 0xff) | ((extendedServerCapabilitiesArray[1] & 0xff) << 8); 343 | Connection.this.extendedServerCapabilities = extendedServerCapabilities; 344 | Connection.this.serverCapabilities |= extendedServerCapabilities << 16; 345 | 346 | //Set the client capabilities to match the server capabilities, we'll then update to turn off what we can't support 347 | //or don't wish to use 348 | Connection.this.clientCapabilities = Connection.this.serverCapabilities; 349 | 350 | 351 | if ((Connection.this.serverCapabilities & CLIENT_PLUGIN_AUTH) == CLIENT_PLUGIN_AUTH) 352 | { 353 | authPluginDataLength = (byte) mysqlIO.extractDataAsString(1); 354 | } 355 | else 356 | { 357 | //The CLIENT_AUTH_PLUGIN might not be set if MySQL version is below 358 | //5.5.0 but there is a null byte here instead so shift it along by 1 359 | mysqlIO.shiftCurrentBytePosition(1); 360 | } 361 | 362 | //Check if the server is supporting compression, if it does, we need to turn off as we don't 363 | if ((Connection.this.serverCapabilities & CLIENT_COMPRESS) == CLIENT_COMPRESS) 364 | { 365 | clientCapabilities &= ~CLIENT_COMPRESS; 366 | } 367 | 368 | //Check if the server is set to don't allow database.table, if so unset it so we can 369 | //Having this enabled means SHOW TABLES and SHOW DATABASES can't be executed as it will only 370 | //use the database that we are connected to 371 | if ((Connection.this.serverCapabilities & CLIENT_NO_SCHEMA) == CLIENT_NO_SCHEMA) 372 | { 373 | clientCapabilities &= ~CLIENT_NO_SCHEMA; 374 | } 375 | 376 | if ((Connection.this.serverCapabilities & CLIENT_ODBC) == CLIENT_ODBC) 377 | { 378 | clientCapabilities &= ~CLIENT_ODBC; 379 | } 380 | if ((Connection.this.serverCapabilities & CLIENT_INTERACTIVE) == CLIENT_INTERACTIVE) 381 | { 382 | clientCapabilities &= ~CLIENT_INTERACTIVE; 383 | } 384 | if ((Connection.this.serverCapabilities & CLIENT_IGNORE_SIGPIPE) == CLIENT_IGNORE_SIGPIPE) 385 | { 386 | clientCapabilities &= ~CLIENT_IGNORE_SIGPIPE; 387 | } 388 | if ((Connection.this.serverCapabilities & CLIENT_IGNORE_SPACE) == CLIENT_IGNORE_SPACE) 389 | { 390 | clientCapabilities &= ~ CLIENT_IGNORE_SPACE; 391 | } 392 | 393 | //There is 10 byte filler so shift on by 10 394 | mysqlIO.shiftCurrentBytePosition(10); 395 | if ((Connection.this.serverCapabilities & CLIENT_SECURE_CONNECTION) == CLIENT_SECURE_CONNECTION) { 396 | int length = 0; 397 | if (authPluginDataLength > 0) 398 | { 399 | length = authPluginDataLength - 8; 400 | } 401 | else 402 | { 403 | length = 13; 404 | } 405 | if (length > 13) //Shouldn't be any bigger than 13 406 | { 407 | length = 13; 408 | } 409 | 410 | byte[] salt = (byte[]) mysqlIO.extractDataAsString(length); 411 | authSalt2 = Connection.toString(salt, 0, salt.length); 412 | 413 | StringBuilder stringBuilder = new StringBuilder(length); 414 | stringBuilder.append(authSalt); 415 | stringBuilder.append(authSalt2); 416 | seed = stringBuilder.toString(); 417 | } 418 | 419 | if ((Connection.this.serverCapabilities & CLIENT_PLUGIN_AUTH) == CLIENT_PLUGIN_AUTH) { 420 | authPluginName = mysqlIO.extractDataAsString(false); 421 | } 422 | 423 | //Check if we're using TLS connection, if so we need to send an SSL Request Packet 424 | if ((Connection.this.serverCapabilities & CLIENT_SSL) == CLIENT_SSL) 425 | { 426 | sendSSLRequest(); 427 | } 428 | else 429 | { 430 | sendAuthResponse(); 431 | } 432 | } 433 | catch (IOException ex) 434 | { 435 | Log.e("MySQLConnection", ex.toString()); 436 | } 437 | catch (MySQLConnException ex) 438 | { 439 | throw ex; 440 | } 441 | catch (InvalidSQLPacketException e) 442 | { 443 | e.printStackTrace(); 444 | } 445 | catch (UnsupportedMySQLServerException ex) 446 | { 447 | Log.e("MySQLCOnnection", ex.toString()); 448 | } 449 | } 450 | 451 | private void setCharset() throws UnsupportedMySQLServerException { 452 | switch (serverLanguage) 453 | { 454 | case LATIN1_SWEDISH_CI: 455 | charset = Charset.forName("LATIN1"); 456 | break; 457 | case UTF8_GENERAL_CI: 458 | case UTF8_UNICODE_CI: 459 | charset = Charset.forName("UTF-8"); 460 | break; 461 | default: 462 | throw new UnsupportedMySQLServerException(this, "Server Language " + String.format("0x%02X", serverLanguage)); 463 | } 464 | } 465 | 466 | private void sendSSLRequest() 467 | { 468 | try 469 | { 470 | Log.d("Connection", "Sending SSL Request"); 471 | SSLRequest sslRequest = new SSLRequest(this); 472 | this.getMysqlIO().sendDataOnSocket(sslRequest.getPacketData().toByteArray(), true, new IIntConnectionInterface() 473 | { 474 | @Override 475 | public void socketDataSent() 476 | { 477 | Log.d("Connection", "Socket Data Sent"); 478 | performSSLHandshake(); 479 | } 480 | 481 | @Override 482 | public void handleException(MySQLConnException ex) 483 | { 484 | Log.e("MySQLConnection", ex.toString()); 485 | ex.printStackTrace(); 486 | } 487 | }); 488 | } 489 | catch (Exception ex) 490 | { 491 | Log.e("MySQLCOnnection", ex.toString()); 492 | ex.printStackTrace(); 493 | } 494 | } 495 | 496 | private void performSSLHandshake() 497 | { 498 | Log.d("Connection", "Performing SSL Handshake"); 499 | try 500 | { 501 | ServerSocketFactory ssf = ServerSocketFactory.getDefault(); 502 | SSLContext sslContext = SSLContext.getInstance("SSL"); 503 | sslContext.init(null, new TrustManager[]{new SSLTrustManager()}, null); 504 | SSLSocketFactory sslf = sslContext.getSocketFactory(); 505 | 506 | SSLSocket sslSocket = (SSLSocket) sslf.createSocket(mysqlSocket, null, 507 | mysqlSocket.getPort(), false); 508 | sslSocket.setEnabledProtocols(new String[]{"TLSv1.1"}); 509 | sslSocket.setTcpNoDelay(true); 510 | sslSocket.setReuseAddress(true); 511 | sslSocket.setSoTimeout(5000); 512 | 513 | List allowedCiphers = null; 514 | boolean disableDHAlgorithm = true; 515 | 516 | if (disableDHAlgorithm) { 517 | allowedCiphers = new ArrayList(); 518 | for (String cipher : sslSocket.getEnabledCipherSuites()) { 519 | if (!(disableDHAlgorithm && (cipher.indexOf("_DHE_") > -1 || cipher.indexOf("_DH_") > -1))) { 520 | allowedCiphers.add(cipher); 521 | } 522 | } 523 | } 524 | 525 | // if some ciphers were filtered into allowedCiphers 526 | if (allowedCiphers != null) { 527 | sslSocket.setEnabledCipherSuites(allowedCiphers.toArray(new String[0])); 528 | } 529 | 530 | sslSocket.startHandshake(); 531 | mysqlSSLSocket = sslSocket; 532 | //this.mysqlSocket = null; 533 | mysqlIO.updateSocketStream(mysqlSSLSocket); 534 | 535 | incrementPacketSequenceNumber(); 536 | sendAuthResponse(); 537 | } 538 | catch (SSLHandshakeException ex) 539 | { 540 | Log.e("MySQLConnection", ex.toString()); 541 | } 542 | catch (IOException ex) 543 | { 544 | Log.e("MySQLConnection",ex.toString()); 545 | } 546 | catch (NoSuchAlgorithmException e) { 547 | e.printStackTrace(); 548 | } 549 | catch (KeyManagementException e) { 550 | e.printStackTrace(); 551 | } 552 | } 553 | 554 | public void incrementPacketSequenceNumber() 555 | { 556 | this.packetSequenceNumber++; 557 | } 558 | 559 | @TargetApi(Build.VERSION_CODES.KITKAT) 560 | @RequiresApi(api = Build.VERSION_CODES.KITKAT) 561 | private void sendAuthResponse() 562 | { 563 | Log.d("MySQLConnection", "Sending Auth Response"); 564 | try 565 | { 566 | AuthResponse authResponse = new AuthResponse(this); 567 | 568 | this.getMysqlIO().sendDataOnSocket(authResponse.getPacketData().toByteArray(), new IIntConnectionInterface() 569 | { 570 | @Override 571 | public void socketDataSent() 572 | { 573 | Log.d("Connection", "Socket Data Sent"); 574 | try { 575 | Helpers.MYSQL_PACKET_TYPE mysqlPacketType = Helpers.getMySQLPacketType(Connection.this.mysqlIO.getSocketByteArray()); 576 | if (mysqlPacketType == Helpers.MYSQL_PACKET_TYPE.MYSQL_ERROR_PACKET) { 577 | MySQLErrorPacket mySQLErrorPacket = new MySQLErrorPacket(Connection.this); 578 | //We can't do anything else here, so throw a MySQLConnException 579 | 580 | final MySQLConnException connException = new MySQLConnException(mySQLErrorPacket.getErrorMsg(), mySQLErrorPacket.getErrorCode(), mySQLErrorPacket.getSqlState()); 581 | if (getReturnCallbackToMainThread()) 582 | { 583 | getActivity().runOnUiThread(new Runnable() 584 | { 585 | @Override 586 | public void run() 587 | { 588 | iConnectionInterface.handleMySQLConnException(connException); 589 | } 590 | }); 591 | } 592 | else 593 | { 594 | iConnectionInterface.handleMySQLConnException(connException); 595 | } 596 | } 597 | else if (mysqlPacketType == Helpers.MYSQL_PACKET_TYPE.MYSQL_OK_PACKET) { 598 | MySQLOKPacket mySQLOKPacket = new MySQLOKPacket(Connection.this); 599 | if (getReturnCallbackToMainThread()) 600 | { 601 | getActivity().runOnUiThread(new Runnable() 602 | { 603 | @Override 604 | public void run() 605 | { 606 | iConnectionInterface.actionCompleted(); 607 | } 608 | }); 609 | } 610 | else 611 | { 612 | iConnectionInterface.actionCompleted(); 613 | } 614 | } 615 | else if (mysqlPacketType == Helpers.MYSQL_PACKET_TYPE.MYSQL_EOF_PACKET) 616 | { 617 | MySQLOKPacket mySQLOKPacket = new MySQLOKPacket(Connection.this); 618 | if (getReturnCallbackToMainThread()) 619 | { 620 | getActivity().runOnUiThread(new Runnable() 621 | { 622 | @Override 623 | public void run() 624 | { 625 | iConnectionInterface.actionCompleted(); 626 | } 627 | }); 628 | } 629 | else 630 | { 631 | iConnectionInterface.actionCompleted(); 632 | } 633 | } 634 | } 635 | catch (final IOException ex) 636 | { 637 | Log.e("MySQLConnection", ex.toString()); 638 | if (getReturnCallbackToMainThread()) 639 | { 640 | getActivity().runOnUiThread(new Runnable() 641 | { 642 | @Override 643 | public void run() 644 | { 645 | iConnectionInterface.handleIOException(ex); 646 | } 647 | }); 648 | } 649 | else 650 | { 651 | iConnectionInterface.handleIOException(ex); 652 | } 653 | } 654 | catch (final InvalidSQLPacketException ex) 655 | { 656 | Log.e("MySQLConnection", ex.toString()); 657 | if (getReturnCallbackToMainThread()) 658 | { 659 | getActivity().runOnUiThread(new Runnable() 660 | { 661 | @Override 662 | public void run() 663 | { 664 | iConnectionInterface.handleInvalidSQLPacketException(ex); 665 | } 666 | }); 667 | } 668 | else 669 | { 670 | iConnectionInterface.handleInvalidSQLPacketException(ex); 671 | } 672 | } 673 | } 674 | 675 | @Override 676 | public void handleException(final MySQLConnException ex) 677 | { 678 | if (getReturnCallbackToMainThread()) 679 | { 680 | getActivity().runOnUiThread(new Runnable() 681 | { 682 | @Override 683 | public void run() 684 | { 685 | iConnectionInterface.handleException(ex); 686 | } 687 | }); 688 | } 689 | else 690 | { 691 | iConnectionInterface.handleException(ex); 692 | } 693 | } 694 | }); 695 | 696 | //Check the first byte, if the first byte in the response is 0xff then we have a MySQL Error Packet 697 | 698 | 699 | } 700 | catch (final IOException ex) 701 | { 702 | Log.e("MySQLConnection", ex.toString()); 703 | if (getReturnCallbackToMainThread()) 704 | { 705 | getActivity().runOnUiThread(new Runnable() 706 | { 707 | @Override 708 | public void run() 709 | { 710 | iConnectionInterface.handleIOException(ex); 711 | } 712 | }); 713 | } 714 | else 715 | { 716 | iConnectionInterface.handleIOException(ex); 717 | } 718 | } 719 | } 720 | 721 | 722 | 723 | 724 | public void updateClientCapabilities(int serverCapabilities) 725 | { 726 | this.clientCapabilities &= serverCapabilities; 727 | } 728 | 729 | public final String readString(byte[] byteBuffer) { 730 | int position = 0; 731 | int i = 0; 732 | int len = 0; 733 | int maxLen = 21; 734 | 735 | while ((i < maxLen) && (byteBuffer[i] != 0)) { 736 | len++; 737 | i++; 738 | } 739 | 740 | String s = Connection.toString(byteBuffer, position, len); 741 | 742 | return s; 743 | } 744 | 745 | public void resetPacketSequenceNumber() 746 | { 747 | this.packetSequenceNumber = 1; 748 | } 749 | public void resetPacketSequenceNumber(boolean resetToZero) 750 | { 751 | if (resetToZero) 752 | { 753 | this.packetSequenceNumber = 0; 754 | } 755 | else 756 | { 757 | this.packetSequenceNumber = 1; 758 | } 759 | } 760 | 761 | public static String toString(byte[] value, int offset, int length) { 762 | Charset cs = Charset.forName("UTF-8"); 763 | 764 | return cs.decode(ByteBuffer.wrap(value, offset, length)).toString(); 765 | } 766 | 767 | public Statement createStatement() 768 | { 769 | return new Statement(this); 770 | } 771 | 772 | private void parseVersionNumber() 773 | { 774 | if (this.serverVersion.indexOf("-") >= 0) 775 | { 776 | this.serverVersion = this.serverVersion.substring(0, this.serverVersion.indexOf("-") - 1); 777 | } 778 | this.serverVersion = this.serverVersion.replaceAll("[^\\d.]", ""); 779 | if ((this.serverVersion != null) && this.serverVersion.length() > 0) 780 | { 781 | String[] versions = this.serverVersion.split("\\."); 782 | //We expect 3 parts 783 | if (versions.length == 0) 784 | { 785 | //Set default values as we failed to parse 786 | this.majorVersion = -1; 787 | this.minorVersion = -1; 788 | this.subMinorVersion = -1; 789 | } 790 | if (versions.length < 3) 791 | { 792 | //This shouldn't happen, we should always get a version number of x.y.z but if we did get something 793 | //unexpected, parse what we can 794 | this.majorVersion = ((versions[0] != null) && versions[0].length() != 0) ? Integer.parseInt(versions[0]) : -1; 795 | this.minorVersion = ((versions.length > 1) && (versions[1] != null) && versions[1].length() != 0) ? Integer.parseInt(versions[1]) : -1; 796 | } 797 | else 798 | { 799 | //We got what we expected at least in size 800 | this.majorVersion = ((versions[0] != null) && versions[0].length() != 0) ? Integer.parseInt(versions[0]) : -1; 801 | this.minorVersion = ((versions[1] != null) && versions[1].length() != 0) ? Integer.parseInt(versions[1]) : -1; 802 | this.subMinorVersion = ((versions[2] != null) && versions[2].length() != 0) ? Integer.parseInt(versions[2]) : -1; 803 | } 804 | } 805 | else 806 | { 807 | //Set to invalid options - it means we failed to parse the version number from the connection greeting 808 | this.majorVersion = -1; 809 | this.minorVersion = -1; 810 | this.subMinorVersion = -1; 811 | } 812 | } 813 | 814 | public boolean isConnectedVersionLessThan(int major, int minor, int subMinor) 815 | { 816 | if (this.majorVersion < major) 817 | { 818 | return true; 819 | } 820 | else if (this.majorVersion == major && this.minorVersion < minor) 821 | { 822 | return true; 823 | } 824 | else if (this.majorVersion == major && this.minorVersion == minor && this.subMinorVersion < subMinor) 825 | { 826 | return true; 827 | } 828 | else 829 | { 830 | return false; 831 | } 832 | } 833 | 834 | public boolean doesVersionMeetMinimumRequired(int major, int minor, int subMinor) 835 | { 836 | if (this.majorVersion >= major) { 837 | if (this.majorVersion == major) { 838 | if (this.minorVersion >= minor) { 839 | if (this.minorVersion == minor) { 840 | return (this.minorVersion >= subMinor); 841 | } 842 | 843 | // newer than major.minor 844 | return true; 845 | } 846 | 847 | // older than major.minor 848 | return false; 849 | } 850 | 851 | // newer than major 852 | return true; 853 | } 854 | 855 | return false; 856 | } 857 | 858 | public void switchDatabase(final String database, final IConnectionInterface iConnectionInterface) 859 | { 860 | try 861 | { 862 | this.resetPacketSequenceNumber(true); 863 | COM_Query com_query = new COM_Query(this, COM_Query.COM_INIT_DB, database); 864 | final byte[] data = com_query.getPacketData().toByteArray(); 865 | SocketSender socketSender = new SocketSender(Connection.this, new IIntConnectionInterface() 866 | { 867 | @Override 868 | public void socketDataSent() 869 | { 870 | Connection.this.database = database; 871 | if (getReturnCallbackToMainThread()) 872 | { 873 | getActivity().runOnUiThread(new Runnable() 874 | { 875 | @Override 876 | public void run() 877 | { 878 | iConnectionInterface.actionCompleted(); 879 | } 880 | }); 881 | } 882 | else 883 | { 884 | iConnectionInterface.actionCompleted(); 885 | } 886 | } 887 | 888 | @Override 889 | public void handleException(final MySQLConnException ex) 890 | { 891 | if (getReturnCallbackToMainThread()) 892 | { 893 | getActivity().runOnUiThread(new Runnable() 894 | { 895 | @Override 896 | public void run() 897 | { 898 | iConnectionInterface.handleMySQLConnException(ex); 899 | } 900 | }); 901 | } 902 | else 903 | { 904 | iConnectionInterface.handleMySQLConnException(ex); 905 | } 906 | } 907 | }); 908 | socketSender.execute(data); 909 | } 910 | catch (final IOException ex) 911 | { 912 | if (getReturnCallbackToMainThread()) 913 | { 914 | getActivity().runOnUiThread(new Runnable() 915 | { 916 | @Override 917 | public void run() 918 | { 919 | iConnectionInterface.handleIOException(ex); 920 | } 921 | }); 922 | } 923 | else 924 | { 925 | iConnectionInterface.handleIOException(ex); 926 | } 927 | } 928 | } 929 | 930 | public String escape_string(String str) 931 | { 932 | String data = null; 933 | if (str != null && str.length() > 0) { 934 | str = str.replace("\\", "\\\\"); 935 | str = str.replace("'", "\\'"); 936 | str = str.replace("\0", "\\0"); 937 | str = str.replace("\n", "\\n"); 938 | str = str.replace("\r", "\\r"); 939 | str = str.replace("\"", "\\\""); 940 | str = str.replace("\\x1a", "\\Z"); 941 | data = str; 942 | } 943 | return data; 944 | } 945 | 946 | public void close() 947 | { 948 | try 949 | { 950 | COM_Query com_query = new COM_Query(this, COM_Query.COM_QUIT, null); 951 | final byte[] data = com_query.getPacketData().toByteArray(); 952 | SocketSender socketSender = new SocketSender(Connection.this, new IIntConnectionInterface() { 953 | @Override 954 | public void socketDataSent() { 955 | Log.d("MySQLConnection", "Database successfully closed"); 956 | } 957 | 958 | @Override 959 | public void handleException(MySQLConnException ex) { 960 | Log.e("MySQLConnection", ex.toString()); 961 | } 962 | }); 963 | socketSender.execute(data); 964 | } 965 | catch (IOException e) 966 | { 967 | e.printStackTrace(); 968 | } 969 | } 970 | } 971 | --------------------------------------------------------------------------------