blob: 6986b2f054fb05d93cff6335027c9dc98c2d62ec [file] [log] [blame]
/*
* 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 <unistd.h>
#include <mutex>
#include <condition_variable>
#include <hidl/HidlTransportSupport.h>
#include <hidl/ServiceManagement.h>
#include <hidl/Status.h>
#include <hardware/hardware.h>
#include <utils/Thread.h>
#include <utils/Errors.h>
#include <utils/StrongPointer.h>
#include <log/log.h>
#include <android/hardware/lowpan/1.0/ILowpanDevice.h>
#include <android/hidl/manager/1.0/IServiceManager.h>
#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<std::mutex> lock(mMutex);
if (mOpenError == -1) {
mConditionVariable.wait(lock);
}
return mOpenError;
}
Return<void> onReceiveFrame(const hidl_vec<uint8_t>& data) override {
if (data.size() > kMaxFrameSize) {
ALOGE("TOOBIG: Frame received from device is too big");
return Return<void>();
}
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<std::mutex> lock(mMutex);
if (write(mFd, buffer, bufferIndex) != bufferIndex) {
ALOGE("IOFAIL: write: %s (%d)", strerror(errno), errno);
exit(EXIT_FAILURE);
}
return Return<void>();
}
Return<void> onEvent(LowpanEvent event, LowpanStatus status) override {
std::unique_lock<std::mutex> 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<void>();
}
};
class ReadThread : public Thread {
int kReadThreadBufferSize;
sp<ILowpanDevice> mService;
int mFd;
uint8_t mBuffer[LOWPAN_HDLC_ADAPTER_MAX_FRAME_SIZE];
int mBufferIndex;
bool mUnescapeNextByte;
uint16_t mFcs;
sp<LowpanDeviceCallback> mCallback;
public:
ReadThread(sp<ILowpanDevice> service, int fd, sp<LowpanDeviceCallback> 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<uint8_t> 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<ILowpanDevice> 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<LowpanDeviceCallback> 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<Thread> readThread = new ReadThread(service, STDIN_FILENO, callback);
readThread->run("ReadThread");
joinRpcThreadpool();
ALOGI("Shutting down");
return EXIT_SUCCESS;
}