Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5dd8c5cf authored by Joseph Pirozzo's avatar Joseph Pirozzo
Browse files

MapClientService Obex Unit tests

Create a framework for unit testing obex operations for use in
validating obex requests againgst a fake obex server.
The tests added here verify normal operation of several get and put
requests.

Bug: 190851901
Test: atest BluetoothInstrumentationTests
Tag: #stability
Change-Id: Iddfe6727f57d9b28ab365e7606932ac7a6913899
parent 313e8806
Loading
Loading
Loading
Loading
+265 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.bluetooth.mapclient;

import static com.google.common.truth.Truth.assertThat;

import androidx.test.filters.MediumTest;
import androidx.test.runner.AndroidJUnit4;

import com.android.bluetooth.FakeObexServer;
import com.android.bluetooth.map.BluetoothMapAppParams;
import com.android.bluetooth.map.BluetoothMapFolderElement;
import com.android.bluetooth.mapclient.RequestSetMessageStatus.StatusIndicator;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.io.IOException;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;

import javax.obex.ClientSession;
import javax.obex.HeaderSet;
import javax.obex.Operation;
import javax.obex.ResponseCodes;

@MediumTest
@RunWith(AndroidJUnit4.class)
public class RequestTest {

    private static final String SIMPLE_MMS_MESSAGE =
            "BEGIN:BMSG\r\nVERSION:1.0\r\nSTATUS:READ\r\nTYPE:MMS\r\nFOLDER:null\r\nBEGIN:BENV\r\n"
                    + "BEGIN:VCARD\r\nVERSION:2.1\r\nN:null;;;;\r\nTEL:555-5555\r\nEND:VCARD\r\n"
                    + "BEGIN:BBODY\r\nLENGTH:39\r\nBEGIN:MSG\r\nThis is a new msg\r\nEND:MSG\r\n"
                    + "END:BBODY\r\nEND:BENV\r\nEND:BMSG\r\n";

    private static final String TYPE_GET_FOLDER_LISTING = "x-obex/folder-listing";
    private static final String TYPE_GET_MESSAGE_LISTING = "x-bt/MAP-msg-listing";
    private static final String TYPE_MESSAGE = "x-bt/message";
    private static final String TYPE_SET_MESSAGE_STATUS = "x-bt/messageStatus";
    private static final String TYPE_SET_NOTIFICATION_REGISTRATION =
            "x-bt/MAP-NotificationRegistration";


    private static final String HANDLE = "0000001";

    private static final Bmessage TEST_MESSAGE = BmessageParser.createBmessage(SIMPLE_MMS_MESSAGE);
    private static final ArrayList<String> TEST_FOLDER_LIST = new ArrayList<String>(
            Arrays.asList("folder1"));
    private static final ArrayList<Message> TEST_MESSAGE_LIST = new ArrayList<Message>();
    private static final Date TEST_TIME = new Date();
    private static final byte TEST_STATUS_INDICATOR = Request.STATUS_INDICATOR_READ;
    private static final byte TEST_STATUS_VALUE = Request.STATUS_YES;

    private ClientSession mFakeClientSession;
    private FakeObexServer mFakeMapObexServer;

    @Before
    public void setUp() throws IOException {
        mFakeMapObexServer = new FakeMapObexServer();
        mFakeClientSession = new ClientSession(mFakeMapObexServer.mClientObexTransport);
        mFakeClientSession.connect(new HeaderSet());
    }

    @Test
    public void testRequestGetMessagesListing() throws IOException {
        RequestGetMessagesListing newRequest = new RequestGetMessagesListing(
                TEST_FOLDER_LIST.get(0), /*parameters*/ 0, /*filter*/ null, /*subjectLength*/ 0,
                /*maxListCount*/ 0, /*listStartOffset*/ 0);
        assertThat(newRequest).isNotNull();
        newRequest.execute(mFakeClientSession);

        assertThat(newRequest.isSuccess()).isTrue();
        assertThat(newRequest.getList().toString()).isEqualTo(TEST_MESSAGE_LIST.toString());
        assertThat(newRequest.getNewMessageStatus()).isTrue();
        assertThat(newRequest.getMseTime().toString()).isEqualTo(TEST_TIME.toString());
    }

