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