This commit is contained in:
CismonX 2018-05-28 22:02:26 +08:00
commit 8d8dd74bc6
Signed by: cismonx
GPG Key ID: 3094873E29A482FB
54 changed files with 5382 additions and 0 deletions

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2018 CismonX
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

26
README.md Normal file
View File

@ -0,0 +1,26 @@
# BBB-Simple-ACS
Simple access control system using Beaglebone Black.
## 1. Documentation
### 1.1 Requirements
* Beaglebone Black with official Debian installed.
* RFID-RC522 module using SPI bus.
* OLED screen driven by SSD1306 using SPI bus (with an extra D/C pin).
* Internet connection.
* A server.
### 1.2 Dependencies (client)
* The Boost C++ Libraries (v1.62)
* Boost.Beast (v124 release)
* The mysql++ library.
* The qrencode library.
* The Boost helper JSON header.
### 1.3 Notes
* Build and install client/lib first before building the client.
* The server code may no longer work since some of dependencies are removed from Packagist. Luckily, the server code is simple enough to be easily rewritten using any PHP web framework.

16
client/Makefile Normal file
View File

@ -0,0 +1,16 @@
SOURCES = $(wildcard *.cpp)
OBJECTS = $(SOURCES:%.cpp=%.o)
APPLICATION = acs
CXXFLAGS = -Wall -c -g -O0 -I/usr/include/mysql -std=c++14 -o
LDFLAGS = -lacsdriver -lboost_system -lpthread -lmysqlpp -lqrencode
all: ${OBJECTS} ${APPLICATION}
${APPLICATION}: ${OBJECTS}
${CXX} -o $@ ${OBJECTS} ${LDFLAGS}
${OBJECTS}:
${CXX} ${CXXFLAGS} $@ ${@:%.o=%.cpp}
clean:
rm -f ${APPLICATION} ${OBJECTS}

View File