    @Test
    public void testRequestGetMessage() throws IOException {
        RequestGetMessage newRequest = new RequestGetMessage(HANDLE, MasClient.CharsetType.UTF_8,
                /*attachment*/ false);
        assertThat(newRequest).isNotNull();
        assertThat(newRequest.getHandle()).isEqualTo(HANDLE);
        newRequest.execute(mFakeClientSession);

        assertThat(newRequest.isSuccess()).isTrue();
        assertThat(newRequest.getMessage().toString()).isEqualTo(TEST_MESSAGE.toString());
        assertThat(newRequest.getHandle()).isEqualTo(HANDLE);
    }

    @Test
    public void testRequestGetFolderListing() throws IOException {
        RequestGetFolderListing newRequest = new RequestGetFolderListing(/*maxListCount*/ 255,
                /*listStartOffset*/ 0);
        assertThat(newRequest).isNotNull();
        newRequest.execute(mFakeClientSession);

        assertThat(newRequest.isSuccess()).isTrue();
        assertThat(newRequest.getList().toString()).isEqualTo(TEST_FOLDER_LIST.toString());
    }

    @Test
    public void testRequestPushMessage() throws IOException {
        RequestPushMessage newRequest = new RequestPushMessage(TEST_FOLDER_LIST.get(0),
                TEST_MESSAGE, /*charset*/ null, /*transparent*/ false, /*retry*/ false);
        assertThat(newRequest).isNotNull();
        assertThat(newRequest.getMsgHandle()).isEqualTo(null);
        newRequest.execute(mFakeClientSession);

        assertThat(newRequest.isSuccess()).isTrue();
        assertThat(newRequest.getBMsg().toString()).isEqualTo(TEST_MESSAGE.toString());
        assertThat(newRequest.getMsgHandle()).isEqualTo(HANDLE);
    }

    @Test
    public void testRequestSetMessageStatus() throws IOException {
        RequestSetMessageStatus newRequest = new RequestSetMessageStatus(HANDLE,
                StatusIndicator.READ, TEST_STATUS_VALUE);
        assertThat(newRequest).isNotNull();
        assertThat(newRequest.getHandle()).isEqualTo(HANDLE);
        newRequest.execute(mFakeClientSession);

        assertThat(newRequest.isSuccess()).isTrue();
        assertThat(newRequest.getValue()).isEqualTo(TEST_STATUS_VALUE);
        assertThat(newRequest.getHandle()).isEqualTo(HANDLE);
    }

    @Test
    public void testRequestSetNotificationRegistration() throws IOException {
        RequestSetNotificationRegistration newRequest = new RequestSetNotificationRegistration(
                /*status*/ true);
        assertThat(newRequest).isNotNull();
        newRequest.execute(mFakeClientSession);

        assertThat(newRequest.getStatus()).isTrue();
    }

    @Test
    public void testRequestSetPath() throws IOException {
        RequestSetPath newRequest = new RequestSetPath(TEST_FOLDER_LIST.get(0));
        assertThat(newRequest).isNotNull();
        newRequest.execute(mFakeClientSession);

        assertThat(newRequest.isSuccess()).isTrue();
    }

    static class FakeMapObexServer extends FakeObexServer {

        FakeMapObexServer() throws IOException {
            super();
        }

