├── 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 |
8 |
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 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
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 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
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 |
--------------------------------------------------------------------------------