1b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair/*
2b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair * Copyright 2007 the original author or authors.
3b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair *
4b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair * Licensed under the Apache License, Version 2.0 (the "License");
5b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair * you may not use this file except in compliance with the License.
6b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair * You may obtain a copy of the License at
7b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair *
8b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair *      http://www.apache.org/licenses/LICENSE-2.0
9b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair *
10b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair * Unless required by applicable law or agreed to in writing, software
11b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair * distributed under the License is distributed on an "AS IS" BASIS,
12b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair * See the License for the specific language governing permissions and
14b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair * limitations under the License.
15b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair */
16b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairpackage org.mockftpserver.core.session;
17b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
18b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport org.apache.log4j.Logger;
19b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport org.mockftpserver.core.MockFtpServerException;
20b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport org.mockftpserver.core.command.Command;
21b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport org.mockftpserver.core.socket.StubServerSocket;
22b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport org.mockftpserver.core.socket.StubServerSocketFactory;
23b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport org.mockftpserver.core.socket.StubSocket;
24b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport org.mockftpserver.core.socket.StubSocketFactory;
25b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport org.mockftpserver.core.util.AssertFailedException;
26b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport org.mockftpserver.test.AbstractTest;
27b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
28b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport java.io.ByteArrayInputStream;
29b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport java.io.ByteArrayOutputStream;
30b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport java.io.IOException;
31b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport java.io.InputStream;
32b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport java.net.InetAddress;
33b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport java.net.SocketTimeoutException;
34b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport java.util.Collections;
35b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport java.util.HashMap;
36b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairimport java.util.Map;
37b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
38b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair/**
39b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair * Tests for the DefaultSession class
40b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair *
41b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair * @version $Revision$ - $Date$
42b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair *
43b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair * @author Chris Mair
44b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair */
45b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismairpublic final class DefaultSessionTest extends AbstractTest {
46b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
47b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    private static final Logger LOG = Logger.getLogger(DefaultSessionTest.class);
48b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    private static final String DATA = "sample data 123";
49b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    private static final int PORT = 197;
50b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    private static final String NAME1 = "name1";
51b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    private static final String NAME2 = "name2";
52b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    private static final Object VALUE = "value";
53b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
54b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    private DefaultSession session;
55b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    private ByteArrayOutputStream outputStream;
56b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    private Map commandHandlerMap;
57b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    private StubSocket stubSocket;
58b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    private InetAddress clientHost;
59b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
60b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
61b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Perform initialization before each test
62b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     *
63b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * @see org.mockftpserver.test.AbstractTest#setUp()
64b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
65b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    protected void setUp() throws Exception {
66b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        super.setUp();
67b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
68b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        commandHandlerMap = new HashMap();
69b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        outputStream = new ByteArrayOutputStream();
70b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session = createDefaultSession("");
71b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        clientHost = InetAddress.getLocalHost();
72b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
73b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
74b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
75b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * @see org.mockftpserver.test.AbstractTest#tearDown()
76b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
77b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    protected void tearDown() throws Exception {
78b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        super.tearDown();
79b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
80b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
81b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
82b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the Constructor when the control socket is null
83b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
84b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testConstructor_NullControlSocket() {
85b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        try {
86b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            new DefaultSession(null, commandHandlerMap);
87b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            fail("Expected AssertFailedException");
88b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
89b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        catch (AssertFailedException expected) {
90b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            LOG.info("Expected: " + expected);
91b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
92b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
93b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
94b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
95b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the Constructor when the command handler Map is null
96b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
97b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testConstructor_NullCommandHandlerMap() {
98b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        try {
99b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            new DefaultSession(stubSocket, null);
100b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            fail("Expected AssertFailedException");
101b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
102b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        catch (AssertFailedException expected) {
103b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            LOG.info("Expected: " + expected);
104b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
105b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
106b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
107b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
108b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the setClientDataPort() method
109b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
110b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testSetClientDataPort() {
111b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubSocket stubSocket = createTestSocket("");
112b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubSocketFactory stubSocketFactory = new StubSocketFactory(stubSocket);
113b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.socketFactory = stubSocketFactory;
114b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.setClientDataPort(PORT);
115b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.setClientDataHost(clientHost);
116b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.openDataConnection();
117b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("data port", PORT, stubSocketFactory.requestedDataPort);
118b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
119b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
120b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
121b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the setClientDataPort() method after the session was in passive data mode
122b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
123b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testSetClientDataPort_AfterPassiveConnectionMode() throws IOException {
124b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubServerSocket stubServerSocket = new StubServerSocket(PORT);
125b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubServerSocketFactory stubServerSocketFactory = new StubServerSocketFactory(stubServerSocket);
126b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.serverSocketFactory = stubServerSocketFactory;
127b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
128b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.switchToPassiveMode();
129b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertFalse("server socket closed", stubServerSocket.isClosed());
130b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertNotNull("passiveModeDataSocket", session.passiveModeDataSocket);
131b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.setClientDataPort(PORT);
132b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
133b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        // Make sure that any passive mode connection info is cleared out
134b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertTrue("server socket closed", stubServerSocket.isClosed());
135b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertNull("passiveModeDataSocket should be null", session.passiveModeDataSocket);
136b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
137b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
138b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
139b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the setClientHost() method
140b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
141b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testSetClientHost() throws Exception {
142b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubSocket stubSocket = createTestSocket("");
143b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubSocketFactory stubSocketFactory = new StubSocketFactory(stubSocket);
144b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.socketFactory = stubSocketFactory;
145b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.setClientDataHost(clientHost);
146b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.openDataConnection();
147b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("client host", clientHost, stubSocketFactory.requestedHost);
148b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
149b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
150b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
151b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the openDataConnection(), setClientDataPort() and setClientDataHost() methods
152b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
153b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testOpenDataConnection() {
154b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubSocket stubSocket = createTestSocket("");
155b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubSocketFactory stubSocketFactory = new StubSocketFactory(stubSocket);
156b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.socketFactory = stubSocketFactory;
157b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
158b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        // Use default client data port
159b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.setClientDataHost(clientHost);
160b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.openDataConnection();
161b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("data port", DefaultSession.DEFAULT_CLIENT_DATA_PORT, stubSocketFactory.requestedDataPort);
162b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("client host", clientHost, stubSocketFactory.requestedHost);
163b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
164b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        // Set client data port explicitly
165b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.setClientDataPort(PORT);
166b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.setClientDataHost(clientHost);
167b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.openDataConnection();
168b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("data port", PORT, stubSocketFactory.requestedDataPort);
169b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("client host", clientHost, stubSocketFactory.requestedHost);
170b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
171b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
172b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
173b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the OpenDataConnection method, when in passive mode and no incoming connection is
174b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * initiated
175b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
176b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testOpenDataConnection_PassiveMode_NoConnection() throws IOException {
177b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
178b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubServerSocket stubServerSocket = new StubServerSocket(PORT);
179b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubServerSocketFactory stubServerSocketFactory = new StubServerSocketFactory(stubServerSocket);
180b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.serverSocketFactory = stubServerSocketFactory;
181b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
182b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.switchToPassiveMode();
183b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
184b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        try {
185b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            session.openDataConnection();
186b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            fail("Expected MockFtpServerException");
187b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
188b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        catch (MockFtpServerException expected) {
189b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            LOG.info("Expected: " + expected);
190b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            assertSame("cause", SocketTimeoutException.class, expected.getCause().getClass());
191b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
192b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
193b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
194b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
195b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the OpenDataConnection method, when the clientHost has not been set
196b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
197b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testOpenDataConnection_NullClientHost() {
198b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        try {
199b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            session.openDataConnection();
200b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            fail("Expected AssertFailedException");
201b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
202b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        catch (AssertFailedException expected) {
203b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            LOG.info("Expected: " + expected);
204b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
205b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
206b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
207b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
208b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the readData() method
209b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
210b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testReadData() {
211b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubSocket stubSocket = createTestSocket(DATA);
212b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.socketFactory = new StubSocketFactory(stubSocket);
213b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.setClientDataHost(clientHost);
214b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
215b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.openDataConnection();
216b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        byte[] data = session.readData();
217b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        LOG.info("data=[" + new String(data) + "]");
218b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("data", DATA.getBytes(), data);
219b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
220b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
221b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
222b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the readData() method after switching to passive mode
223b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
224b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testReadData_PassiveMode() throws IOException {
225b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubSocket stubSocket = createTestSocket(DATA);
226b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubServerSocket stubServerSocket = new StubServerSocket(PORT, stubSocket);
227b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubServerSocketFactory stubServerSocketFactory = new StubServerSocketFactory(stubServerSocket);
228b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.serverSocketFactory = stubServerSocketFactory;
229b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
230b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.switchToPassiveMode();
231b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.openDataConnection();
232b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        byte[] data = session.readData();
233b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        LOG.info("data=[" + new String(data) + "]");
234b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("data", DATA.getBytes(), data);
235b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
236b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
237b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
238b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the closeDataConnection() method
239b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
240b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testCloseDataConnection() {
241b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubSocket stubSocket = createTestSocket(DATA);
242b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.socketFactory = new StubSocketFactory(stubSocket);
243b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
244b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.setClientDataHost(clientHost);
245b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.openDataConnection();
246b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.closeDataConnection();
247b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertTrue("client data socket should be closed", stubSocket.isClosed());
248b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
249b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
250b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
251b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the switchToPassiveMode() method
252b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
253b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testSwitchToPassiveMode() throws IOException {
254b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubServerSocket stubServerSocket = new StubServerSocket(PORT);
255b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubServerSocketFactory stubServerSocketFactory = new StubServerSocketFactory(stubServerSocket);
256b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.serverSocketFactory = stubServerSocketFactory;
257b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
258b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertNull("passiveModeDataSocket starts out null", session.passiveModeDataSocket);
259b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        int port = session.switchToPassiveMode();
260b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertSame("passiveModeDataSocket", stubServerSocket, session.passiveModeDataSocket);
261b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("port", PORT, port);
262b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
263b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
264b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
265b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the getServerHost() method
266b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
267b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testGetServerHost() {
268b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("host", DEFAULT_HOST, session.getServerHost());
269b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
270b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
271b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
272b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the getClientHost() method when the session is not yet started
273b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
274b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testGetClientHost_NotRunning() {
275b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertNull("null", session.getClientHost());
276b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
277b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
278b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
279b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the parseCommand() method
280b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
281b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testParseCommand() {
282b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        Command command = session.parseCommand("LIST");
283b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("command name", "LIST", command.getName());
284b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("command parameters", EMPTY, command.getParameters());
285b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
286b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        command = session.parseCommand("USER user123");
287b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("command name", "USER", command.getName());
288b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("command parameters", array("user123"), command.getParameters());
289b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
290b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        command = session.parseCommand("PORT 127,0,0,1,17,37");
291b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("command name", "PORT", command.getName());
292b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("command parameters", new String[] { "127", "0", "0", "1", "17", "37" }, command
293b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair                .getParameters());
294b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
295b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
296b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
297b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the parseCommand() method, passing in an empty command String
298b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
299b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testParseCommand_EmptyCommandString() {
300b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        try {
301b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            session.parseCommand("");
302b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            fail("Expected AssertFailedException");
303b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
304b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        catch (AssertFailedException expected) {
305b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            LOG.info("Expected: " + expected);
306b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
307b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
308b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
309b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
310b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the sendData() method, as well as the openDataConnection() and closeDataConnection()
311b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
312b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testSendData() {
313b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubSocket stubSocket = createTestSocket("1234567890 abcdef");
314b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.socketFactory = new StubSocketFactory(stubSocket);
315b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
316b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.setClientDataHost(clientHost);
317b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.openDataConnection();
318b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.sendData(DATA.getBytes(), DATA.length());
319b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        LOG.info("output=[" + outputStream.toString() + "]");
320b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("output", DATA, outputStream.toString());
321b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
322b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
323b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
324b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the SendData() method, passing in a null byte[]
325b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
326b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testSendData_Null() {
327b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
328b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        try {
329b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            session.sendData(null, 1);
330b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            fail("Expected AssertFailedException");
331b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
332b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        catch (AssertFailedException expected) {
333b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            LOG.info("Expected: " + expected);
334b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
335b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
336b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
337b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
338b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the SendReply(int,String) method, passing in an invalid reply code
339b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
340b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testSendReply_InvalidReplyCode() {
341b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
342b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        try {
343b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            session.sendReply(-66, "text");
344b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            fail("Expected AssertFailedException");
345b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
346b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        catch (AssertFailedException expected) {
347b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            LOG.info("Expected: " + expected);
348b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
349b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
350b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
351b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
352b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the getAttribute() and setAttribute() methods
353b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
354b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testGetAndSetAttribute() {
355b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertNull("name does not exist yet", session.getAttribute(NAME1));
356b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.setAttribute(NAME1, VALUE);
357b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.setAttribute(NAME2, null);
358b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("NAME1", VALUE, session.getAttribute(NAME1));
359b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertNull("NAME2", session.getAttribute(NAME2));
360b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertNull("no such name", session.getAttribute("noSuchName"));
361b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
362b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
363b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
364b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the getAttribute() method, passing in a null name
365b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
366b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testGetAttribute_Null() {
367b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        try {
368b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            session.getAttribute(null);
369b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            fail("Expected AssertFailedException");
370b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
371b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        catch (AssertFailedException expected) {
372b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            LOG.info("Expected: " + expected);
373b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
374b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
375b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
376b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
377b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the setAttribute() method, passing in a null name
378b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
379b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testSetAttribute_NullName() {
380b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        try {
381b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            session.setAttribute(null, VALUE);
382b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            fail("Expected AssertFailedException");
383b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
384b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        catch (AssertFailedException expected) {
385b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            LOG.info("Expected: " + expected);
386b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
387b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
388b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
389b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
390b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the removeAttribute()
391b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
392b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testRemoveAttribute() {
393b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.removeAttribute("noSuchName");      // do nothing
394b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.setAttribute(NAME1, VALUE);
395b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.removeAttribute(NAME1);
396b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertNull("NAME1", session.getAttribute(NAME1));
397b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
398b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
399b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
400b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the removeAttribute() method, passing in a null name
401b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
402b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testRemoveAttribute_Null() {
403b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        try {
404b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            session.removeAttribute(null);
405b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            fail("Expected AssertFailedException");
406b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
407b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        catch (AssertFailedException expected) {
408b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair            LOG.info("Expected: " + expected);
409b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        }
410b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
411b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
412b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
413b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Test the getAttributeNames()
414b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
415b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    public void testGetAttributeNames() {
416b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("No names yet", Collections.EMPTY_SET, session.getAttributeNames());
417b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.setAttribute(NAME1, VALUE);
418b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("1", Collections.singleton(NAME1), session.getAttributeNames());
419b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        session.setAttribute(NAME2, VALUE);
420b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        assertEquals("2", set(NAME1, NAME2), session.getAttributeNames());
421b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
422b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
423b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    // -------------------------------------------------------------------------
424b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    // Internal Helper Methods
425b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    // -------------------------------------------------------------------------
426b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
427b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
428b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Create and return a DefaultSession object that reads from an InputStream with the specified
429b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * contents and writes to the predefined outputStrean ByteArrayOutputStream. Also, save the
430b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * StubSocket being used in the stubSocket attribute.
431b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     *
432b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * @param inputStreamContents - the contents of the input stream
433b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * @return the DefaultSession
434b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
435b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    private DefaultSession createDefaultSession(String inputStreamContents) {
436b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        stubSocket = createTestSocket(inputStreamContents);
437b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        return new DefaultSession(stubSocket, commandHandlerMap);
438b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
439b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
440b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    /**
441b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * Create and return a StubSocket that reads from an InputStream with the specified contents and
442b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * writes to the predefined outputStrean ByteArrayOutputStream.
443b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     *
444b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * @param inputStreamContents - the contents of the input stream
445b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     * @return the StubSocket
446b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair     */
447b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    private StubSocket createTestSocket(String inputStreamContents) {
448b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        InputStream inputStream = new ByteArrayInputStream(inputStreamContents.getBytes());
449b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        StubSocket stubSocket = new StubSocket(inputStream, outputStream);
450b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        stubSocket._setLocalAddress(DEFAULT_HOST);
451b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair        return stubSocket;
452b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair    }
453b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair
454b2f4a2dfc590c250e42b21eb40d9539ac135b495chrismair}
455