        @Override
        public int onGetValidator(final Operation op) {
            OutputStream outputStream;
            HeaderSet replyHeaders = new HeaderSet();
            BluetoothMapAppParams outAppParams = new BluetoothMapAppParams();
            try {

                HeaderSet request = op.getReceivedHeader();
                String type = (String) request.getHeader(HeaderSet.TYPE);
                switch (type) {
                    case TYPE_GET_FOLDER_LISTING:
                        op.sendHeaders(replyHeaders);
                        outputStream = op.openOutputStream();
                        BluetoothMapFolderElement root =
                                new BluetoothMapFolderElement(/*name*/ "root", /*parent*/ null);
                        root.addFolder("Folder1");
                        outputStream.write(root.encode(/*offset*/ 0, /*count*/ 1));
                        outputStream.close();
                        return ResponseCodes.OBEX_HTTP_OK;

                    case TYPE_MESSAGE:
                        op.sendHeaders(replyHeaders);
                        outputStream = op.openOutputStream();
                        outputStream.write(SIMPLE_MMS_MESSAGE.getBytes());
                        outputStream.close();
                        return ResponseCodes.OBEX_HTTP_OK;

                    case TYPE_GET_MESSAGE_LISTING:
                        outAppParams.setNewMessage(1);
                        outAppParams.setMseTime(TEST_TIME.getTime());
                        replyHeaders.setHeader(HeaderSet.APPLICATION_PARAMETER,
                                outAppParams.encodeParams());
                        op.sendHeaders(replyHeaders);
                        return ResponseCodes.OBEX_HTTP_OK;
                }
            } catch (Exception e) {
                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
            }
            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
        }

        @Override
        public int onPutValidator(final Operation op) {
            try {
                HeaderSet request = op.getReceivedHeader();
                String type = (String) request.getHeader(HeaderSet.TYPE);
                byte[] appParamRaw = (byte[]) request.getHeader(HeaderSet.APPLICATION_PARAMETER);
                BluetoothMapAppParams appParams;
                if (appParamRaw != null) {
                    appParams = new BluetoothMapAppParams(appParamRaw);
                } else {
                    return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
                }
                switch (type) {
                    case TYPE_SET_MESSAGE_STATUS:
                        if (appParams.getStatusIndicator() != TEST_STATUS_INDICATOR) {
                            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
                        }
                        if (appParams.getStatusValue() != TEST_STATUS_VALUE) {
                            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
                        }
                        return ResponseCodes.OBEX_HTTP_OK;

                    case TYPE_SET_NOTIFICATION_REGISTRATION:
                        if (appParams.getNotificationStatus() != 1) {
                            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
                        }
                        return ResponseCodes.OBEX_HTTP_OK;

                    case TYPE_MESSAGE:
                        HeaderSet replyHeaders = new HeaderSet();
                        replyHeaders.setHeader(HeaderSet.NAME, HANDLE);
                        op.sendHeaders(replyHeaders);
                        return ResponseCodes.OBEX_HTTP_OK;
                }
            } catch (Exception e) {
                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
            }
            return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
        }

        @Override
        public int onSetPathValidator(final HeaderSet request, HeaderSet reply,
                final boolean backup,
                final boolean create) {
            try {
                String tmpPath = (String) request.getHeader(HeaderSet.NAME);
                assertThat(tmpPath).isEqualTo(TEST_FOLDER_LIST.get(0));
                return ResponseCodes.OBEX_HTTP_OK;
            } catch (Exception e) {
                return ResponseCodes.OBEX_HTTP_BAD_REQUEST;
            }
        }
    }
}
+94 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.bluetooth;

import java.io.IOException;

import javax.obex.HeaderSet;
import javax.obex.ObexTransport;
import javax.obex.Operation;
import javax.obex.ResponseCodes;
import javax.obex.ServerRequestHandler;
import javax.obex.ServerSession;

/**
 * A fake obex server for testing obex clients. Test cases should implement *Validator functions to
 * validate input and return appropriate responses for individual tests.
 *
 * Note: it is important to not perform any testing Assert operations within the validators as that
 * would crash the testing framework.
 */
public abstract class FakeObexServer {

    public ObexTransport mClientObexTransport;

