/* * Copyright 2013, 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. */ //#define LOG_NEBUG 0 #define LOG_TAG "nettest" #include #include "ANetworkSession.h" #include "TimeSyncer.h" #include #include #include #include #include #include #include #include #include #include #include namespace android { struct TestHandler : public AHandler { TestHandler(const sp &netSession); void listen(int32_t port); void connect(const char *host, int32_t port); protected: virtual ~TestHandler(); virtual void onMessageReceived(const sp &msg); private: enum { kTimeSyncerPort = 8123, }; enum { kWhatListen, kWhatConnect, kWhatTimeSyncerNotify, kWhatNetNotify, kWhatSendMore, kWhatStop, }; sp mNetSession; sp mTimeSyncer; int32_t mServerSessionID; int32_t mSessionID; int64_t mTimeOffsetUs; bool mTimeOffsetValid; int32_t mCounter; int64_t mMaxDelayMs; void dumpDelay(int32_t counter, int64_t delayMs); DISALLOW_EVIL_CONSTRUCTORS(TestHandler); }; TestHandler::TestHandler(const sp &netSession) : mNetSession(netSession), mServerSessionID(0), mSessionID(0), mTimeOffsetUs(-1ll), mTimeOffsetValid(false), mCounter(0), mMaxDelayMs(-1ll) { } TestHandler::~TestHandler() { } void TestHandler::listen(int32_t port) { sp msg = new AMessage(kWhatListen, id()); msg->setInt32("port", port); msg->post(); } void TestHandler::connect(const char *host, int32_t port) { sp msg = new AMessage(kWhatConnect, id()); msg->setString("host", host); msg->setInt32("port", port); msg->post(); } void TestHandler::dumpDelay(int32_t counter, int64_t delayMs) { static const int64_t kMinDelayMs = 0; static const int64_t kMaxDelayMs = 300; const char *kPattern = "########################################"; size_t kPatternSize = strlen(kPattern); int n = (kPatternSize * (delayMs - kMinDelayMs)) / (kMaxDelayMs - kMinDelayMs); if (n < 0) { n = 0; } else if ((size_t)n > kPatternSize) { n = kPatternSize; } if (delayMs > mMaxDelayMs) { mMaxDelayMs = delayMs; } ALOGI("[%d] (%4lld ms / %4lld ms) %s", counter, delayMs, mMaxDelayMs, kPattern + kPatternSize - n); } void TestHandler::onMessageReceived(const sp &msg) { switch (msg->what()) { case kWhatListen: { sp notify = new AMessage(kWhatTimeSyncerNotify, id()); mTimeSyncer = new TimeSyncer(mNetSession, notify); looper()->registerHandler(mTimeSyncer); notify = new AMessage(kWhatNetNotify, id()); int32_t port; CHECK(msg->findInt32("port", &port)); struct in_addr ifaceAddr; ifaceAddr.s_addr = INADDR_ANY; CHECK_EQ((status_t)OK, mNetSession->createTCPDatagramSession( ifaceAddr, port, notify, &mServerSessionID)); break; } case kWhatConnect: { sp notify = new AMessage(kWhatTimeSyncerNotify, id()); mTimeSyncer = new TimeSyncer(mNetSession, notify); looper()->registerHandler(mTimeSyncer); mTimeSyncer->startServer(kTimeSyncerPort); AString host; CHECK(msg->findString("host", &host)); int32_t port; CHECK(msg->findInt32("port", &port)); notify = new AMessage(kWhatNetNotify, id()); CHECK_EQ((status_t)OK, mNetSession->createTCPDatagramSession( 0 /* localPort */, host.c_str(), port, notify, &mSessionID)); break; } case kWhatNetNotify: { int32_t reason; CHECK(msg->findInt32("reason", &reason)); switch (reason) { case ANetworkSession::kWhatConnected: { ALOGI("kWhatConnected"); (new AMessage(kWhatSendMore, id()))->post(); break; } case ANetworkSession::kWhatClientConnected: { ALOGI("kWhatClientConnected"); CHECK_EQ(mSessionID, 0); CHECK(msg->findInt32("sessionID", &mSessionID)); AString clientIP; CHECK(msg->findString("client-ip", &clientIP)); mTimeSyncer->startClient(clientIP.c_str(), kTimeSyncerPort); break; } case ANetworkSession::kWhatDatagram: { sp packet; CHECK(msg->findBuffer("data", &packet)); CHECK_EQ(packet->size(), 12u); int32_t counter = U32_AT(packet->data()); int64_t timeUs = U64_AT(packet->data() + 4); if (mTimeOffsetValid) { timeUs -= mTimeOffsetUs; int64_t nowUs = ALooper::GetNowUs(); int64_t delayMs = (nowUs - timeUs) / 1000ll; dumpDelay(counter, delayMs); } else { ALOGI("received %d", counter); } break; } case ANetworkSession::kWhatError: { ALOGE("kWhatError"); break; } default: TRESPASS(); } break; } case kWhatTimeSyncerNotify: { CHECK(msg->findInt64("offset", &mTimeOffsetUs)); mTimeOffsetValid = true; break; } case kWhatSendMore: { uint8_t buffer[4 + 8]; buffer[0] = mCounter >> 24; buffer[1] = (mCounter >> 16) & 0xff; buffer[2] = (mCounter >> 8) & 0xff; buffer[3] = mCounter & 0xff; int64_t nowUs = ALooper::GetNowUs(); buffer[4] = nowUs >> 56; buffer[5] = (nowUs >> 48) & 0xff; buffer[6] = (nowUs >> 40) & 0xff; buffer[7] = (nowUs >> 32) & 0xff; buffer[8] = (nowUs >> 24) & 0xff; buffer[9] = (nowUs >> 16) & 0xff; buffer[10] = (nowUs >> 8) & 0xff; buffer[11] = nowUs & 0xff; ++mCounter; CHECK_EQ((status_t)OK, mNetSession->sendRequest( mSessionID, buffer, sizeof(buffer), true /* timeValid */, nowUs)); msg->post(100000ll); break; } case kWhatStop: { if (mSessionID != 0) { mNetSession->destroySession(mSessionID); mSessionID = 0; } if (mServerSessionID != 0) { mNetSession->destroySession(mServerSessionID); mServerSessionID = 0; } looper()->stop(); break; } default: TRESPASS(); } } } // namespace android static void usage(const char *me) { fprintf(stderr, "usage: %s -c host:port\tconnect to remote host\n" " -l port \tlisten\n", me); } int main(int argc, char **argv) { using namespace android; // srand(time(NULL)); ProcessState::self()->startThreadPool(); DataSource::RegisterDefaultSniffers(); int32_t connectToPort = -1; AString connectToHost; int32_t listenOnPort = -1; int res; while ((res = getopt(argc, argv, "hc:l:")) >= 0) { switch (res) { case 'c': { const char *colonPos = strrchr(optarg, ':'); if (colonPos == NULL) { usage(argv[0]); exit(1); } connectToHost.setTo(optarg, colonPos - optarg); char *end; connectToPort = strtol(colonPos + 1, &end, 10); if (*end != '\0' || end == colonPos + 1 || connectToPort < 0 || connectToPort > 65535) { fprintf(stderr, "Illegal port specified.\n"); exit(1); } break; } case 'l': { char *end; listenOnPort = strtol(optarg, &end, 10); if (*end != '\0' || end == optarg || listenOnPort < 0 || listenOnPort > 65535) { fprintf(stderr, "Illegal port specified.\n"); exit(1); } break; } case '?': case 'h': usage(argv[0]); exit(1); } } if ((listenOnPort < 0 && connectToPort < 0) || (listenOnPort >= 0 && connectToPort >= 0)) { fprintf(stderr, "You need to select either client or server mode.\n"); exit(1); } sp netSession = new ANetworkSession; netSession->start(); sp looper = new ALooper; sp handler = new TestHandler(netSession); looper->registerHandler(handler); if (listenOnPort) { handler->listen(listenOnPort); } if (connectToPort >= 0) { handler->connect(connectToHost.c_str(), connectToPort); } looper->start(true /* runOnCallingThread */); return 0; }