/* * Copyright (C) 2017 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_TAG "lowpan-hdlc-adapter" #include "hdlc_lite.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #define LOWPAN_HDLC_ADAPTER_MAX_FRAME_SIZE 2048 using ::android::hardware::Return; using ::android::hardware::Void; using ::android::hardware::configureRpcThreadpool; using ::android::hardware::hidl_death_recipient; using ::android::hardware::hidl_string; using ::android::hardware::hidl_vec; using ::android::hardware::joinRpcThreadpool; using ::android::sp; using namespace ::android::hardware::lowpan::V1_0; using namespace ::android; struct LowpanDeathRecipient : hidl_death_recipient { LowpanDeathRecipient() = default; virtual void serviceDied(uint64_t /*cookie*/, const wp<::android::hidl::base::V1_0::IBase>& /*who*/) { ALOGE("LowpanDevice died"); exit(EXIT_FAILURE); } }; struct LowpanDeviceCallback : public ILowpanDeviceCallback { int mFd; std::mutex mMutex; std::condition_variable mConditionVariable; int mOpenError; static const uint32_t kMaxFrameSize = LOWPAN_HDLC_ADAPTER_MAX_FRAME_SIZE; public: LowpanDeviceCallback(int fd): mFd(fd), mOpenError(-1) {} virtual ~LowpanDeviceCallback() = default; int waitForOpenStatus() { std::unique_lock lock(mMutex); if (mOpenError == -1) { mConditionVariable.wait(lock); } return mOpenError; } Return onReceiveFrame(const hidl_vec& data) override { if (data.size() > kMaxFrameSize) { ALOGE("TOOBIG: Frame received from device is too big"); return Return(); } int bufferIndex = 0; uint16_t fcs = kHdlcCrcResetValue; uint8_t buffer[kMaxFrameSize*2 + 5]; // every character escaped, escaped crc, and frame marker uint8_t c; for (size_t i = 0; i < data.size(); i++) { c = data[i]; fcs = hdlc_crc16(fcs, c); bufferIndex += hdlc_write_byte(buffer + bufferIndex, c); } fcs = hdlc_crc16_finalize(fcs); bufferIndex += hdlc_write_byte(buffer + bufferIndex, uint8_t(fcs & 0xFF)); bufferIndex += hdlc_write_byte(buffer + bufferIndex, uint8_t((fcs >> 8) & 0xFF)); buffer[bufferIndex++] = HDLC_BYTE_FLAG; std::unique_lock lock(mMutex); if (write(mFd, buffer, bufferIndex) != bufferIndex) { ALOGE("IOFAIL: write: %s (%d)", strerror(errno), errno); exit(EXIT_FAILURE); } return Return(); } Return onEvent(LowpanEvent event, LowpanStatus status) override { std::unique_lock lock(mMutex); switch (event) { case LowpanEvent::OPENED: if (mOpenError == -1) { mOpenError = 0; mConditionVariable.notify_all(); } ALOGI("Device opened"); break; case LowpanEvent::CLOSED: ALOGI("Device closed"); exit(EXIT_SUCCESS); break; case LowpanEvent::RESET: ALOGI("Device reset"); break; case LowpanEvent::ERROR: if (mOpenError == -1) { mOpenError = int(status); mConditionVariable.notify_all(); } switch (status) { case LowpanStatus::IOFAIL: ALOGE("IOFAIL: Input/Output error from device. Terminating."); exit(EXIT_FAILURE); break; case LowpanStatus::GARBAGE: ALOGW("GARBAGE: Bad frame from device."); break; case LowpanStatus::TOOBIG: ALOGW("TOOBIG: Device sending frames that are too large."); break; default: ALOGW("Unknown error %d", status); break; } break; } return Return(); } }; class ReadThread : public Thread { int kReadThreadBufferSize; sp mService; int mFd; uint8_t mBuffer[LOWPAN_HDLC_ADAPTER_MAX_FRAME_SIZE]; int mBufferIndex; bool mUnescapeNextByte; uint16_t mFcs; sp mCallback; public: ReadThread(sp service, int fd, sp callback): Thread(false /*canCallJava*/), kReadThreadBufferSize(service->getMaxFrameSize()), mService(service), mFd(fd), mBufferIndex(0), mUnescapeNextByte(false), mFcs(kHdlcCrcResetValue), mCallback(callback) { if (kReadThreadBufferSize < 16) { ALOGE("Device returned bad max frame size: %d bytes", kReadThreadBufferSize); exit(EXIT_FAILURE); } if ((size_t)kReadThreadBufferSize > sizeof(mBuffer)) { kReadThreadBufferSize = (int)sizeof(mBuffer); } } virtual ~ReadThread() {} private: bool threadLoop() override { uint8_t buffer[LOWPAN_HDLC_ADAPTER_MAX_FRAME_SIZE]; if (int error = mCallback->waitForOpenStatus()) { ALOGE("Call to `open()` failed: %d", error); exit(EXIT_FAILURE); } while (!exitPending()) { ssize_t bytesRead = read(mFd, buffer, sizeof(buffer)); if (exitPending()) { break; } if (bytesRead < 0) { ALOGE("IOFAIL: read: %s (%d)", strerror(errno), errno); exit(EXIT_FAILURE); break; } feedBytes(buffer, bytesRead); } return false; } void feedBytes(const uint8_t* dataPtr, ssize_t dataLen) { while(dataLen--) { feedByte(*dataPtr++); } } void sendFrame(uint8_t* p_data, uint16_t data_len) { hidl_vec data; data.setToExternal(p_data, data_len); mService->sendFrame(data); } void feedByte(uint8_t byte) { if (mBufferIndex >= kReadThreadBufferSize) { ALOGE("TOOBIG: HDLC frame too big (Max: %d)", kReadThreadBufferSize); mUnescapeNextByte = false; mBufferIndex = 0; mFcs = kHdlcCrcResetValue; } else if (byte == HDLC_BYTE_FLAG) { if (mBufferIndex <= 2) { // Ignore really small frames. // Don't remove this or we will underflow our // index for onReceiveFrame(), below! } else if (mUnescapeNextByte || (mFcs != kHdlcCrcCheckValue)) { ALOGE("GARBAGE: HDLC frame with bad CRC (LEN:%d, mFcs:0x%04X)", mBufferIndex, mFcs); } else { // -2 for CRC sendFrame(mBuffer, uint16_t(mBufferIndex - 2)); } mUnescapeNextByte = false; mBufferIndex = 0; mFcs = kHdlcCrcResetValue; } else if (byte == HDLC_BYTE_ESC) { mUnescapeNextByte = true; } else if (hdlc_byte_needs_escape(byte)) { // Skip all other control codes. } else { if (mUnescapeNextByte) { byte = byte ^ HDLC_ESCAPE_XFORM; mUnescapeNextByte = false; } mFcs = hdlc_crc16(mFcs, byte); mBuffer[mBufferIndex++] = byte; } } }; int main(int argc, char* argv []) { using ::android::hardware::defaultServiceManager; using Transport = ::android::hidl::manager::V1_0::IServiceManager::Transport; const char* serviceName = "default"; if (argc >= 2) { serviceName = argv[1]; } sp service = ILowpanDevice::getService(serviceName, false /* getStub */); if (service == nullptr) { ALOGE("Unable to find LowpanDevice named \"%s\"", serviceName); exit(EXIT_FAILURE); } service->linkToDeath(new LowpanDeathRecipient(), 0 /*cookie*/); configureRpcThreadpool(1, true /* callerWillJoin */); sp callback = new LowpanDeviceCallback(STDOUT_FILENO); { auto status = service->open(callback); if (status.isOk()) { if (status == LowpanStatus::OK) { ALOGD("%s: open() ok.", serviceName); } else { ALOGE("%s: open() failed: (%d).", serviceName, LowpanStatus(status)); exit(EXIT_FAILURE); } } else { ALOGE("%s: open() failed: transport error", serviceName); exit(EXIT_FAILURE); } } sp readThread = new ReadThread(service, STDIN_FILENO, callback); readThread->run("ReadThread"); joinRpcThreadpool(); ALOGI("Shutting down"); return EXIT_SUCCESS; }