    private ObexTransport mServerObexTransport;
    private Server mFakeServer;
    private FakeObexTransport mFakeObexTransport;

    public FakeObexServer() throws IOException {
        mFakeServer = new Server();
        mFakeObexTransport = new FakeObexTransport();
        mServerObexTransport = mFakeObexTransport.mServerTransport;
        mClientObexTransport = mFakeObexTransport.mClientTransport;
        new ServerSession(mServerObexTransport, mFakeServer, null);
    }

    public abstract int onGetValidator(Operation op);

    public abstract int onPutValidator(Operation op);

    public abstract int onSetPathValidator(HeaderSet request, HeaderSet reply,
            boolean backup, boolean create);

    class Server extends ServerRequestHandler {

        @Override
        public int onConnect(final HeaderSet request, HeaderSet reply) {
            return ResponseCodes.OBEX_HTTP_OK;
        }

        @Override
        public void onDisconnect(final HeaderSet request, HeaderSet reply) {
        }

        @Override
        public int onGet(final Operation op) {
            return onGetValidator(op);
        }

        @Override
        public int onPut(final Operation op) {
            return onPutValidator(op);
        }

        @Override
        public int onAbort(final HeaderSet request, HeaderSet reply) {
            return ResponseCodes.OBEX_HTTP_NOT_IMPLEMENTED;
        }

        @Override
        public int onSetPath(final HeaderSet request, HeaderSet reply, final boolean backup,
                final boolean create) {
            return onSetPathValidator(request, reply, backup, create);
        }

        @Override
        public void onClose() {
        }
    }
}
+122 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.bluetooth;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;

import javax.obex.ObexTransport;

/**
 * A fake obex transport used for testing Client/Server connections.
 * The transport uses two pairs of pipes to route input from the client to the server, and back.
 * The obex transport is of the simplest form, returning default values for everything.
 */
public class FakeObexTransport {
    ObexTransport mClientTransport;
    ObexTransport mServerTransport;

    PipedInputStream mClientInputStream;
    PipedInputStream mServerInputStream;
    PipedOutputStream mClientOutputStream;
    PipedOutputStream mServerOutputStream;

    public FakeObexTransport() throws IOException {
        mClientInputStream = new PipedInputStream();
        mServerOutputStream = new PipedOutputStream(mClientInputStream);
        mServerInputStream = new PipedInputStream();
        mClientOutputStream = new PipedOutputStream(mServerInputStream);

        mClientTransport = new BiDirectionalTransport(mClientInputStream, mClientOutputStream);
        mServerTransport = new BiDirectionalTransport(mServerInputStream, mServerOutputStream);
    }

    static class BiDirectionalTransport implements ObexTransport {

        InputStream mInputStream;
        OutputStream mOutputStream;

        BiDirectionalTransport(InputStream inputStream, OutputStream outputStream) {
            mInputStream = inputStream;
            mOutputStream = outputStream;
        }

        @Override
        public DataInputStream openDataInputStream() throws IOException {
            return new DataInputStream(openInputStream());
        }

        @Override
        public DataOutputStream openDataOutputStream() throws IOException {
            return new DataOutputStream(openOutputStream());
        }

        @Override
        public InputStream openInputStream() throws IOException {
            return mInputStream;
        }

        @Override
        public OutputStream openOutputStream() throws IOException {
            return mOutputStream;
        }

        @Override
        public void connect() throws IOException {
        }

        @Override
        public void create() throws IOException {
        }

        @Override
        public void disconnect() throws IOException {
        }

        @Override
        public void listen() throws IOException {
        }

        @Override
        public void close() throws IOException {
        }

        public boolean isConnected() throws IOException {
            return true;
        }

        @Override
        public int getMaxTransmitPacketSize() {
            return -1;
        }

        @Override
        public int getMaxReceivePacketSize() {
            return -1;
        }

        @Override
        public boolean isSrmSupported() {
            return true;
        }
    }
}