@ -0,0 +1,50 @@
#include "access_controller.hpp"
#include "factory.hpp"
namespace acs
{
access_controller::access_controller() : state_(
factory::get()->get_state()), extra_gpio_(
factory::get()->get_devices()->extra()), reader_(
factory::get()->get_reader()), writer_(
factory::get()->get_writer()), timer_(
factory::get()->get_await_timer()) {}
void access_controller::authorized()
{
state_->set_status(state::entry_await);
writer_->authorized();
extra_gpio_->stream_write(gpio::high);
wait(4'000);
}
void access_controller::forbidden()
{
state_->set_status(state::entry_await);
writer_->access_denied();
wait(1'000);
}
void access_controller::force_reset_if_idle()
{
if (state_->get_status() == state::idle)
reset({ });
}
void access_controller::wait(unsigned duration)
{
timer_->defer_with(duration, boost::bind(
&access_controller::reset, this, boost::asio::placeholders::error));
}
void access_controller::reset(const boost::system::error_code& ec)
{
if (ec)
exit(1);
writer_->refresh();
state_->set_status(state::idle);
extra_gpio_->stream_write(gpio::low);
writer_->write_qr_code();
}
}

View File

@ -0,0 +1,44 @@
#pragma once
#include <acs-driver/gpio.hpp>
namespace boost {
namespace system {
class error_code;
}
}
namespace acs
{
class state;
class card_reader;
class oled_writer;
class await_timer;
class access_controller
{
state* state_;
gpio* extra_gpio_;
card_reader* reader_;
oled_writer* writer_;
await_timer* timer_;
void wait(unsigned duration);
void reset(const boost::system::error_code& ec);
public:
explicit access_controller();
void authorized();
void forbidden();
void force_reset_if_idle();
};
}

16
client/await_timer.cpp Normal file
View File

@ -0,0 +1,16 @@
#include "await_timer.hpp"
#include "factory.hpp"
namespace acs
{
void await_timer::callback_handler(const boost::system::error_code& ec)
{
user_cb_(ec);
}
await_timer::await_timer() : loop_(factory::get()->get_loop()), timer_(*loop_->get_io_service())
{
}
}

28
client/await_timer.hpp Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include <boost/asio.hpp>
#include <boost/bind.hpp>
namespace acs
{
class loop;
class await_timer
{
loop* loop_;
boost::asio::deadline_timer timer_;
std::function<void(const boost::system::error_code&)> user_cb_;
void callback_handler(const boost::system::error_code& ec);
public:
explicit await_timer();
template <typename F>
void defer_with(int duration, F&& func)
{
user_cb_ = func;
timer_.expires_from_now(boost::posix_time::millisec(duration));
timer_.async_wait(boost::bind(
&await_timer::callback_handler, this, boost::asio::placeholders::error));
}
};
}

49
client/card_reader.cpp Normal file
View File

@ -0,0 +1,49 @@
#include "card_reader.hpp"
#include "factory.hpp"
#include <cstring>
namespace acs
{
card_reader::card_reader()
{
rfid_ = factory::get()->get_devices()->rfid();
rfid_->pcd_init();
}
uint32_t card_reader::find_card()
{
if (!rfid_->picc_is_new_card_present())
return 0;
if (!rfid_->picc_read_card_serial())
return 0;
last_uid_ = rfid_->uid;
uint32_t retval;
memcpy(&retval, last_uid_.uid_byte, 4);
return retval;
}
std::string card_reader::get_student_number()
{
auto status = rfid_->pcd_authenticate(
mfrc522::PICC_CMD_MF_AUTH_KEY_A, 1, &key_, &last_uid_);
if (status != mfrc522::STATUS_OK)
return {};
byte buffer[18];
byte len = 18;
status = rfid_->mifare_read(1, buffer, &len);
if (status != mfrc522::STATUS_OK || len != 18)
return {};
std::string retval;
for (auto i = 0; i < 10; ++i)
{
if (buffer[i] < 0x30)
break;
retval += buffer[i];
}
rfid_->pcd_stop_crypto1();
return retval;
}
mfrc522::mifare_key card_reader::key_ = { { 1, 2, 3, 4, 5, 6 } };
}

19
client/card_reader.hpp Normal file
View File

@ -0,0 +1,19 @@
#pragma once
#include <acs-driver/mfrc522.hpp>
namespace acs
{
class card_reader
{
mfrc522* rfid_ = nullptr;
mfrc522::uid_t last_uid_;
static mfrc522::mifare_key key_;
public:
explicit card_reader();
uint32_t find_card();
std::string get_student_number();
};
}

38
client/devices.hpp Normal file
View File

@ -0,0 +1,38 @@
#pragma once
#include <acs-driver/mfrc522.hpp>
#include <acs-driver/ssd1306.hpp>
#include <acs-driver/gpio.hpp>
namespace acs
{
class devices
{
mfrc522 rfid_;
ssd1306 oled_;
gpio extra_;
public:
explicit devices() : rfid_(2, 0, 27), oled_(1, 0, 22, 61), extra_(44)
{
extra_.set_direction(gpio::output);
extra_.stream_open();
extra_.stream_write(gpio::low);
}
mfrc522* rfid()
{
return &rfid_;
}
ssd1306* oled()
{
return &oled_;
}
gpio* extra()
{
return &extra_;
}
};
}

6
client/factory.cpp Normal file
View File

@ -0,0 +1,6 @@
#include "factory.hpp"
namespace acs
{
factory factory::singleton_;
}

110
client/factory.hpp Normal file
View File

@ -0,0 +1,110 @@
#pragma once
#include "devices.hpp"
#include "card_reader.hpp"
#include "oled_writer.hpp"
#include "state.hpp"
#include "loop.hpp"
#include "reader_timer.hpp"
#include "await_timer.hpp"
#include "mysql_conn.hpp"
#include "qr_encoder.hpp"
#include "request_delegate.hpp"
#include "access_controller.hpp"
#include "request_timer.hpp"
namespace acs
{
class factory
{
devices devices_;
loop loop_;
card_reader reader_;
oled_writer writer_;
state state_;
reader_timer reader_timer_;
await_timer await_timer_;
mysql_conn mysql_conn_;
qr_encoder qr_encoder_;
request_delegate request_delegate_;
access_controller access_controller_;
request_timer request_timer_;
static factory singleton_;
explicit factory() = default;
public:
static factory* get()
{
return &singleton_;
}
devices* get_devices()
{
return &devices_;
}
loop* get_loop()
{
return &loop_;
}
card_reader* get_reader()
{
return &reader_;
}
oled_writer* get_writer()
{
return &writer_;
}
state* get_state()
{
return &state_;
}
reader_timer* get_reader_timer()
{
return &reader_timer_;
}
await_timer* get_await_timer()
{
return &await_timer_;
}
mysql_conn* get_mysql()
{
return &mysql_conn_;
}
qr_encoder* get_qr_encoder()
{
return &qr_encoder_;
}
request_delegate* get_request_delegate()
{
return &request_delegate_;
}
access_controller* get_controller()
{
return &access_controller_;
}
};
}

76
client/http_client.cpp Normal file
View File

@ -0,0 +1,76 @@
#include "http_client.hpp"
#include <boost/asio/connect.hpp>
#include <boost/bind/bind.hpp>
#include <boost/asio.hpp>
#include <iostream>
#include "factory.hpp"
namespace acs
{
void http_client::on_resolve(const boost::system::error_code& ec, const tcp::resolver::iterator& iterator)
{
if (ec)
return fail("Resolve failed.", ec);
async_connect(socket_, iterator, boost::bind(
&http_client::on_connect, this, boost::asio::placeholders::error));
}
void http_client::on_connect(const boost::system::error_code& ec)
{
if (ec)
return fail("Connect failed.", ec);
http::async_write(socket_, request_, boost::bind(
&http_client::on_write, this, boost::asio::placeholders::error));
}
void http_client::on_write(const boost::system::error_code& ec)
{
if (ec)
return fail("Failed to send request.", ec);
http::async_read(socket_, buffer_, response_, boost::bind(
&http_client::on_read, this, boost::asio::placeholders::error));
}
void http_client::on_read(const boost::system::error_code& ec)
{
if (ec)
return fail("Failed to fetch responce.", ec);
callback_(buffers_to_string(response_.body().data()));
reset();
}
void http_client::fail(const std::string& message, const boost::beast::error_code& ec)
{
std::cerr << message << " Message: " << ec.message() << std::endl;
reset();
callback_("");
}
void http_client::reset()
{
buffer_.consume(buffer_.max_size());
buffer_.shrink_to_fit();
request_.clear();
response_.clear();
}
http_client::http_client() : io_service_(
factory::get()->get_loop()->get_io_service()), resolver_(*io_service_), socket_(*io_service_)
{
}
void http_client::async_get(const std::string& host, const std::string& query,
const std::function<void(const std::string&)>& callback)
{
host_ = host;
callback_ = callback;
request_ = { http::verb::get, query, 11 };
request_.set(http::field::host, host_);
request_.prepare_payload();
resolver_.async_resolve({ host, "7722" }, boost::bind(&http_client::on_resolve, this,
boost::asio::placeholders::error, boost::asio::placeholders::iterator));
}
}

51
client/http_client.hpp Normal file
View File

@ -0,0 +1,51 @@
#pragma once
#include <boost/beast.hpp>
using tcp = boost::asio::ip::tcp;
namespace http = boost::beast::http;
namespace acs
{
class http_client
{
boost::asio::io_service* io_service_;
std::string host_;
tcp::resolver resolver_;
boost::beast::error_code ec_;
boost::beast::flat_buffer buffer_;
tcp::socket socket_;
http::response<http::dynamic_body> response_;
http::request<http::empty_body> request_;
std::function<void(const std::string&)> callback_;
void on_resolve(const boost::system::error_code& ec, const tcp::resolver::iterator& iterator);
void on_connect(const boost::system::error_code& ec);
void on_write(const boost::system::error_code& ec);
void on_read(const boost::system::error_code& ec);
void fail(const std::string& message, const boost::beast::error_code& ec);
void reset();
public:
explicit http_client();
void async_get(
const std::string& host,
const std::string& query,
const std::function<void(const std::string&)>& callback);
};
}

21
client/lib/Makefile Normal file
View File

@ -0,0 +1,21 @@
SOURCES = $(wildcard *.cpp)
HEADERS = $(wildcard *.hpp)
OBJECTS = $(SOURCES:%.cpp=%.o)
LIBRARY = libacsdriver.so
CXXFLAGS = -Wall -c -fpic -g -O0 -std=c++14 -o
LDFLAGS = -shared
all: ${OBJECTS} ${LIBRARY}
${LIBRARY}: ${OBJECTS}
${CXX} -o $@ ${OBJECTS} ${LDFLAGS}
${OBJECTS}:
${CXX} ${CXXFLAGS} $@ ${@:%.o=%.cpp}
clean:
rm -f ${LIBRARY} ${OBJECTS}
install:
cp -f ${HEADERS} /usr/include/acs-driver
cp ${LIBRARY} /usr/lib

187
client/lib/gpio.cpp Normal file
View File

@ -0,0 +1,187 @@
/*
* GPIO.cpp Created on: 29 Apr 2014
* Copyright (c) 2014 Derek Molloy (www.derekmolloy.ie)
* Made available for the book "Exploring BeagleBone"
* If you use this code in your work please cite:
* Derek Molloy, "Exploring BeagleBone: Tools and Techniques for Building
* with Embedded Linux", Wiley, 2014, ISBN:9781118935125.
* See: www.exploringbeaglebone.com
* Licensed under the EUPL V.1.1
*
* This Software is provided to You under the terms of the European
* Union Public License (the "EUPL") version 1.1 as published by the
* European Union. Any use of this Software, other than as authorized
* under this License is strictly prohibited (to the extent such use
* is covered by a right of the copyright holder of this Software).
*
* This Software is provided under the License on an "AS IS" basis and
* without warranties of any kind concerning the Software, including
* without limitation merchantability, fitness for a particular purpose,
* absence of defects or errors, accuracy, and non-infringement of
* intellectual property rights other than copyright. This disclaimer
* of warranty is an essential part of the License and a condition for
* the grant of any rights to this Software.
*
* For more details, see http://www.derekmolloy.ie/
*/
#include "gpio.hpp"
#include <fstream>
#include <string>
#include <iostream>
#include <unistd.h>
int gpio::write(const std::string& path, const std::string& filename, const std::string& value) {
std::ofstream fs;
fs.open(path + filename);
if (!fs.is_open()) {
std::cerr << "GPIO: write failed to open file." << std::endl;
return -1;
}
fs << value;
fs.close();
return 0;
}
std::string gpio::read(const std::string& path, const std::string& filename) {
std::ifstream fs;
fs.open(path + filename);
if (!fs.is_open()) {
std::cerr << "GPIO: read failed to open file " << std::endl;
return { };
}
std::string input;
getline(fs, input);
fs.close();
return input;
}
int gpio::write(const std::string& path, const std::string& filename, int value) {
return write(path, filename, std::to_string(value));
}
gpio::gpio(int number) : number_(number)
{
name_ = std::string("gpio") + std::to_string(number);
path_ = std::string(base_path) + this->name_ + "/";
this->export_gpio();
// Need to give Linux time to set up the sysfs structure
usleep(300'000);
}
int gpio::export_gpio()
{
return write(base_path, "export", number_);
}
int gpio::unexport_gpio()
{
return write(base_path, "unexport", number_);
}
int gpio::set_direction(direction dir)
{
switch (dir) {
case input:
return write(path_, "direction", "in");
case output:
return write(path_, "direction", "out");
}
return -1;
}
int gpio::set_value(value value)
{
switch (value) {
case high:
return write(path_, "value", "1");
case low:
return write(path_, "value", "0");
}
return -1;
}
int gpio::set_edge_type(edge value)
{
switch (value) {
case none:
return write(path_, "edge", "none");
case rising:
return write(path_, "edge", "rising");
case falling:
return write(path_, "edge", "falling");
case both:
return write(path_, "edge", "both");
}
return -1;
}
int gpio::set_active_low(bool is_low)
{
if (is_low)
return write(path_, "active_low", "1");
return write(path_, "active_low", "0");
}
int gpio::set_active_high()
{
return this->set_active_low(false);
}
gpio::value gpio::get_value() const
{
auto input = read(path_, "value");
if (input == "0")
return low;
return high;
}
gpio::direction gpio::get_direction() const
{
auto direction = read(path_, "direction");
if (direction == "in")
return input;
return output;
}
gpio::edge gpio::get_edge_type() const
{
auto edge = read(path_, "edge");
if (edge == "rising")
return rising;
if (edge == "falling")
return falling;
if (edge == "both")
return both;
return none;
}
int gpio::stream_open()
{
stream_.open(path_ + "value");
return 0;
}
int gpio::stream_write(value value)
{
stream_ << value << std::flush;
return 0;
}
int gpio::stream_close()
{
stream_.close();
return 0;
}
int gpio::toggle_output()
{
set_direction(output);
if (get_value())
set_value(low);
else
set_value(high);
return 0;
}
gpio::~gpio() {
this->unexport_gpio();
}

100
client/lib/gpio.hpp Normal file
View File

@ -0,0 +1,100 @@
/*
* @file GPIO.h
* @author Derek Molloy
* @version 0.1
*
* Created on: 29 Apr 2014
* Copyright (c) 2014 Derek Molloy (www.derekmolloy.ie)
* Made available for the book "Exploring BeagleBone"
* See: www.exploringbeaglebone.com
* Licensed under the EUPL V.1.1
*
* This Software is provided to You under the terms of the European
* Union Public License (the "EUPL") version 1.1 as published by the
* European Union. Any use of this Software, other than as authorized
* under this License is strictly prohibited (to the extent such use
* is covered by a right of the copyright holder of this Software).
*
* This Software is provided under the License on an "AS IS" basis and
* without warranties of any kind concerning the Software, including
* without limitation merchantability, fitness for a particular purpose,
* absence of defects or errors, accuracy, and non-infringement of
* intellectual property rights other than copyright. This disclaimer
* of warranty is an essential part of the License and a condition for
* the grant of any rights to this Software.
*
* For more details, see http://www.derekmolloy.ie/
*/
#pragma once
#include <string>
#include <fstream>
class gpio
{
public:
enum direction
{
input, output
};
enum value
{
low = 0, high = 1
};
enum edge
{
none, rising, falling, both
};
private:
static constexpr const char* base_path = "/sys/class/gpio/";
/// The GPIO number of the object
int number_;
/// The name of the GPIO
std::string name_;
/// The full path to the GPIO
std::string path_;
/// Write a string to a file.
static int write(const std::string& path, const std::string& filename, const std::string& value);
/// Write an integer to a file.
static int write(const std::string& path, const std::string& filename, int value);
/// Read a string from a file.
static std::string read(const std::string& path, const std::string& filename);
public:
explicit gpio(int number);
int get_number() const
{
return number_;
}
// General Input and Output Settings
int set_direction(direction);
direction get_direction() const;
int set_value(value);
int toggle_output();
value get_value() const;
int set_active_low(bool is_low = true);
int set_active_high(); //default
// Advanced OUTPUT: Faster write by keeping the stream alive (~20X)
int stream_open();
int stream_write(value);
int stream_close();
// Advanced INPUT: Detect input edges; threaded and non-threaded
int set_edge_type(edge);
edge get_edge_type() const;
~gpio();
private:
int export_gpio();
int unexport_gpio();
std::ofstream stream_;
};

1325
client/lib/mfrc522.cpp Normal file

File diff suppressed because it is too large Load Diff

382
client/lib/mfrc522.hpp Normal file
View File

@ -0,0 +1,382 @@
/**
* MFRC522.h - Library to use ARDUINO RFID MODULE KIT 13.56 MHZ WITH TAGS SPI W AND R BY COOQROBOT.
* Based on code Dr.Leong ( WWW.B2CQSHOP.COM )
* Created by Miguel Balboa (circuitito.com), Jan, 2012.
* Rewritten by Søren Thing Andersen (access.thing.dk), fall of 2013 (Translation to English, refactored, comments, anti collision, cascade levels.)
* Extended by Tom Clement with functionality to write to sector 0 of UID changeable Mifare cards.
* Released into the public domain.
*
* Please read this file for an overview and then MFRC522.cpp for comments on the specific functions.
* Search for "mf-rc522" on ebay.com to purchase the MF-RC522 board.
*
* There are three hardware components involved:
* 1) The micro controller: An Arduino
* 2) The PCD (short for Proximity Coupling Device): NXP MFRC522 Contactless Reader IC
* 3) The PICC (short for Proximity Integrated Circuit Card): A card or tag using the ISO 14443A interface, eg Mifare or NTAG203.
*
* The microcontroller and card reader uses SPI for communication.
* The protocol is described in the MFRC522 datasheet: http://www.nxp.com/documents/data_sheet/MFRC522.pdf
*
* The card reader and the tags communicate using a 13.56MHz electromagnetic field.
* The protocol is defined in ISO/IEC 14443-3 Identification cards -- Contactless integrated circuit cards -- Proximity cards -- Part 3: Initialization and anticollision".
* A free version of the final draft can be found at http://wg8.de/wg8n1496_17n3613_Ballot_FCD14443-3.pdf
* Details are found in chapter 6, Type A Initialization and anticollision.
*
* If only the PICC UID is wanted, the above documents has all the needed information.
* To read and write from MIFARE PICCs, the MIFARE protocol is used after the PICC has been selected.
* The MIFARE Classic chips and protocol is described in the datasheets:
* 1K: http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf
* 4K: http://datasheet.octopart.com/MF1S7035DA4,118-NXP-Semiconductors-datasheet-11046188.pdf
* Mini: http://www.idcardmarket.com/download/mifare_S20_datasheet.pdf
* The MIFARE Ultralight chip and protocol is described in the datasheets:
* Ultralight: http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf
* Ultralight C: http://www.nxp.com/documents/short_data_sheet/MF0ICU2_SDS.pdf
*
* MIFARE Classic 1K (MF1S503x):
* Has 16 sectors * 4 blocks/sector * 16 bytes/block = 1024 bytes.
* The blocks are numbered 0-63.
* Block 3 in each sector is the Sector Trailer. See http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf sections 8.6 and 8.7:
* Bytes 0-5: Key A
* Bytes 6-8: Access Bits
* Bytes 9: User data
* Bytes 10-15: Key B (or user data)
* Block 0 is read-only manufacturer data.
* To access a block, an authentication using a key from the block's sector must be performed first.
* Example: To read from block 10, first authenticate using a key from sector 3 (blocks 8-11).
* All keys are set to FFFFFFFFFFFFh at chip delivery.
* Warning: Please read section 8.7 "Memory Access". It includes this text: if the PICC detects a format violation the whole sector is irreversibly blocked.
* To use a block in "value block" mode (for Increment/Decrement operations) you need to change the sector trailer. Use PICC_SetAccessBits() to calculate the bit patterns.
* MIFARE Classic 4K (MF1S703x):
* Has (32 sectors * 4 blocks/sector + 8 sectors * 16 blocks/sector) * 16 bytes/block = 4096 bytes.
* The blocks are numbered 0-255.
* The last block in each sector is the Sector Trailer like above.
* MIFARE Classic Mini (MF1 IC S20):
* Has 5 sectors * 4 blocks/sector * 16 bytes/block = 320 bytes.
* The blocks are numbered 0-19.
* The last block in each sector is the Sector Trailer like above.
*
* MIFARE Ultralight (MF0ICU1):
* Has 16 pages of 4 bytes = 64 bytes.
* Pages 0 + 1 is used for the 7-byte UID.
* Page 2 contains the last check digit for the UID, one byte manufacturer internal data, and the lock bytes (see http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf section 8.5.2)
* Page 3 is OTP, One Time Programmable bits. Once set to 1 they cannot revert to 0.
* Pages 4-15 are read/write unless blocked by the lock bytes in page 2.
* MIFARE Ultralight C (MF0ICU2):
* Has 48 pages of 4 bytes = 192 bytes.
* Pages 0 + 1 is used for the 7-byte UID.
* Page 2 contains the last check digit for the UID, one byte manufacturer internal data, and the lock bytes (see http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf section 8.5.2)
* Page 3 is OTP, One Time Programmable bits. Once set to 1 they cannot revert to 0.
* Pages 4-39 are read/write unless blocked by the lock bytes in page 2.
* Page 40 Lock bytes
* Page 41 16 bit one way counter
* Pages 42-43 Authentication configuration
* Pages 44-47 Authentication key
*/
#pragma once
// Enable integer limits
#define __STDC_LIMIT_MACROS
#include <cstdint>
#include "spi_device.hpp"
#include "gpio.hpp"
using byte = unsigned char;
class mfrc522 {
/// The SPI bus.
spi_device spi_;
/// The reset GPIO pin.
gpio reset_;
// Size of the MFRC522 FIFO
static constexpr byte fifo_size = 64;
// Default value for unused pin
static constexpr uint8_t unused_pin = UINT8_MAX;
// Firmware data for self-test
// Reference values based on firmware version
// Hint: if needed, you can remove unused self-test data to save flash memory
//
// Version 0.0 (0x90)
// Philips Semiconductors; Preliminary Specification Revision 2.0 - 01 August 2005; 16.1 self-test
static const byte mfrc522_firmware_reference_v0_0[64];
// Version 1.0 (0x91)
// NXP Semiconductors; Rev. 3.8 - 17 September 2014; 16.1.1 self-test
static const byte mfrc522_firmware_reference_v1_0[64];
// Version 2.0 (0x92)
// NXP Semiconductors; Rev. 3.8 - 17 September 2014; 16.1.1 self-test
static const byte mfrc522_firmware_reference_v2_0[64];
// Clone
// Fudan Semiconductor FM17522 (0x88)
static const byte fm17522_firmware_reference[64];
public:
// MFRC522 registers. Described in chapter 9 of the datasheet.
// When using SPI all addresses are shifted one bit left in the "SPI address byte" (section 8.1.2.3)
enum pcd_register : byte {
// Page 0: Command and status
// 0x00 // reserved for future use
CommandReg = 0x01 << 1, // starts and stops command execution
ComIEnReg = 0x02 << 1, // enable and disable interrupt request control bits
DivIEnReg = 0x03 << 1, // enable and disable interrupt request control bits
ComIrqReg = 0x04 << 1, // interrupt request bits
DivIrqReg = 0x05 << 1, // interrupt request bits
ErrorReg = 0x06 << 1, // error bits showing the error status of the last command executed
Status1Reg = 0x07 << 1, // communication status bits
Status2Reg = 0x08 << 1, // receiver and transmitter status bits
FIFODataReg = 0x09 << 1, // input and output of 64 byte FIFO buffer
FIFOLevelReg = 0x0A << 1, // number of bytes stored in the FIFO buffer
WaterLevelReg = 0x0B << 1, // level for FIFO underflow and overflow warning
ControlReg = 0x0C << 1, // miscellaneous control registers
BitFramingReg = 0x0D << 1, // adjustments for bit-oriented frames
CollReg = 0x0E << 1, // bit position of the first bit-collision detected on the RF interface
// 0x0F // reserved for future use
// Page 1: Command
// 0x10 // reserved for future use
ModeReg = 0x11 << 1, // defines general modes for transmitting and receiving
TxModeReg = 0x12 << 1, // defines transmission data rate and framing
RxModeReg = 0x13 << 1, // defines reception data rate and framing
TxControlReg = 0x14 << 1, // controls the logical behavior of the antenna driver pins TX1 and TX2
TxASKReg = 0x15 << 1, // controls the setting of the transmission modulation
TxSelReg = 0x16 << 1, // selects the internal sources for the antenna driver
RxSelReg = 0x17 << 1, // selects internal receiver settings
RxThresholdReg = 0x18 << 1, // selects thresholds for the bit decoder
DemodReg = 0x19 << 1, // defines demodulator settings
// 0x1A // reserved for future use
// 0x1B // reserved for future use
MfTxReg = 0x1C << 1, // controls some MIFARE communication transmit parameters
MfRxReg = 0x1D << 1, // controls some MIFARE communication receive parameters
// 0x1E // reserved for future use
SerialSpeedReg = 0x1F << 1, // selects the speed of the serial UART interface
// Page 2: Configuration
// 0x20 // reserved for future use
CRCResultRegH = 0x21 << 1, // shows the MSB and LSB values of the CRC calculation
CRCResultRegL = 0x22 << 1,
// 0x23 // reserved for future use
ModWidthReg = 0x24 << 1, // controls the ModWidth setting?
// 0x25 // reserved for future use
RFCfgReg = 0x26 << 1, // configures the receiver gain
GsNReg = 0x27 << 1, // selects the conductance of the antenna driver pins TX1 and TX2 for modulation
CWGsPReg = 0x28 << 1, // defines the conductance of the p-driver output during periods of no modulation
ModGsPReg = 0x29 << 1, // defines the conductance of the p-driver output during periods of modulation
TModeReg = 0x2A << 1, // defines settings for the internal timer
TPrescalerReg = 0x2B << 1, // the lower 8 bits of the TPrescaler value. The 4 high bits are in TModeReg.
TReloadRegH = 0x2C << 1, // defines the 16-bit timer reload value
TReloadRegL = 0x2D << 1,
TCounterValueRegH = 0x2E << 1, // shows the 16-bit timer value
TCounterValueRegL = 0x2F << 1,
// Page 3: Test Registers
// 0x30 // reserved for future use
TestSel1Reg = 0x31 << 1, // general test signal configuration
TestSel2Reg = 0x32 << 1, // general test signal configuration
TestPinEnReg = 0x33 << 1, // enables pin output driver on pins D1 to D7
TestPinValueReg = 0x34 << 1, // defines the values for D1 to D7 when it is used as an I/O bus
TestBusReg = 0x35 << 1, // shows the status of the internal test bus
AutoTestReg = 0x36 << 1, // controls the digital self-test
VersionReg = 0x37 << 1, // shows the software version
AnalogTestReg = 0x38 << 1, // controls the pins AUX1 and AUX2
TestDAC1Reg = 0x39 << 1, // defines the test value for TestDAC1
TestDAC2Reg = 0x3A << 1, // defines the test value for TestDAC2
TestADCReg = 0x3B << 1 // shows the value of ADC I and Q channels
// 0x3C // reserved for production tests
// 0x3D // reserved for production tests
// 0x3E // reserved for production tests
// 0x3F // reserved for production tests
};
// MFRC522 commands. Described in chapter 10 of the datasheet.
enum pcd_command : byte {
PCD_Idle = 0x00, // no action, cancels current command execution
PCD_Mem = 0x01, // stores 25 bytes into the internal buffer
PCD_GenerateRandomID = 0x02, // generates a 10-byte random ID number
PCD_CalcCRC = 0x03, // activates the CRC coprocessor or performs a self-test
PCD_Transmit = 0x04, // transmits data from the FIFO buffer
PCD_NoCmdChange = 0x07, // no command change, can be used to modify the CommandReg register bits without affecting the command, for example, the PowerDown bit
PCD_Receive = 0x08, // activates the receiver circuits
PCD_Transceive = 0x0C, // transmits data from FIFO buffer to antenna and automatically activates the receiver after transmission
PCD_MFAuthent = 0x0E, // performs the MIFARE standard authentication as a reader
PCD_SoftReset = 0x0F // resets the MFRC522
};
// MFRC522 RxGain[2:0] masks, defines the receiver's signal voltage gain factor (on the PCD).
// Described in 9.3.3.6 / table 98 of the datasheet at http://www.nxp.com/documents/data_sheet/MFRC522.pdf
enum pcd_rx_gain : byte {
RxGain_18dB = 0x00 << 4, // 000b - 18 dB, minimum
RxGain_23dB = 0x01 << 4, // 001b - 23 dB
RxGain_18dB_2 = 0x02 << 4, // 010b - 18 dB, it seems 010b is a duplicate for 000b
RxGain_23dB_2 = 0x03 << 4, // 011b - 23 dB, it seems 011b is a duplicate for 001b
RxGain_33dB = 0x04 << 4, // 100b - 33 dB, average, and typical default
RxGain_38dB = 0x05 << 4, // 101b - 38 dB
RxGain_43dB = 0x06 << 4, // 110b - 43 dB
RxGain_48dB = 0x07 << 4, // 111b - 48 dB, maximum
RxGain_min = 0x00 << 4, // 000b - 18 dB, minimum, convenience for RxGain_18dB
RxGain_avg = 0x04 << 4, // 100b - 33 dB, average, convenience for RxGain_33dB
RxGain_max = 0x07 << 4 // 111b - 48 dB, maximum, convenience for RxGain_48dB
};
// Commands sent to the PICC.
enum picc_command : byte {
// The commands used by the PCD to manage communication with several PICCs (ISO 14443-3, Type A, section 6.4)
PICC_CMD_REQA = 0x26, // REQuest command, Type A. Invites PICCs in state IDLE to go to READY and prepare for anticollision or selection. 7 bit frame.
PICC_CMD_WUPA = 0x52, // Wake-UP command, Type A. Invites PICCs in state IDLE and HALT to go to READY(*) and prepare for anticollision or selection. 7 bit frame.
PICC_CMD_CT = 0x88, // Cascade Tag. Not really a command, but used during anti collision.
PICC_CMD_SEL_CL1 = 0x93, // Anti collision/Select, Cascade Level 1
PICC_CMD_SEL_CL2 = 0x95, // Anti collision/Select, Cascade Level 2
PICC_CMD_SEL_CL3 = 0x97, // Anti collision/Select, Cascade Level 3
PICC_CMD_HLTA = 0x50, // HaLT command, Type A. Instructs an ACTIVE PICC to go to state HALT.
PICC_CMD_RATS = 0xE0, // Request command for Answer To Reset.
// The commands used for MIFARE Classic (from http://www.mouser.com/ds/2/302/MF1S503x-89574.pdf, Section 9)
// Use PCD_MFAuthent to authenticate access to a sector, then use these commands to read/write/modify the blocks on the sector.
// The read/write commands can also be used for MIFARE Ultralight.
PICC_CMD_MF_AUTH_KEY_A = 0x60, // Perform authentication with Key A
PICC_CMD_MF_AUTH_KEY_B = 0x61, // Perform authentication with Key B
PICC_CMD_MF_READ = 0x30, // Reads one 16 byte block from the authenticated sector of the PICC. Also used for MIFARE Ultralight.
PICC_CMD_MF_WRITE = 0xA0, // Writes one 16 byte block to the authenticated sector of the PICC. Called "COMPATIBILITY WRITE" for MIFARE Ultralight.
PICC_CMD_MF_DECREMENT = 0xC0, // Decrements the contents of a block and stores the result in the internal data register.
PICC_CMD_MF_INCREMENT = 0xC1, // Increments the contents of a block and stores the result in the internal data register.
PICC_CMD_MF_RESTORE = 0xC2, // Reads the contents of a block into the internal data register.
PICC_CMD_MF_TRANSFER = 0xB0, // Writes the contents of the internal data register to a block.
// The commands used for MIFARE Ultralight (from http://www.nxp.com/documents/data_sheet/MF0ICU1.pdf, Section 8.6)
// The PICC_CMD_MF_READ and PICC_CMD_MF_WRITE can also be used for MIFARE Ultralight.
PICC_CMD_UL_WRITE = 0xA2 // Writes one 4 byte page to the PICC.
};
// MIFARE constants that does not fit anywhere else
enum mifare_misc {
MF_ACK = 0xA, // The MIFARE Classic uses a 4 bit ACK/NAK. Any other value than 0xA is NAK.
MF_KEY_SIZE = 6 // A Mifare Crypto1 key is 6 bytes.
};
// PICC types we can detect. Remember to update PICC_GetTypeName() if you add more.
// last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered
enum picc_type : byte {
PICC_TYPE_UNKNOWN,
PICC_TYPE_ISO_14443_4, // PICC compliant with ISO/IEC 14443-4
PICC_TYPE_ISO_18092, // PICC compliant with ISO/IEC 18092 (NFC)
PICC_TYPE_MIFARE_MINI, // MIFARE Classic protocol, 320 bytes
PICC_TYPE_MIFARE_1K, // MIFARE Classic protocol, 1KB
PICC_TYPE_MIFARE_4K, // MIFARE Classic protocol, 4KB
PICC_TYPE_MIFARE_UL, // MIFARE Ultralight or Ultralight C
PICC_TYPE_MIFARE_PLUS, // MIFARE Plus
PICC_TYPE_MIFARE_DESFIRE, // MIFARE DESFire
PICC_TYPE_TNP3XXX, // Only mentioned in NXP AN 10833 MIFARE Type Identification Procedure
PICC_TYPE_NOT_COMPLETE = 0xff // SAK indicates UID is not complete.
};
// Return codes from the functions in this class. Remember to update GetStatusCodeName() if you add more.
// last value set to 0xff, then compiler uses less ram, it seems some optimisations are triggered
enum status_code : byte {
STATUS_OK, // Success
STATUS_ERROR, // Error in communication
STATUS_COLLISION, // Collission detected
STATUS_TIMEOUT, // Timeout in communication.
STATUS_NO_ROOM, // A buffer is not big enough.
STATUS_INTERNAL_ERROR, // Internal error in the code. Should not happen ;-)
STATUS_INVALID, // Invalid argument.
STATUS_CRC_WRONG, // The CRC_A does not match
STATUS_MIFARE_NACK = 0xff // A MIFARE PICC responded with NAK.
};
// A struct used for passing the UID of a PICC.
typedef struct {
byte size; // Number of bytes in the UID. 4, 7 or 10.
byte uid_byte[10];
byte sak; // The SAK (Select acknowledge) byte returned from the PICC after successful selection.
} uid_t;
// A struct used for passing a MIFARE Crypto1 key
typedef struct {
byte key_byte[MF_KEY_SIZE];
} mifare_key;
// Member variables
uid_t uid; // Used by PICC_ReadCardSerial().
/////////////////////////////////////////////////////////////////////////////////////
// Functions for setting up the device
/////////////////////////////////////////////////////////////////////////////////////
explicit mfrc522(unsigned bus, unsigned device, unsigned reset);
/////////////////////////////////////////////////////////////////////////////////////
// Basic interface functions for communicating with the MFRC522
/////////////////////////////////////////////////////////////////////////////////////
void pcd_write_register(pcd_register reg, byte value);
void pcd_write_register(pcd_register reg, byte count, byte *values);
byte pcd_read_register(pcd_register reg);
void pcd_read_register(pcd_register reg, byte count, byte *values, byte rx_align = 0);
void pcd_set_register_bit_mask(pcd_register reg, byte mask);
void pcd_clear_register_bit_mask(pcd_register reg, byte mask);
status_code pcd_calculate_crc(byte *data, byte length, byte *result);
/////////////////////////////////////////////////////////////////////////////////////
// Functions for manipulating the MFRC522
/////////////////////////////////////////////////////////////////////////////////////
void pcd_init();
void pcd_reset();
void pcd_antenna_on();
void pcd_antenna_off();
byte pcd_get_antenna_gain();
void pcd_set_antenna_gain(byte mask);
bool pcd_perform_self_test();
/////////////////////////////////////////////////////////////////////////////////////
// Power control functions
/////////////////////////////////////////////////////////////////////////////////////
void pcd_soft_power_down();
/////////////////////////////////////////////////////////////////////////////////////
// Functions for communicating with PICCs
/////////////////////////////////////////////////////////////////////////////////////
status_code pcd_transceive_data(
byte *send_data, byte send_len, byte *back_data, byte *back_len,
byte *valid_bits = nullptr, byte rx_align = 0, bool check_crc = false);
status_code pcd_communicate_with_picc(
byte command, byte wait_irq, byte *send_data, byte send_len,
byte *back_data = nullptr, byte *back_len = nullptr,
byte *valid_bits = nullptr, byte rx_align = 0, bool check_crc = false);
status_code picc_request_a(byte *buffer_atqa, byte *buffer_size);
status_code picc_wakeup_a(byte *buffer_atqa, byte *buffer_size);
status_code picc_reqa_or_wupa(byte command, byte *buffer_atqa, byte *buffer_size);
virtual status_code picc_select(uid_t *uid, byte valid_bits = 0);
status_code picc_halt_a();
/////////////////////////////////////////////////////////////////////////////////////
// Functions for communicating with MIFARE PICCs
/////////////////////////////////////////////////////////////////////////////////////
status_code pcd_authenticate(byte command, byte block_addr, mifare_key const *key, uid_t *uid);
void pcd_stop_crypto1();
status_code mifare_read(byte block_addr, byte *buffer, byte *buffer_size);
status_code mifare_write(byte block_addr, byte *buffer, byte buffer_size);
status_code mifare_ultralight_write(byte page, byte *buffer, byte buffer_size);
status_code mifare_decrement(byte block_addr, int32_t delta);
status_code mifare_increment(byte block_addr, int32_t delta);
status_code mifare_restore(byte block_addr);
status_code mifare_transfer(byte block_addr);
status_code mifare_get_value(byte block_addr, int32_t *value);
status_code mifare_set_value(byte block_addr, int32_t value);
status_code pcd_ntag216_auth(byte *password, byte p_ack[]);
/////////////////////////////////////////////////////////////////////////////////////
// Support functions
/////////////////////////////////////////////////////////////////////////////////////
status_code pcd_mifare_transceive(byte *send_data, byte send_len, bool accept_timeout = false);
static picc_type picc_get_type(byte sak);
// Advanced functions for MIFARE
void mifare_set_access_bits(byte *access_bit_buffer, byte g0, byte g1, byte g2, byte g3);
/////////////////////////////////////////////////////////////////////////////////////
// Convenience functions - does not add extra functionality
/////////////////////////////////////////////////////////////////////////////////////
virtual bool picc_is_new_card_present();
virtual bool picc_read_card_serial();
protected:
// Functions for communicating with MIFARE PICCs
status_code mifare_two_step_helper(byte command, byte block_addr, int32_t data);
};

147
client/lib/spi_device.cpp Normal file
View File

@ -0,0 +1,147 @@
/*
* SPIDevice.cpp Created on: 22 May 2014
* Copyright (c) 2014 Derek Molloy (www.derekmolloy.ie)
* Made available for the book "Exploring BeagleBone"
* See: www.exploringbeaglebone.com
* Licensed under the EUPL V.1.1
*
* This Software is provided to You under the terms of the European
* Union Public License (the "EUPL") version 1.1 as published by the
* European Union. Any use of this Software, other than as authorized
* under this License is strictly prohibited (to the extent such use
* is covered by a right of the copyright holder of this Software).
*
* This Software is provided under the License on an "AS IS" basis and
* without warranties of any kind concerning the Software, including
* without limitation merchantability, fitness for a particular purpose,
* absence of defects or errors, accuracy, and non-infringement of
* intellectual property rights other than copyright. This disclaimer
* of warranty is an essential part of the License and a condition for
* the grant of any rights to this Software.
*
* For more details, see http://www.derekmolloy.ie/
*/
#include "spi_device.hpp"
#include <iostream>
#include <string>
#include <cstdlib>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <linux/spi/spidev.h>
spi_device::spi_device(unsigned int bus, unsigned int device) : bus_(bus), device_(device)
{
filename_ = std::string(base_path) + std::to_string(bus) + '.' + std::to_string(device);
if ((fd_ = open(filename_.c_str(), O_RDWR)) < 0 ||
set_mode(mode_) == -1 ||
set_speed(speed_) == -1 ||
set_speed(speed_) == -1 ||
set_bits_per_word(bits_) == -1)
{
std::cerr << "Failed to open SPI device: " <<
base_path << bus << '.' << device << std::endl;
exit(1);
}
}
int spi_device::transfer(unsigned char send[], unsigned char receive[], unsigned length)
{
spi_ioc_transfer transfer = {
reinterpret_cast<unsigned long>(send),
reinterpret_cast<unsigned long>(receive),
length, speed_, delay_, bits_
};
auto status = ioctl(this->fd_, SPI_IOC_MESSAGE(1), &transfer);
if (status < 0)
std::cerr << "SPI: SPI_IOC_MESSAGE Failed" << std::endl;
return status;
}
unsigned char spi_device::read_register(unsigned char register_address)
{
unsigned char send[2] = { 0 }, receive[2] = { 0 };
send[0] = static_cast<unsigned char>(0x80 | register_address);
transfer(send, receive, 2);
return receive[1];
}
unsigned char spi_device::read(unsigned char register_address)
{
unsigned char send[2] = { register_address };
unsigned char receive[2] = { 0 };
transfer(send, receive, 2);
return receive[1];
}
int spi_device::write(unsigned char value)
{
this->transfer(&value, nullptr, 1);
return 0;
}
int spi_device::write(unsigned char value[], unsigned length)
{
this->transfer(value, nullptr, length);
return 0;
}
int spi_device::write_register(unsigned char register_address, unsigned char value)
{
unsigned char send[2] = { register_address, value };
unsigned char receive[2] = { 0 };
this->transfer(send, receive, 2);
return 0;
}
int spi_device::set_speed(uint32_t speed)
{
speed_ = speed;
if (ioctl(fd_, SPI_IOC_WR_MAX_SPEED_HZ, &speed_) == -1) {
std::cerr << "SPI: Can't set max speed HZ" << std::endl;
return -1;
}
if (ioctl(fd_, SPI_IOC_RD_MAX_SPEED_HZ, &speed_) == -1) {
std::cerr << "SPI: Can't get max speed HZ." << std::endl;
return -1;
}
return 0;
}
int spi_device::set_mode(mode mode)
{
mode_ = mode;
if (ioctl(fd_, SPI_IOC_WR_MODE, &mode_) == -1) {
std::cerr << "SPI: Can't set SPI mode." << std::endl;
return -1;
}
if (ioctl(fd_, SPI_IOC_RD_MODE, &mode_) == -1) {
std::cerr << "SPI: Can't get SPI mode." << std::endl;
return -1;
}
return 0;
}
int spi_device::set_bits_per_word(uint8_t bits)
{
bits_ = bits;
if (ioctl(fd_, SPI_IOC_WR_BITS_PER_WORD, &bits_) == -1) {
std::cerr << "SPI: Can't set bits per word." << std::endl;
return -1;
}
if (ioctl(fd_, SPI_IOC_RD_BITS_PER_WORD, &bits_) == -1) {
std::cerr << "SPI: Can't get bits per word." << std::endl;
return -1;
}
return 0;
}
spi_device::~spi_device()
{
close(fd_);
}

77
client/lib/spi_device.hpp Normal file
View File

@ -0,0 +1,77 @@
/*
* SPIDevice.h Created on: 22 May 2014
* Copyright (c) 2014 Derek Molloy (www.derekmolloy.ie)
* Made available for the book "Exploring BeagleBone"
* See: www.exploringbeaglebone.com
* Licensed under the EUPL V.1.1
*
* This Software is provided to You under the terms of the European
* Union Public License (the "EUPL") version 1.1 as published by the
* European Union. Any use of this Software, other than as authorized
* under this License is strictly prohibited (to the extent such use
* is covered by a right of the copyright holder of this Software).
*
* This Software is provided under the License on an "AS IS" basis and
* without warranties of any kind concerning the Software, including
* without limitation merchantability, fitness for a particular purpose,
* absence of defects or errors, accuracy, and non-infringement of
* intellectual property rights other than copyright. This disclaimer
* of warranty is an essential part of the License and a condition for
* the grant of any rights to this Software.
*
* For more details, see http://www.derekmolloy.ie/
*/
#pragma once
#include <string>
class spi_device
{
public:
/// SPI modes.
enum mode
{
/// Low at idle, capture on rising clock edge
mode0 = 0,
/// Low at idle, capture on falling clock edge
mode1 = 1,
/// High at idle, capture on falling clock edge
mode2 = 2,
/// High at idle, capture on rising clock edge
mode3 = 3
};
private:
/// Base path of SPI devices.
static constexpr const char* base_path = "/dev/spidev";
/// Bus number.
unsigned int bus_;
/// Device number on the bus.
unsigned int device_;
/// Device file descriptor.
int fd_ = -1;
/// The precise filename for the SPI device.
std::string filename_;
/// The current SPI mode.
mode mode_ = mode3;
/// The number of bits per word
uint8_t bits_ = 8;
/// The speed of transfer in Hz.
uint32_t speed_ = 500000;
/// The transfer delay in usecs.
uint16_t delay_ = 0;
public:
spi_device(unsigned bus, unsigned device);
unsigned char read(unsigned char register_address);
unsigned char read_register(unsigned char register_address);
int write_register(unsigned char register_address, unsigned char value);
int write(unsigned char value);
int write(unsigned char value[], unsigned length);
int set_speed(uint32_t speed);
int set_mode(mode mode);
int set_bits_per_word(uint8_t bits);
~spi_device();
int transfer(unsigned char send[], unsigned char receive[], unsigned length);
};

1543
client/lib/ssd1306.cpp Normal file

File diff suppressed because it is too large Load Diff

207
client/lib/ssd1306.hpp Normal file
View File

@ -0,0 +1,207 @@
/*
* MIT License
Copyright (c) 2017 DeeplyEmbedded
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
* SSD1306_OLED.h
*
* Created on : Sep 21, 2017
* Author : Vinay Divakar
* Website : www.deeplyembedded.org
*/
#pragma once
#include "spi_device.hpp"
#include "gpio.hpp"
#define DISPLAY_BUFF_SIZE (SSD1306_LCDWIDTH*SSD1306_LCDHEIGHT/8)
/* COLOR MACROS */
#define WHITE 1
#define BLACK 0
#define INVERSE 2
/* Number output format */
#define DEC 10
#define HEX 16
#define OCT 8
#define BIN 2
#define DEFAULT 0
#define SSD1306_LCDWIDTH 128
#define SSD1306_LCDHEIGHT 64
/* SSD1306 Commands */
#define SSD1306_DISPLAY_OFF 0xAE
#define SSD1306_SET_DISP_CLK 0xD5
#define SSD1306_SET_MULTIPLEX 0xA8
#define SSD1306_SET_DISP_OFFSET 0xD3
#define SSD1306_SET_DISP_START_LINE 0x40
#define SSD1306_CONFIG_CHARGE_PUMP 0x8D
#define SSD1306_SET_MEM_ADDR_MODE 0x20
#define SSD1306_SEG_REMAP (0xA0 | 0x01)
#define SSD1306_SET_COMSCANDEC 0xC8
#define SSD1306_SET_COMPINS 0xDA
#define SSD1306_SET_CONTRAST 0x81
#define SSD1306_SET_PRECHARGE 0xD9
#define SSD1306_SET_VCOMDETECT 0xDB
#define SSD1306_DISPLAYALLON_RESUME 0xA4
#define SSD1306_NORMAL_DISPLAY 0xA6
#define SSD1306_DISPLAYON 0xAF
#define SSD1306_SET_COL_ADDR 0x21
#define SSD1306_PAGEADDR 0x22
#define SSD1306_INVERT_DISPLAY 0x01
#define SSD1306_NORMALIZE_DISPLAY 0x00
/* SDD1306 Scroll Commands */
#define SSD1306_SET_VERTICAL_SCROLL_AREA 0xA3
#define SSD1306_ACTIVATE_SCROLL 0x2F
#define SSD1306_DEACTIVATE_SCROLL 0x2E
#define SSD1306_RIGHT_HORIZONTAL_SCROLL 0x26
#define SSD1306_LEFT_HORIZONTAL_SCROLL 0x27
#define SSD1306_VERTICAL_AND_RIGHT_HORIZONTAL_SCROLL 0x29
#define SSD1306_VERTICAL_AND_LEFT_HORIZONTAL_SCROLL 0x2A
#define SSD1306_INVERTDISPLAY 0xA7
/* SSD1306 Configuration Commands */
#define SSD1306_DISPCLK_DIV 0x80
#define SSD1306_MULT_64 0x3F
#define SSD1306_DISP_OFFSET_VAL 0x00
#define SSD1306_COL_START_ADDR 0x00
#define SSD1306_COL_END_ADDR (SSD1306_LCDWIDTH - 1)
#define SSD1306_PG_START_ADDR 0x00
#define SSD1306_PG_END_ADDR 7
#define SSD1306_CHARGE_PUMP_EN 0x14
#define SSD1306_CONFIG_COM_PINS 0x12
#define SSD1306_CONTRAST_VAL 0xCF
#define SSD1306_PRECHARGE_VAL 0xF1
#define SSD1306_VCOMH_VAL 0x40
#define SSD1306_MULT_DAT (SSD1306_LCDHEIGHT - 1)
#define SSD1306_HOR_MM 0x00
#define TO_CONST_UCHAR_STR(str) (reinterpret_cast<const unsigned char*>(str))
typedef struct { // Data stored PER GLYPH
unsigned short bitmap_offset; // Pointer into GFXfont->bitmap
unsigned char width, height; // Bitmap dimensions in pixels
unsigned char x_advance; // Distance to advance cursor (x axis)
char x_offset, y_offset; // Dist from cursor pos to UL corner
} gf_xglyph_t, *gf_xglyph_ptr;
typedef struct { // Data stored for FONT AS A WHOLE:
unsigned char *bitmap; // Glyph bitmaps, concatenated
gf_xglyph_ptr glyph; // Glyph array
unsigned char first, last; // ASCII extents
unsigned char y_advance; // Newline distance (y axis)
} gf_xfont_t, *gf_xfont_ptr;
class ssd1306
{
gf_xfont_ptr gfx_font_ = nullptr;
unsigned char chunk_[16] = { 0 };
unsigned char screen_[DISPLAY_BUFF_SIZE] = { 0 };
const static unsigned char font[1280];
unsigned char rotation_ = 0;
unsigned char textsize_ = 0;
spi_device spi_;
gpio reset_;
gpio dc_;
short width_ = SSD1306_LCDWIDTH;
short height_ = SSD1306_LCDHEIGHT;
short cursor_x_ = 0, cursor_y_ = 0, textcolor_ = 0, textbgcolor_ = 0;
bool cp437_ = false, wrap_ = true;
void transfer();
void draw_fast_v_line(short x, short y, short h, short color);
void write_fast_v_line(short x, short y, short h, short color);
void draw_fast_h_line(short x, short y, short w, short color);
void write_fast_h_line(short x, short y, short w, short color);
short print(const unsigned char *buffer, short size);
int cmd(const unsigned char byte)
{
if (dc_.stream_write(gpio::low))
return -1;
return spi_.write(byte);
}
int data()
{
return dc_.stream_write(gpio::high);
}
public:
explicit ssd1306(unsigned bus, unsigned device, unsigned reset, unsigned dc);
~ssd1306();
void clear_display();
void display_init_seq();
void display();
void init_col_pg_addrs(unsigned char col_start_addr, unsigned char col_end_addr,
unsigned char pg_start_addr, unsigned char pg_end_addr);
void set_rotation(unsigned char x);
void start_scroll_right(unsigned char start, unsigned char stop);
void start_scroll_left(unsigned char start, unsigned char stop);
void start_scroll_diag_right(unsigned char start, unsigned char stop);
void start_scroll_diag_left(unsigned char start, unsigned char stop);
void stop_scroll();
void set_cursor(short x, short y);
short get_cursor_x() const;
short get_cursor_y() const;
unsigned char get_rotation() const;
void invert_display(unsigned char i);
signed char draw_pixel(short x, short y, short color);
void write_line(short x0, short y0, short x1, short y1, short color);
void draw_circle_helper( short x0, short y0, short r, unsigned char cornername, short color);
void draw_line(short x0, short y0, short x1, short y1, short color);
void draw_rect(short x, short y, short w, short h, short color);
void fill_rect(short x, short y, short w, short h, short color);
void draw_circle(short x0, short y0, short r, short color);
void fill_circle_helper(short x0, short y0, short r, unsigned char cornername, short delta, short color);
void fill_circle(short x0, short y0, short r, short color);
void draw_triangle(short x0, short y0, short x1, short y1, short x2, short y2, short color);
void fill_triangle(short x0, short y0, short x1, short y1, short x2, short y2, short color);
void draw_round_rect(short x, short y, short w, short h, short r, short color);
void fill_round_rect(short x, short y, short w, short h, short r, short color);
void draw_bitmap(short x, short y, const unsigned char* bitmap, short w, short h, short color);
short oled_write(unsigned char c);
void set_text_size(unsigned char s);
void set_text_color(short c);
void set_text_wrap(bool w);
void draw_char(short x, short y, unsigned char c, short color, short bg, unsigned char size);
short print_str(const char *strPtr);
short println();
short print_strln(const char *strPtr);
short print_number(unsigned long n, unsigned char base);
short print_number_ul(unsigned long n, int base);
short print_number_ul_ln(unsigned long num, int base);
short print_number_ui(unsigned int n, int base);
short print_number_ui_ln(unsigned int n, int base);
short print_number_uc(unsigned char b, int base);
short print_number_uc_ln(unsigned char b, int base);
short print_number_l(long n, int base);
short print_number_l_ln(long num, int base);
short print_number_i(int n, int base);
short print_number_i_ln(int n, int base);
short print_float(double number, unsigned char digits);
short print_float_ln(double num, int digits);
};

24
client/loop.hpp Normal file
View File

@ -0,0 +1,24 @@
#pragma once
#include <boost/asio.hpp>
namespace acs
{
class loop
{
boost::asio::io_service io_service_;
public:
explicit loop() = default;
boost::asio::io_service* get_io_service()
{
return &io_service_;
}
void run()
{
io_service_.run();
}
};
}

6
client/main.cpp Normal file
View File

@ -0,0 +1,6 @@
#include "factory.hpp"
int main()
{
acs::factory::get()->get_loop()->run();
}

34
client/mysql_conn.cpp Normal file
View File

@ -0,0 +1,34 @@
#include "mysql_conn.hpp"
#include <iostream>
namespace acs
{
mysql_conn::mysql_conn() : conn_(false)
{
if (!conn_.connect("acs", "localhost", "acs", "testpass", 3306))
{
std::cerr << "Failed to connect to database." << std::endl;
exit(1);
}
}
std::string mysql_conn::get_stunum(uint32_t uid)
{
char query_str[60] = { 0 };
std::sprintf(query_str, "SELECT `stunum` FROM `auth` WHERE uid = %d LIMIT 1", uid);
auto query = conn_.query(query_str);
auto result = query.store();
if (!result)
return {};
for (auto&& row : result)
return row[0].c_str();
return {};
}
bool mysql_conn::stunum_exists(const std::string& stunum)
{
std::string query_str("SELECT `id` FROM `auth` WHERE stunum = \"");
auto query = conn_.query(query_str + stunum + "\" LIMIT 1");
return query.store();
}
}

18
client/mysql_conn.hpp Normal file
View File

@ -0,0 +1,18 @@
#pragma once
#include <mysql++/mysql++.h>
namespace acs
{
class mysql_conn
{
mysqlpp::Connection conn_;
public:
explicit mysql_conn();
std::string get_stunum(uint32_t uid);
bool stunum_exists(const std::string& stunum);
};
}

112
client/oled_writer.cpp Normal file
View File

@ -0,0 +1,112 @@
#include "oled_writer.hpp"
#include "factory.hpp"
namespace acs
{
const unsigned char oled_writer::tick[] = {
0b00000000, 0b01111110, 0b00000000,
0b00000011, 0b11111111, 0b11000000,
0b00001111, 0b11111111, 0b11110000,
0b00011111, 0b11111111, 0b11111000,
0b00111111, 0b11111111, 0b11111100,
0b01111111, 0b11111111, 0b11111110,
0b01111111, 0b11111111, 0b11000110,
0b01111111, 0b11111111, 0b10000110,
0b11111111, 0b11111111, 0b00000111,
0b11111111, 0b11111110, 0b00001111,
0b11100011, 0b11111100, 0b00011111,
0b11100001, 0b11111000, 0b00111111,
0b11100000, 0b11110000, 0b01111111,
0b11110000, 0b01100000, 0b11111111,
0b11111000, 0b00000001, 0b11111111,
0b11111100, 0b00000011, 0b11111111,
0b01111110, 0b00000111, 0b11111110,
0b01111111, 0b00001111, 0b11111110,
0b01111111, 0b10011111, 0b11111110,
0b00111111, 0b11111111, 0b11111100,
0b00011111, 0b11111111, 0b11111000,
0b00001111, 0b11111111, 0b11110000,
0b00000011, 0b11111111, 0b11000000,
0b00000000, 0b01111110, 0b00000000
};
const unsigned char oled_writer::cross[] = {
0b00000000, 0b11111111, 0b00000000,
0b00000011, 0b11111111, 0b11000000,
0b00001111, 0b11111111, 0b11110000,
0b00011111, 0b11111111, 0b11111000,
0b00110001, 0b11111111, 0b10001100,
0b00110000, 0b11111111, 0b00001100,
0b01110000, 0b01111110, 0b00001110,
0b01111000, 0b00111100, 0b00011110,
0b11111100, 0b00011000, 0b00111111,
0b11111110, 0b00000000, 0b01111111,
0b11111111, 0b00000000, 0b11111111,
0b11111111, 0b10000001, 0b11111111,
0b11111111, 0b10000001, 0b11111111,
0b11111111, 0b00000000, 0b11111111,
0b11111110, 0b00000000, 0b01111111,
0b11111100, 0b00011000, 0b00111111,
0b01111000, 0b00111100, 0b00011110,
0b01110000, 0b01111110, 0b00001110,
0b00110000, 0b11111111, 0b00001100,
0b00110001, 0b11111111, 0b10001100,
0b00011111, 0b11111111, 0b11111000,
0b00001111, 0b11111111, 0b11110000,
0b00000011, 0b11111111, 0b11000000,
0b00000000, 0b11111111, 0b00000000
};
void oled_writer::refresh()
{
oled_->clear_display();
oled_->display();
}
oled_writer::oled_writer() : qr_encoder_(factory::get()->get_qr_encoder())
{
oled_ = factory::get()->get_devices()->oled();
oled_->set_text_color(WHITE);
oled_->display_init_seq();
refresh();
}
void oled_writer::authorized()
{
refresh([this]()
{
oled_->set_text_size(2);
oled_->set_cursor(5, 43);
oled_->print_str("Authorized");
oled_->draw_bitmap(52, 8, tick, 24, 24, WHITE);
});
}
void oled_writer::access_denied()
{
refresh([this]()
{
oled_->set_text_size(2);
oled_->set_cursor(9, 43);
oled_->print_str("Forbidden");
oled_->draw_bitmap(52, 8, cross, 24, 24, WHITE);
});
}
void oled_writer::write_qr_code()
{
refresh([this]()
{
auto width = qr_encoder_->get_width();
auto map = qr_encoder_->get_map();
auto border = (64 - width) / 2;
for (auto i = border; i < width + border; ++i)
{
for (auto j = border; j < width + border; ++j)
{
if (map[(i - border) * width + (j - border)])
oled_->draw_pixel(j, i, WHITE);
}
}
});
}
}

28
client/oled_writer.hpp Normal file
View File

@ -0,0 +1,28 @@
#pragma once
#include <acs-driver/ssd1306.hpp>
namespace acs
{
class qr_encoder;
class oled_writer
{
static const unsigned char tick[72];
static const unsigned char cross[72];
qr_encoder* qr_encoder_;
ssd1306* oled_ = nullptr;
template <typename F>
void refresh(F func)
{
oled_->clear_display();
func();
oled_->display();
}
public:
explicit oled_writer();
void refresh();
void authorized();
void access_denied();
void write_qr_code();
};
}

32
client/qr_encoder.cpp Normal file
View File

@ -0,0 +1,32 @@
#include "qr_encoder.hpp"
#include <qrencode.h>
#include <cstring>
namespace acs
{
bool qr_encoder::encode_url(const std::string& suffix)
{
memset(map_, 0, 64 * 64);
auto url = std::string(base_url) + suffix;
auto qr_code = QRcode_encodeString(url.c_str(), 8, QR_ECLEVEL_M, QR_MODE_8, true);
width_ = qr_code->width;
if (width_ > 64)
return false;
for (auto i = 0U; i < width_ * width_; ++i)
{
map_[i] = qr_code->data[i] % 2 ? 0 : 1;
}
return true;
}
unsigned char* qr_encoder::get_map()
{
return map_;
}
unsigned qr_encoder::get_width() const
{
return width_;
}
}

20
client/qr_encoder.hpp Normal file
View File

@ -0,0 +1,20 @@
#pragma once
#include <string>
namespace acs
{
class qr_encoder
{
unsigned char map_[64 * 64] = { 0 };
unsigned width_ = 0;
static constexpr auto base_url = "https://api.cismon.net/auth/";
public:
explicit qr_encoder() = default;
bool encode_url(const std::string& suffix);
unsigned char* get_map();
unsigned get_width() const;
};
}

45
client/reader_timer.cpp Normal file
View File

@ -0,0 +1,45 @@
#include "reader_timer.hpp"
#include "factory.hpp"
#include <boost/date_time.hpp>
#include <boost/bind.hpp>
namespace acs
{
void reader_timer::callback_handler(const boost::system::error_code& ec)
{
if (ec)
exit(1);
auto factory = factory::get();
auto state = factory->get_state();
if (state->get_status() == state::idle) {
auto reader = factory->get_reader();
auto uid = reader->find_card();
if (uid) {
std::cout << "UID: " << uid << std::endl;
auto stu_num = reader->get_student_number();
if (conn_->get_stunum(uid) == stu_num) {
controller_->authorized();
} else {
controller_->forbidden();
}
}
}
defer();
}
void reader_timer::defer()
{
timer_.expires_from_now(boost::posix_time::millisec(200));
timer_.async_wait(boost::bind(
&reader_timer::callback_handler, this, boost::asio::placeholders::error));
}
reader_timer::reader_timer() : loop_(
factory::get()->get_loop()), timer_(
*loop_->get_io_service()), controller_(
factory::get()->get_controller()), conn_(factory::get()->get_mysql())
{
defer();
}
}

26
client/reader_timer.hpp Normal file
View File

@ -0,0 +1,26 @@
#pragma once
#include <boost/asio.hpp>
namespace acs
{
class loop;
class access_controller;
class mysql_conn;
class reader_timer
{
loop* loop_;
boost::asio::deadline_timer timer_;
access_controller* controller_;
mysql_conn* conn_;
void callback_handler(const boost::system::error_code& ec);
void defer();
public:
explicit reader_timer();
};
}

View File

@ -0,0 +1,41 @@
#include "request_delegate.hpp"
#include "http_client.hpp"
#include "factory.hpp"
#include <json.hpp>
namespace acs
{
void request_delegate::response_handler(const std::string& response_body) const
{
if (response_body == "") {
callback_({ {}, {} });
return;
}
boost_helper::json response(response_body);
if (!response.has("err")) {
std::cerr << "Bad response. " << response_body << std::endl;
callback_({ {}, {} });
return;
}
if (response["err"].val() != "0") {
std::cerr << "Server responded with error " <<
response["err"].val() << response["data"].val() << std::endl;
callback_({ {}, {} });
return;
}
callback_({ response["data"]["token"].val(), response["data"]["stunum"].val() });
}
void request_delegate::execute(const callback_t& callback)
{
callback_ = callback;
// Unfortunately we can't DI client here.
// There's a bug in lower versions of Boost.Beast,
// resulting flat_buffer instance not being cleared properly.
delete client_;
client_ = new http_client;
client_->async_get(host, query, boost::bind(
&request_delegate::response_handler, this, _1));
}
}

View File

@ -0,0 +1,29 @@
#pragma once
#include <functional>
namespace acs
{
class http_client;
class request_delegate
{
http_client* client_ = nullptr;
static constexpr auto host = "api.cismon.net";
static constexpr auto query = "/client/1/testacsclient";
using callback_t = std::function<void(const std::pair<std::string, std::string>&)>;
callback_t callback_;
void response_handler(const std::string& response_body) const;
public:
explicit request_delegate() = default;
void execute(const callback_t& callback);
};
}

53
client/request_timer.cpp Normal file
View File

@ -0,0 +1,53 @@
#include "request_timer.hpp"
#include "factory.hpp"
namespace acs
{
void request_timer::response_handler(const std::pair<std::string, std::string>& response)
{
auto factory = factory::get();
auto token = response.first;
auto stunum = response.second;
if (token != "")
{
auto qr_encoder = factory->get_qr_encoder();
qr_encoder->encode_url(token);
if (stunum != "") {
if (conn_->stunum_exists(stunum))
controller_->authorized();
else
controller_->forbidden();
} else {
controller_->force_reset_if_idle();
}
}
defer();
}
void request_timer::callback_handler(const boost::system::error_code& ec)
{
if (ec)
exit(1);
auto factory = factory::get();
auto state = factory->get_state();
if (state->get_status() == state::idle)
request_delegate_->execute(boost::bind(&request_timer::response_handler, this, _1));
}
void request_timer::defer()
{
timer_.expires_from_now(boost::posix_time::millisec(5'000));
timer_.async_wait(boost::bind(
&request_timer::callback_handler, this, boost::asio::placeholders::error));
}
request_timer::request_timer() : loop_(
factory::get()->get_loop()), timer_(
*loop_->get_io_service()), conn_(
factory::get()->get_mysql()), request_delegate_(
factory::get()->get_request_delegate()), controller_(
factory::get()->get_controller())
{
defer();
}
}

30
client/request_timer.hpp Normal file
View File

@ -0,0 +1,30 @@
#pragma once
#include <boost/asio.hpp>
namespace acs
{
class loop;
class mysql_conn;
class request_delegate;
class access_controller;
class request_timer
{
loop* loop_;
boost::asio::deadline_timer timer_;
mysql_conn* conn_;
request_delegate* request_delegate_;
access_controller* controller_;
void response_handler(const std::pair<std::string, std::string>& response);
void callback_handler(const boost::system::error_code& ec);
void defer();
public:
explicit request_timer();
};
}

25
client/state.hpp Normal file
View File

@ -0,0 +1,25 @@
#pragma once
namespace acs
{
class state
{
public:
enum status {
idle, entry_await
};
private:
status status_ = idle;
public:
explicit state() = default;
status get_status() const
{
return status_;
}
void set_status(status new_status)
{
status_ = new_status;
}
};
}

10
server/acs Normal file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env php
<?php
ini_set('memory_limit', '512M');
require_once __DIR__ . '/constants.php';
require_once __DIR__ . '/vendor/autoload.php';
require_once __DIR__ . '/src/main.php';
use Acast\Server;
Server::start();

7
server/composer.json Normal file
View File

@ -0,0 +1,7 @@
{
"require" : {
"php": ">=7.1",
"cismonx/acast": "^1.2",
"twt/sso": "^1.0"
}
}

12
server/constants.php Normal file
View File

@ -0,0 +1,12 @@
<?php
const _MEMCACHED_HOST = '127.0.0.1';
const _MEMCACHED_PORT = '11211';
const _MYSQL_CONFIG = [
'host' => '127.0.0.1',
'port' => '3306',
'user' => 'root',
'password' => 'generator',
'db_name' => 'acs',
'charset' => 'utf8mb4'
];

View File

@ -0,0 +1,55 @@
<?php
namespace acs\Controller;
use Acast\Http\Config;
use Acast\Http\Controller;
use Acast\Http\Router;
use TwT\SSO;
use Workerman\Protocols\Http;
class Auth extends Controller
{
/**
* @var \acs\View\Auth
*/
protected $view;
/**
* @var \acs\Model\Client
*/
protected $model;
/**
* @var \TwT\SSO\Api
*/
protected $sso_api;
function __construct(Router $router)
{
parent::__construct($router);
$this->sso_api = new SSO\Api(Config::get('TWT_SSO_ID'), Config::get('TWT_SSO_KEY'));
}
function init()
{
$token = strval($this->params['token']);
$login_url = $this->sso_api->getLoginUrl('https://api.cismon.net/auth-confirm/'.$token);
Http::header('Location: '.$login_url);
}
function confirm()
{
$auth_token = strval($this->params['token']);
$token = strval($_GET['token']);
$stunum = strval($this->sso_api->fetchUserInfo($token)->result->user_number);
if ($stunum != '') {
if ($this->model->updateStunum($auth_token, $stunum)) {
$this->view->retJson("Auth complete, waiting.");
return;
}
$this->view->retJson("Pending auth or bad token.", 3);
return;
}
$this->view->retJson("Auth failed.", 4);
}
}

View File

@ -0,0 +1,59 @@
<?php
namespace acs\Controller;
use Acast\Http\Config;
use Acast\Http\Controller;
use Acast\Http\Router;
class Client extends Controller
{
/**
* @var \acs\View\Client
*/
protected $view;
/**
* @var \acs\Model\Client
*/
protected $model;
protected $id;
protected $key;
function __construct(Router $router)
{
parent::__construct($router);
$this->id = intval($this->params['id']);
$this->key = strval($this->params['key']);
}
function tick()
{
if ($this->id < 1 || empty($this->key)) {
$this->view->retJson('Bad request.', -1, 400);
return;
}
$result = $this->model->fetchById($this->id);
if (!$result || $result['key'] != $this->key) {
$this->view->retJson('Invalid credentials.', 1, 403);
return;
}
$token = '';
$expired = time() > $result['updated'] + Config::get('EXPIRY_TIME');
// No token yet; Token expired; Token used.
if ($result['token'] == '' || $expired || $result['stunum'] != '') {
$token = strtr(base64_encode(random_bytes(15)), '+/', '-_');
if (!$this->model->updateToken($this->id, $token)) {
$this->view->retJson('Failed to update token', -3, 500);
return;
}
}
$stunum = strval($result['stunum']);
$this->view->retJson([
'token' => $token,
'stunum' => $stunum
]);
}
}

View File

@ -0,0 +1,13 @@
<?php
namespace acs;
use Acast\Http\Config;
function init_config() {
Config::setArray([
'TWT_SSO_ID' => 13,
'TWT_SSO_KEY' => 'mBA0G56tzc1L2DbY2oXV',
'EXPIRY_TIME' => 300
]);
}

View File

@ -0,0 +1,15 @@
<?php
namespace acs;
use Acast\Http\Console;
use Acast\Http\Model;
use Acast\Http\Server;
function init_database()
{
if (!Server::$memcached->addServer(_MEMCACHED_HOST, _MEMCACHED_PORT))
Console::warning('Failed to add memcached server.');
//Init mysql connection.
Model::config(_MYSQL_CONFIG);
}

View File

@ -0,0 +1,21 @@
<?php
namespace acs;
use Acast\Http\Router;
use Acast\Http\Server;
function init_routes()
{
$router = Router::create('acs');
$router->add(['auth', '/token'], 'GET', function () {
$this->invoke();
})->bind(['Auth', 'init']);
$router->add(['client', '/id', '/key'], 'GET', function () {
$this->invoke();
})->bind(['Client', 'tick']);
$router->add(['auth-confirm', '/token'], 'GET', function () {
$this->invoke();
})->bind(['Auth', 'confirm']);
Server::app('acs')->route('acs');
}

View File

@ -0,0 +1,15 @@
<?php
namespace acs;
function init_structure()
{
foreach (glob(__DIR__ . '/../Controller/*.php') as $file)
require_once $file;
require_once __DIR__ . '/../Model/Misc/Base.php';
foreach (glob(__DIR__ . '/../Model/*.php') as $file)
require_once $file;
require_once __DIR__ . '/../View/Misc/Base.php';
foreach (glob(__DIR__ . '/../View/*.php') as $file)
require_once $file;
}

View File

@ -0,0 +1,8 @@
<?php
namespace acs\Model;
class Auth extends Base
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace acs\Model;
class Client extends Base {
}

View File

@ -0,0 +1,40 @@
<?php
namespace acs\Model;
use Acast\Http\Model;
class Base extends Model
{
function __construct()
{
$this->table('client');
}
function fetchById($id)
{
return $this->_select(['`key`', 'token', 'stunum', 'UNIX_TIMESTAMP(updated) AS updated'],
'id='.$id, null, null, null, self::ROW);
}
function fetchByToken($token)
{
if (empty($token))
return false;
return $this->_select(['id', '`key`', 'stunum', 'UNIX_TIMESTAMP(updated) AS updated'],
'token=:t', ['t' => $token], null, null, self::ROW);
}
function updateToken($id, $token)
{
return $this->_update(['token', 'stunum'], 'id='.$id, ['token' => $token, 'stunum' => ''], 1);
}
function updateStunum($token, $stunum)
{
if (empty($token))
return 0;
return $this->_update(['stunum'], ['token=:t', 'stunum=\'\''], [
't' => $token,
'stunum' => $stunum
], 1);
}
}

7
server/src/View/Auth.php Normal file
View File

@ -0,0 +1,7 @@
<?php
namespace acs\View;
class Auth extends Base {
}

View File

@ -0,0 +1,7 @@
<?php
namespace acs\View;
class Client extends Base {
}

View File

@ -0,0 +1,20 @@
<?php
namespace acs\View;
use Acast\Http\View;
use Workerman\Protocols\Http;
abstract class Base extends View
{
function retJson($msg, int $err = 0, ?int $http_code = null)
{
Http::header('Content-Type: application/json');
if (isset($http_code))
Http::header('HTTP', $http_code);
$this->_controller->retMsg = json_encode([
'err' => $err,
'data' => $msg
]);
}
}

24
server/src/main.php Normal file
View File

@ -0,0 +1,24 @@
<?php
namespace acs;
use Acast\Http\Server;
use Workerman\Worker;
foreach (glob(__DIR__.'/Init/*.php') as $init_file)
require_once $init_file;
Server::create('acs', '[::]:7722');
Worker::$pidFile = __DIR__ . '/workerman.pid';
Server::app('acs')->workerConfig([
'name' => 'acs',
'count' => 1
]);
Server::app('acs')->event('WorkerStart', function () {
init_config();
init_structure();
init_routes();
init_database();
});