/* This file is part of VoltDB. * Copyright (C) 2008-2017 VoltDB Inc. * * Permission is hereby granted, free of charge, to any person obtaining * a copy of this software and associated documentation files (the * "Software"), to deal in the Software without restriction, including * without limitation the rights to use, copy, modify, merge, publish, * distribute, sublicense, and/or sell copies of the Software, and to * permit persons to whom the Software is furnished to do so, subject to * the following conditions: * * The above copyright notice and this permission notice shall be * included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. */ package org.voltdb.regressionsuites; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.IOException; import java.nio.ByteBuffer; import java.security.SecureRandom; import java.util.Arrays; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.voltdb.BackendTarget; import org.voltdb.StoredProcedureInvocation; import org.voltdb.client.ClientAuthScheme; import org.voltdb.client.ConnectionUtil; import org.voltdb.compiler.VoltProjectBuilder; public class TestClientPortChannel extends JUnit4LocalClusterTest { int m_clientPort; int m_adminPort; LocalCluster m_config; public TestClientPortChannel() { } /** * JUnit special method called to setup the test. This instance will start * the VoltDB server using the VoltServerConfig instance provided. */ @Before public void setUp() throws Exception { m_clientPort = SecureRandom.getInstance("SHA1PRNG").nextInt(2000) + 22000; m_adminPort = m_clientPort + 1; System.out.println("Random Client port is: " + m_clientPort); try { //Build the catalog VoltProjectBuilder builder = new VoltProjectBuilder(); String mySchema = "create table A (" + "s varchar(20) default null, " + "); " + "create table B (" + "clm_integer integer default 0 not null, " + "clm_tinyint tinyint default 0, " + "clm_smallint smallint default 0, " + "clm_bigint bigint default 0, " + "clm_string varchar(20) default null, " + "clm_decimal decimal default null, " + "clm_float float default null, " + "clm_timestamp timestamp default null, " + "clm_varinary varbinary(20) default null" + "); "; builder.addLiteralSchema(mySchema); String catalogJar = "dummy.jar"; m_config = new LocalCluster(catalogJar, 2, 1, 0, BackendTarget.NATIVE_EE_JNI); m_config.setHasLocalServer(true); m_config.portGenerator.enablePortProvider(); m_config.portGenerator.pprovider.setNextClient(m_clientPort); m_config.portGenerator.pprovider.setAdmin(m_adminPort); m_config.setHasLocalServer(false); boolean success = m_config.compile(builder); assertTrue(success); m_config.startUp(); } catch (IOException ex) { fail(ex.getMessage()); } finally { } } /** * JUnit special method called to shutdown the test. This instance will * stop the VoltDB server using the VoltServerConfig instance provided. */ @After public void tearDown() throws Exception { if (m_config != null) { m_config.shutDown(); } } // Just do a login public void login(PortConnector conn) throws Exception { ByteBuffer buf = ByteBuffer.allocate(41); buf.putInt(37); buf.put((byte) 0); buf.putInt(8); buf.put("database".getBytes("UTF-8")); buf.putInt(0); buf.put("".getBytes("UTF-8")); buf.put(ConnectionUtil.getHashedPassword(ClientAuthScheme.HASH_SHA1, "")); buf.flip(); conn.write(buf); ByteBuffer resp = ByteBuffer.allocate(4); conn.read(resp, 4); resp.flip(); int length = resp.getInt(); resp = ByteBuffer.allocate(length); conn.read(resp, length); resp.flip(); byte code = resp.get(); assertEquals(code, 0); byte rcode = resp.get(); assertEquals(rcode, 0); resp.getInt(); resp.getLong(); resp.getLong(); resp.getInt(); int buildStringLength = resp.getInt(); byte buildStringBytes[] = new byte[buildStringLength]; resp.get(buildStringBytes); System.out.println("Authenticated to server: " + new String(buildStringBytes, "UTF-8")); } // Just do a login public void loginSha2(PortConnector conn) throws Exception { ByteBuffer buf = ByteBuffer.allocate(54); buf.putInt(50); buf.put((byte) 1); buf.put((byte )ClientAuthScheme.HASH_SHA256.getValue()); // Add scheme buf.putInt(8); buf.put("database".getBytes("UTF-8")); buf.putInt(0); buf.put("".getBytes("UTF-8")); buf.put(ConnectionUtil.getHashedPassword(ClientAuthScheme.HASH_SHA256, "")); buf.flip(); conn.write(buf); ByteBuffer resp = ByteBuffer.allocate(4); conn.read(resp, 4); resp.flip(); int length = resp.getInt(); resp = ByteBuffer.allocate(length); conn.read(resp, length); resp.flip(); byte code = resp.get(); assertEquals(code, 0); byte rcode = resp.get(); assertEquals(rcode, 0); resp.getInt(); resp.getLong(); resp.getLong(); resp.getInt(); int buildStringLength = resp.getInt(); byte buildStringBytes[] = new byte[buildStringLength]; resp.get(buildStringBytes); System.out.println("Authenticated to server: " + new String(buildStringBytes, "UTF-8")); } //Login with new connection and close public void doLoginAndClose(int port) throws Exception { System.out.println("Testing valid login message"); PortConnector channel = new PortConnector("localhost", port); channel.connect(); login(channel); channel.close(); } //Login with new connection and close public void doLoginSha2AndClose(int port) throws Exception { System.out.println("Testing valid login message"); PortConnector channel = new PortConnector("localhost", port); channel.connect(); loginSha2(channel); channel.close(); } @Test public void testLoginMessagesClientPort() throws Exception { runBadLoginMessages(m_clientPort); } @Test public void testLoginMessagesAdminPort() throws Exception { runBadLoginMessages(m_adminPort); } public void runBadLoginMessages(int port) throws Exception { //Just connect and disconnect PortConnector channel = new PortConnector("localhost", port); System.out.println("Testing Connect and Close"); int scnt = 0; int fcnt = 0; for (int i = 0; i < 100; i++) { try { channel.connect(); channel.close(); scnt++; } catch (Exception ex) { ex.printStackTrace(); fcnt++; } System.out.println("Success: " + scnt + " Failed: " + fcnt); } //Bad +ve length System.out.println("Testing bad login message"); channel.connect(); ByteBuffer buf = ByteBuffer.allocate(Integer.SIZE); buf.putInt(10); buf.position(0); channel.write(buf); channel.close(); //Bad -ve length. System.out.println("Testing negative length of message"); channel.connect(); buf = ByteBuffer.allocate(Integer.SIZE); buf.putInt(-1); buf.position(0); channel.write(buf); channel.close(); //Bad 0 length. System.out.println("Testing zero length of message"); channel.connect(); buf = ByteBuffer.allocate(Integer.SIZE); buf.putInt(0); buf.position(0); channel.write(buf); channel.close(); //too big length System.out.println("Testing too big length of message"); channel.connect(); buf = ByteBuffer.allocate(Integer.SIZE); buf.putInt(Integer.MAX_VALUE); buf.position(0); channel.write(buf); channel.close(); //login message with bad version System.out.println("Testing bad service name"); channel.connect(); buf = ByteBuffer.allocate(42); buf.putInt(38); buf.put((byte) '0'); buf.put((byte) ClientAuthScheme.HASH_SHA1.getValue()); buf.putInt(8); buf.put("dataCase".getBytes("UTF-8")); buf.putInt(0); buf.put("".getBytes("UTF-8")); buf.put(ConnectionUtil.getHashedPassword(ClientAuthScheme.HASH_SHA1, "")); buf.flip(); channel.write(buf); //Now this will fail because bad version will be read. try { ByteBuffer resp = ByteBuffer.allocate(6); channel.read(resp, 6); resp.flip(); resp.getInt(); resp.get(); byte code = resp.get(); assertEquals(5, code); } catch (Exception ioex) { //Should not get called; we get a legit response. fail(); } //login message with bad version System.out.println("Testing bad scheme name"); channel.connect(); buf = ByteBuffer.allocate(42); buf.putInt(38); buf.put((byte) 1); buf.put((byte) 3); buf.putInt(8); buf.put("database".getBytes("UTF-8")); buf.putInt(0); buf.put("".getBytes("UTF-8")); buf.put(ConnectionUtil.getHashedPassword(ClientAuthScheme.HASH_SHA1, "")); buf.flip(); channel.write(buf); //Now this will fail because bad version will be read. try { ByteBuffer resp = ByteBuffer.allocate(6); channel.read(resp, 6); resp.flip(); resp.getInt(); resp.get(); byte code = resp.get(); assertEquals(3, code); } catch (Exception ioex) { //Should not get called; we get a legit response. fail(); } //login message with bad service name length. System.out.println("Testing service name with invalid length"); channel.connect(); buf = ByteBuffer.allocate(42); buf.putInt(38); buf.put((byte) '0'); buf.put((byte) ClientAuthScheme.HASH_SHA1.getValue()); buf.putInt(Integer.MAX_VALUE); buf.put("database".getBytes("UTF-8")); buf.putInt(0); buf.put("".getBytes("UTF-8")); buf.put(ConnectionUtil.getHashedPassword(ClientAuthScheme.HASH_SHA1, "")); buf.flip(); channel.write(buf); boolean mustfail = false; try { ByteBuffer resp = ByteBuffer.allocate(6); channel.read(resp, 6); resp.flip(); } catch (Exception ioex) { //Should not get called; we get a legit response. mustfail = true; } assertTrue(mustfail); channel.close(); //Make sure server is up and we can login/connect after all the beating it took. doLoginAndClose(port); doLoginSha2AndClose(port); } @Test public void testInvocationClientPort() throws Exception { runInvocationMessageTest(ClientAuthScheme.HASH_SHA1, m_clientPort); runInvocationMessageTest(ClientAuthScheme.HASH_SHA256, m_clientPort); } @Test public void testInvocationAdminPort() throws Exception { runInvocationMessageTest(ClientAuthScheme.HASH_SHA1, m_adminPort); runInvocationMessageTest(ClientAuthScheme.HASH_SHA256, m_adminPort); } final int iVERSION = 0; final int iLENGH = 1; final byte VAR1[] = { 0, // Version (1 byte) 0, 0, 0, 5, // procedure name string length (4 byte int) '@', 'P', 'i', 'n', 'g', // procedure name 0, 0, 0, 0, 0, 0, 0, 0, // Client Data (8 byte long) 0, // Fields byte 0 // Status byte }; private void updateLength(byte[] arr, int length) { byte iarr[] = ByteBuffer.allocate(4).putInt(length).array(); for (int i = 0; i < 4; i++) { arr[i + iLENGH] = iarr[i]; } } public void runInvocationMessageTest(ClientAuthScheme scheme, int port) throws Exception { PortConnector channel = new PortConnector("localhost", port); channel.connect(); //Now start testing combinations of invocation messages. //Start with a good Ping procedure invocation. System.out.println("Testing good Ping invocation before login"); byte pingr[] = VAR1.clone(); try { verifyInvocation(pingr, channel, (byte) 1); fail("Expect exception"); } catch (Exception ioe) { System.out.println("Good that we could not execute a proc."); } //reconnect as we should have bombed. channel.connect(); //Send login message before invocation. if (scheme == ClientAuthScheme.HASH_SHA1) login(channel); else loginSha2(channel); //Now start testing combinations of invocation messages. //Start with a good Ping procedure invocation. System.out.println("Testing good Ping invocation"); verifyInvocation(pingr, channel, (byte) 1); final byte ERROR_CODE = -3; //With bad message length of various shapes and sizes //Procedure name length mismatch System.out.println("Testing Ping invocation with bad procname length"); byte bad_length[] = VAR1.clone(); updateLength(bad_length, 6); verifyInvocation(bad_length, channel, ERROR_CODE); //Procedure name length -ve long System.out.println("Testing Ping invocation with -1 procname length."); byte neg1_length[] = VAR1.clone(); updateLength(neg1_length, -1); verifyInvocation(neg1_length, channel, ERROR_CODE); System.out.println("Testing Ping invocation with -200 procname length."); byte neg2_length[] = VAR1.clone(); updateLength(neg2_length, -200); verifyInvocation(neg2_length, channel, ERROR_CODE); //Procedure name length too long System.out.println("Testing Ping invocation with looooong procname length."); byte too_long_length[] = VAR1.clone(); updateLength(too_long_length, Integer.MAX_VALUE); verifyInvocation(too_long_length, channel, ERROR_CODE); //Bad protocol version System.out.println("Testing good Ping invocation with bad protocol version."); byte bad_proto[] = VAR1.clone(); bad_proto[iVERSION] = (byte) (StoredProcedureInvocation.CURRENT_MOST_RECENT_VERSION + 1); verifyInvocation(bad_proto, channel, ERROR_CODE); //Client Data - Bad Data meaning invalid number of bytes. System.out.println("Testing good Ping invocation with incomplete client data"); byte bad_cl_data[] = Arrays.copyOfRange(VAR1, 0, 12); verifyInvocation(bad_cl_data, channel, ERROR_CODE); System.out.println("Testing good Ping invocation Again"); verifyInvocation(pingr, channel, (byte) 1); channel.close(); } @Test public void testInvocationParamsClientPort() throws Exception { runInvocationParams(ClientAuthScheme.HASH_SHA1, m_clientPort); runInvocationParams(ClientAuthScheme.HASH_SHA256, m_clientPort); } @Test public void testInvocationParamsAdminPort() throws Exception { runInvocationParams(ClientAuthScheme.HASH_SHA1, m_adminPort); runInvocationParams(ClientAuthScheme.HASH_SHA256, m_adminPort); } final byte VAR2[] = { 0, // Version (1 byte) 0, 0, 0, 8, // procedure name string length (4 byte int) 'B', '.', 'i', 'n', 's', 'e', 'r', 't', // procedure name 0, 0, 0, 0, 0, 0, 0, 0, // Client Data (8 byte long) 0, // Fields byte 0 // Status byte }; final int PIDX = VAR2.length - 2; private void updateShortBytes(byte[] arr, short num) { byte[] iarr = ByteBuffer.allocate(2).putShort(num).array(); arr[PIDX] = iarr[0]; arr[PIDX + 1] = iarr[1]; } private void updateIntBytes(byte[] arr, int num, int ith) { byte[] iarr = ByteBuffer.allocate(4).putInt(num).array(); for (int i = 0; i < 4; i++) { arr[ith + i] = iarr[i]; } } public void runInvocationParams(ClientAuthScheme scheme, int port) throws Exception { PortConnector channel = new PortConnector("localhost", port); channel.connect(); //Send login message before invocation. if (scheme == ClientAuthScheme.HASH_SHA1) login(channel); else loginSha2(channel); //Now start testing combinations of invocation messages with invocation params. final byte ERROR_CODE = (byte) -2; // //no param // byte i1[] = VAR2.clone(); verifyInvocation(i1, channel, ERROR_CODE); byte i2[] = VAR2.clone(); updateShortBytes(i2, Short.MAX_VALUE); verifyInvocation(i2, channel, ERROR_CODE); updateShortBytes(i2, (short) -1); verifyInvocation(i2, channel, ERROR_CODE); //Lie about param count. updateShortBytes(i2, (short) 4); verifyInvocation(i2, channel, ERROR_CODE); //Put correct param count but no values updateShortBytes(i2, (short) 9); verifyInvocation(i2, channel, ERROR_CODE); //Lie length of param string. byte i3[] = {0, //Version 0, 0, 0, 8, 'A', '.', 'i', 'n', 's', 'e', 'r', 't', //proc string length and name 0, 0, 0, 0, 0, 0, 0, 0, //Client Data 0, 0, 9, //9 is string type }; updateShortBytes(i3, (short) 1); verifyInvocation(i3, channel, ERROR_CODE); //Pass string length of 8 but dont pass string. byte i4[] = {0, //Version 0, 0, 0, 8, 'A', '.', 'i', 'n', 's', 'e', 'r', 't', //proc string length and name 0, 0, 0, 0, 0, 0, 0, 0, //Client Data 0, 0, 9, //9 is string type 0, 0, 0, 0 //String length }; updateShortBytes(i4, (short) 1); updateIntBytes(i4, 8, 24); verifyInvocation(i4, channel, ERROR_CODE); //Pass string length of 6 and pass 6 byte string this should succeed. byte i5[] = {0, //Version 0, 0, 0, 8, 'A', '.', 'i', 'n', 's', 'e', 'r', 't', //proc string length and name 0, 0, 0, 0, 0, 0, 0, 0, //Client Data 0, 0, 9, //9 is string type 0, 0, 0, 0, //String length 'v', 'o', 'l', 't', 'd', 'b' }; updateShortBytes(i5, (short) 1); updateIntBytes(i5, 6, 24); verifyInvocation(i5, channel, (byte) 1); //Lie length of param array. byte i6[] = {0, //Version 0, 0, 0, 8, 'A', '.', 'i', 'n', 's', 'e', 'r', 't', //proc string length and name 0, 0, 0, 0, 0, 0, 0, 0, //Client Data 0, 0, -99 //-99 is array }; updateShortBytes(i6, (short) 1); verifyInvocation(i6, channel, ERROR_CODE); //Lie length of param array. byte i61[] = {0, //Version 0, 0, 0, 8, 'A', '.', 'i', 'n', 's', 'e', 'r', 't', //proc string length and name 0, 0, 0, 0, 0, 0, 0, 0, //Client Data 0, 0, -99, //-99 is array 70, //bad element type }; updateShortBytes(i61, (short) 1); verifyInvocation(i61, channel, ERROR_CODE); //Array of Array not supported should not crash server. byte i62[] = {0, //Version 0, 0, 0, 8, 'A', '.', 'i', 'n', 's', 'e', 'r', 't', //proc string length and name 0, 0, 0, 0, 0, 0, 0, 0, //Client Data 0, 0, -99, //-99 is array -99, //Array of Array }; updateShortBytes(i62, (short) 1); verifyInvocation(i62, channel, ERROR_CODE); //Array of string but no data. byte i63[] = {0, //Version 0, 0, 0, 8, 'A', '.', 'i', 'n', 's', 'e', 'r', 't', //proc string length and name 0, 0, 0, 0, 0, 0, 0, 0, //Client Data 0, 0, -99, //-99 is array 9, //String with no data }; updateShortBytes(i63, (short) 1); verifyInvocation(i63, channel, ERROR_CODE); //Array of string with bad length byte i631[] = {0, //Version 0, 0, 0, 8, 'A', '.', 'i', 'n', 's', 'e', 'r', 't', //proc string length and name 0, 0, 0, 0, 0, 0, 0, 0, //Client Data 0, 0, -99, //-99 is array 9, 0, 0, 0, 0//String with no data }; updateShortBytes(i631, (short) 1); updateIntBytes(i631, Integer.MAX_VALUE, i631.length - 4); verifyInvocation(i631, channel, ERROR_CODE); //Array of long but no data. byte i64[] = {0, //Version 0, 0, 0, 8, 'A', '.', 'i', 'n', 's', 'e', 'r', 't', //proc string length and name 0, 0, 0, 0, 0, 0, 0, 0, //Client Data 0, 0, -99, //-99 is array 6, //Long with no data }; updateShortBytes(i64, (short) 1); verifyInvocation(i64, channel, ERROR_CODE); //Array of long with non parsable long byte i65[] = {0, //Version 0, 0, 0, 8, 'A', '.', 'i', 'n', 's', 'e', 'r', 't', //proc string length and name 0, 0, 0, 0, 0, 0, 0, 0, //Client Data 0, 0, -99, //-99 is array 6, //Long 'A' }; updateShortBytes(i65, (short) 1); verifyInvocation(i65, channel, ERROR_CODE); //Lie data type invaid data type. byte i7[] = {0, //Version 0, 0, 0, 8, 'A', '.', 'i', 'n', 's', 'e', 'r', 't', //proc string length and name 0, 0, 0, 0, 0, 0, 0, 0, //Client Data 0, 0, 70, //98 bad }; updateShortBytes(i7, (short) 1); verifyInvocation(i7, channel, ERROR_CODE); //Lie data type invaid data type. byte i71[] = {0, //Version 0, 0, 0, 8, 'A', '.', 'i', 'n', 's', 'e', 'r', 't', //proc string length and name 0, 0, 0, 0, 0, 0, 0, 0, //Client Data 0, 0, 26, //98 bad }; updateShortBytes(i71, (short) 1); verifyInvocation(i71, channel, ERROR_CODE); //Test Good Ping at end to ensure server is up. System.out.println("Testing good Ping invocation Again"); byte pingr[] = VAR1.clone(); verifyInvocation(pingr, channel, (byte) 1); //Test insert again verifyInvocation(i5, channel, (byte) 1); channel.close(); } private ByteBuffer verifyInvocation(byte[] in, PortConnector channel, byte expected_status) throws IOException { ByteBuffer buf = ByteBuffer.allocate(in.length + 4); buf.putInt(in.length); buf.put(in); buf.flip(); channel.write(buf); ByteBuffer lenbuf = ByteBuffer.allocate(4); channel.read(lenbuf, 4); lenbuf.flip(); int len = lenbuf.getInt(); System.out.println("Response length is: " + len); ByteBuffer respbuf = ByteBuffer.allocate(len); channel.read(respbuf, len); respbuf.flip(); System.out.println("Version is: " + respbuf.get()); //client handle data long handle = respbuf.getLong(); System.out.println("Client Data is: " + handle); //fields present and status code. byte fp = respbuf.get(); System.out.println("Fields Present is: " + fp); byte status = respbuf.get(); System.out.println("Status is: " + status); assertEquals(expected_status, status); len = respbuf.getInt(); System.out.println("Status length is: " + len); if (len > 0) { byte statusb[] = new byte[len]; respbuf.get(statusb); System.out.println("Status is: " + new String(statusb, "UTF-8")); } return respbuf; } }