├── syslog.conf ├── .gitmodules ├── testdata ├── scan0.rle └── brother.config ├── brscand.ufw_profile ├── brother-scand.service ├── out ├── example.config ├── savejpeg.sh ├── scanhook.sh ├── remote_upload.sh ├── ocr.sh ├── ocr-fake-duplex-adf.sh └── brother.config ├── device_handler.h ├── con_queue.h ├── snmp.h ├── device_handler_test.cpp ├── event_thread.h ├── log.h ├── LICENSE.md ├── con_queue.c ├── connection_test.cpp ├── data_channel.h ├── connection.h ├── CMakeLists.txt ├── scanner_cli.c ├── main.c ├── config.h ├── check_mk.imp ├── Makefile ├── log.c ├── README.md ├── connection_base_test.h ├── snmp.c ├── rle_to_tiff.py ├── data_channel_test.cpp ├── event_thread.c ├── config.c ├── connection.c ├── device_handler.c └── data_channel.c /syslog.conf: -------------------------------------------------------------------------------- 1 | if $programname == 'brother-scand' then /var/log/brother-scand.log 2 | & stop 3 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "ber"] 2 | path = ber 3 | url = https://github.com/darsto/ber.git 4 | -------------------------------------------------------------------------------- /testdata/scan0.rle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rumpeltux/brother-scand/HEAD/testdata/scan0.rle -------------------------------------------------------------------------------- /brscand.ufw_profile: -------------------------------------------------------------------------------- 1 | [brscand] 2 | title=Brother Scanner Driver Service 3 | description=scanner driver 4 | ports=68,138,137,161,49424,54921,54925/udp 5 | -------------------------------------------------------------------------------- /testdata/brother.config: -------------------------------------------------------------------------------- 1 | # Loopback device config for unit tests 2 | ip 127.0.0.1 3 | preset default FILE 4 | network.page.finish.timeout 2 5 | network.page.init.timeout 2 -------------------------------------------------------------------------------- /brother-scand.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=Brother Scanner Driver Service 3 | 4 | [Service] 5 | Type=simple 6 | User=brother-scand 7 | WorkingDirectory=/etc/brother-scand 8 | ExecStart=/usr/bin/brother-scand -c scanner.conf 9 | StandardOutput=syslog 10 | StandardError=syslog 11 | SyslogIdentifier=brother-scand 12 | 13 | [Install] 14 | WantedBy=multi-user.target 15 | -------------------------------------------------------------------------------- /out/example.config: -------------------------------------------------------------------------------- 1 | # global defaults 2 | scan.func ./savejpeg.sh 3 | 4 | # presets 5 | define-preset text 6 | scan.param C RLENGTH 7 | scan.param M TEXT 8 | 9 | define-preset grayscale 10 | scan.param M GRAY64 11 | 12 | # devices 13 | ip 192.168.1.2 14 | preset text OCR 15 | scan.func ./ocr.sh 16 | 17 | preset text OCR 18 | scan.func ./ocr-fake-duplex-adf.sh 19 | hostname duplex 20 | 21 | preset grayscale FILE 22 | preset default IMAGE 23 | -------------------------------------------------------------------------------- /device_handler.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #ifndef BROTHER_DEVICE_HANDLER_H 8 | #define BROTHER_DEVICE_HANDLER_H 9 | 10 | struct device_config; 11 | 12 | void device_handler_init(void); 13 | struct device *device_handler_add_device(struct device_config *config); 14 | 15 | #endif //BROTHER_DEVICE_HANDLER_H 16 | -------------------------------------------------------------------------------- /con_queue.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT license. 4 | * Feel free to reuse, modify and distribute it. 5 | */ 6 | 7 | #ifndef BROTHER_CONQUEUE_H 8 | #define BROTHER_CONQUEUE_H 9 | 10 | #include 11 | #include 12 | 13 | struct con_queue { 14 | atomic_size_t head, tail; 15 | size_t size; 16 | void *data[]; 17 | }; 18 | 19 | int con_queue_push(struct con_queue *queue, void *element); 20 | int con_queue_pop(struct con_queue *queue, void **element); 21 | 22 | #endif //BROTHER_CONQUEUE_H 23 | -------------------------------------------------------------------------------- /snmp.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #ifndef BROTHER_SNMP_H 8 | #define BROTHER_SNMP_H 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | struct brother_conn; 16 | 17 | int snmp_get_printer_status(struct brother_conn *conn, 18 | uint8_t *buf, size_t buf_len, in_addr_t dest_addr); 19 | int snmp_register_scanner_driver(struct brother_conn *conn, bool enabled, 20 | const char **functions, 21 | size_t functions_length, in_addr_t dest_addr); 22 | 23 | #endif //BROTHER_SNMP_H 24 | -------------------------------------------------------------------------------- /device_handler_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "gtest/gtest.h" 4 | 5 | extern "C" { 6 | #include "device_handler.h" 7 | 8 | char *device_handler_extract_hostname(const char *buf); 9 | } 10 | 11 | struct DeviceHandlerTest : public ::testing::Test {}; 12 | 13 | TEST_F(DeviceHandlerTest, FindHostname) { 14 | EXPECT_EQ(device_handler_extract_hostname("foobar"), nullptr); 15 | EXPECT_EQ(device_handler_extract_hostname("abc;USER=\"foobar"), nullptr); 16 | EXPECT_STREQ(device_handler_extract_hostname("abc;USER=\"foobar\";abc"), 17 | "foobar"); 18 | const char *buf = 19 | "TYPE=BR;BUTTON=SCAN;USER=\"mymachine\";FUNC=FILE;HOST=192.168.1.2:54925;" 20 | "APPNUM=1;P1=0;P2=0;P3=0;P4=0;REGID=27164;SEQ=22;"; 21 | EXPECT_STREQ(device_handler_extract_hostname(buf), "mymachine"); 22 | } 23 | -------------------------------------------------------------------------------- /event_thread.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #ifndef BROTHER_EVENT_THREAD_H 8 | #define BROTHER_EVENT_THREAD_H 9 | 10 | struct event_thread; 11 | 12 | void event_thread_lib_init(void); 13 | void event_thread_lib_wait(void); 14 | void event_thread_lib_shutdown(void); 15 | 16 | struct event_thread *event_thread_create(const char *name, 17 | void (*update_cb)(void *), 18 | void (*stop_cb)(void *), void *arg); 19 | int event_thread_enqueue_event(struct event_thread *thread, 20 | void (*callback)(void *, void *), 21 | void *arg1, void *arg2); 22 | int event_thread_pause(struct event_thread *thread); 23 | int event_thread_kick(struct event_thread *thread); 24 | int event_thread_stop(struct event_thread *thread); 25 | struct event_thread *event_thread_self(void); 26 | 27 | #endif //BROTHER_EVENT_THREAD_H 28 | -------------------------------------------------------------------------------- /out/savejpeg.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ################################################################################ 3 | # 4 | # The following environment variables will be set: 5 | # 6 | # SCANNER_XDPI 7 | # SCANNER_YDPI 8 | # SCANNER_HEIGHT 9 | # SCANNER_WIDTH 10 | # SCANNER_PAGE (starts from 1) 11 | # SCANNER_IP 12 | # SCANNER_SCANID (to group a set of scanned pages) 13 | # SCANNER_HOSTNAME (as selected on the device) 14 | # SCANNER_FUNC (as selected on the device) 15 | # SCANNER_FILENAME (where the received data was stored), e.g. scan123.jpg 16 | # 17 | # The script is also called after all pages of a set have been received, 18 | # in this case SCANNER_FILENAME will not be set. 19 | # For a set of pages, you can expect the variables to stay the same (except for 20 | # SCANNER_PAGE and SCANNER_FILENAME). 21 | # 22 | ################################################################################ 23 | 24 | # Saves the currently received page to a JPEG file. 25 | 26 | if [ ! -z "$SCANNER_FILENAME" ]; then 27 | mv $SCANNER_FILENAME $(date "+%Y-%m-%d_%H%M%S").jpg 28 | fi 29 | -------------------------------------------------------------------------------- /log.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #ifndef BROTHER_LOG_H 8 | #define BROTHER_LOG_H 9 | 10 | #include 11 | 12 | enum { LEVEL_DEBUG, LEVEL_INFO, LEVEL_WARN, LEVEL_ERR, LEVEL_FATAL }; 13 | 14 | #define LOG_DEBUG(...) log_printf(LEVEL_DEBUG, __FILE__, __LINE__, __VA_ARGS__) 15 | #define LOG_INFO(...) log_printf(LEVEL_INFO, __FILE__, __LINE__, __VA_ARGS__) 16 | #define LOG_WARN(...) log_printf(LEVEL_WARN, __FILE__, __LINE__, __VA_ARGS__) 17 | #define LOG_ERR(...) log_printf(LEVEL_ERR, __FILE__, __LINE__, __VA_ARGS__) 18 | #define LOG_FATAL(...) log_printf(LEVEL_FATAL, __FILE__, __LINE__, __VA_ARGS__) 19 | 20 | #define DUMP_DEBUG(...) hexdump(LEVEL_DEBUG, __VA_ARGS__) 21 | #define DUMP_ERR(...) hexdump(LEVEL_ERR, __VA_ARGS__) 22 | 23 | void log_set_level(int level); 24 | void log_printf(int level, const char *file, int line, const char *fmt, ...); 25 | void hexdump(int level, const void *data, size_t len); 26 | 27 | #endif //DCP_J105_LOG_H 28 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Dariusz Stojaczyk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /con_queue.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #include "con_queue.h" 8 | 9 | static size_t 10 | con_queue_next(struct con_queue *queue, size_t num) 11 | { 12 | return ++num % queue->size; 13 | } 14 | 15 | int 16 | con_queue_push(struct con_queue *queue, void *element) 17 | { 18 | size_t currentTail = atomic_load(&queue->tail); 19 | size_t newTail = con_queue_next(queue, currentTail); 20 | 21 | if (newTail == atomic_load(&queue->head)) { 22 | return -1; 23 | } 24 | 25 | queue->data[currentTail] = element; 26 | atomic_store(&queue->tail, newTail); 27 | 28 | return 0; 29 | } 30 | 31 | int 32 | con_queue_pop(struct con_queue *queue, void **element) 33 | { 34 | size_t currentHead = atomic_load(&queue->head); 35 | 36 | if (currentHead == atomic_load(&queue->tail)) { 37 | return -1; 38 | } 39 | 40 | *element = queue->data[currentHead]; 41 | atomic_store(&queue->head, con_queue_next(queue, currentHead)); 42 | 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /out/scanhook.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | ################################################################################ 3 | # 4 | # The following environment variables will be set: 5 | # 6 | # SCANNER_XDPI 7 | # SCANNER_YDPI 8 | # SCANNER_HEIGHT 9 | # SCANNER_WIDTH 10 | # SCANNER_PAGE (starts from 1) 11 | # SCANNER_IP 12 | # SCANNER_SCANID (to group a set of scanned pages) 13 | # SCANNER_HOSTNAME (as selected on the device) 14 | # SCANNER_FUNC (as selected on the device) 15 | # SCANNER_FILENAME (where the received data was stored), e.g. scan123.jpg 16 | # 17 | # The script is also called after all pages of a set have been received, 18 | # in this case SCANNER_FILENAME will not be set. 19 | # For a set of pages, you can expect the variables to stay the same (except for 20 | # SCANNER_PAGE and SCANNER_FILENAME). 21 | # 22 | ################################################################################ 23 | 24 | # This is just an example script. It doesn't actually save any data. 25 | 26 | if [ ! -z "$SCANNER_FILENAME" ]; then 27 | "Received page $SCANNER_PAGE page(s) from $SCANNER_IP: $SCANNER_FILENAME" 28 | exit 0 29 | fi 30 | 31 | notify-send "Received $SCANNER_PAGE page(s) from $SCANNER_IP (${SCANNER_WIDTH}x${SCANNER_HEIGHT} px; ${SCANNER_XDPI}x${SCANNER_YDPI} DPI)" 32 | -------------------------------------------------------------------------------- /connection_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include "connection_base_test.h" 9 | #include "gtest/gtest.h" 10 | extern "C" { 11 | #include "connection.h" 12 | } 13 | 14 | struct ConnectionTest : public ConnectionBaseTest {}; 15 | 16 | TEST_F(ConnectionTest, TestChunkParsing) { 17 | std::unique_ptr client = accept_client(); 18 | ASSERT_C_OK(brother_conn_reconnect((struct brother_conn *)conn_, 19 | htonl(INADDR_LOOPBACK), 20 | htons(BROTHER_CONNECTION_BASE_TEST_PORT))); 21 | ASSERT_C_OK(client->get_client_fd()); 22 | EXPECT_EQ(brother_conn_data_available(conn_), 0); 23 | client->write("ABCD"); 24 | EXPECT_EQ(brother_conn_data_available(conn_), 0); 25 | ASSERT_C_OK(brother_conn_fill_buffer(conn_, /*size=*/1, /*timeout=*/1)); 26 | EXPECT_EQ(brother_conn_data_available(conn_), 4); 27 | EXPECT_EQ(std::string((char *)brother_conn_peek(conn_, 2), 2), "AB"); 28 | EXPECT_EQ(std::string((char *)brother_conn_read(conn_, 2), 2), "AB"); 29 | client->write("EFGH"); 30 | EXPECT_EQ(std::string((char *)brother_conn_peek(conn_, 4), 4), "CDEF"); 31 | EXPECT_EQ(std::string((char *)brother_conn_read(conn_, 4), 4), "CDEF"); 32 | bool thread_ran = false; 33 | std::thread receive([this, &thread_ran]() { 34 | EXPECT_EQ(std::string((char *)brother_conn_read(conn_, 4), 4), "GHIJ"); 35 | thread_ran = true; 36 | }); 37 | usleep(2000); 38 | client->write("IJKL"); 39 | receive.join(); 40 | EXPECT_TRUE(thread_ran); 41 | } 42 | -------------------------------------------------------------------------------- /data_channel.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #ifndef BROTHER_DATA_CHANNEL_H 8 | #define BROTHER_DATA_CHANNEL_H 9 | 10 | #include 11 | 12 | #include "config.h" 13 | 14 | #define DATA_CHANNEL_TARGET_PORT 54921 15 | 16 | struct data_channel { 17 | struct brother_conn *conn; 18 | int (*process_cb)(struct data_channel *data_channel); 19 | 20 | FILE *tempfile; 21 | int scan_id; 22 | 23 | struct data_channel_page_data { 24 | int id; 25 | int remaining_chunk_bytes; 26 | } page_data; 27 | 28 | unsigned scanned_pages; 29 | struct event_thread *thread; 30 | 31 | struct scan_param params[CONFIG_SCAN_MAX_PARAMS]; 32 | 33 | int xdpi; 34 | int ydpi; 35 | int width; 36 | int height; 37 | 38 | const struct device_config *config; 39 | // The current hostname/scan_func item that's being processed. 40 | const struct item_config *item; 41 | // The format of the current file: one of "raw", "rle", "jpeg" 42 | const char *file_format; 43 | }; 44 | 45 | int data_channel_set_paused(struct data_channel *data_channel); 46 | struct data_channel *data_channel_create(struct device_config *config); 47 | int data_channel_init_connection(struct data_channel *data_channel); 48 | int data_channel_init(struct data_channel *data_channel); 49 | void data_channel_kick(struct data_channel *data_channel); 50 | 51 | // arg is a struct data_channel 52 | void data_channel_loop(void *data_channel); 53 | void data_channel_set_item(struct data_channel *data_channel, 54 | const struct item_config *item); 55 | 56 | #endif //BROTHER_DATA_CHANNEL_H 57 | -------------------------------------------------------------------------------- /connection.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #ifndef BROTHER_CONNECTION_H 11 | #define BROTHER_CONNECTION_H 12 | 13 | #define BROTHER_CONN_READ_BUFSIZE 2048 14 | 15 | enum brother_connection_type { 16 | BROTHER_CONNECTION_TYPE_UDP, 17 | BROTHER_CONNECTION_TYPE_TCP, 18 | }; 19 | 20 | struct brother_conn; 21 | 22 | struct brother_conn *brother_conn_open(enum brother_connection_type type, unsigned timeout_sec); 23 | int brother_conn_bind(struct brother_conn *conn, in_port_t local_port); 24 | void brother_conn_disconnect(struct brother_conn *conn); 25 | int brother_conn_reconnect(struct brother_conn *conn, in_addr_t dest_addr, 26 | in_port_t dest_port); 27 | int brother_conn_poll(struct brother_conn *conn, unsigned timeout_sec); 28 | int brother_conn_send(struct brother_conn *conn, const void *buf, size_t len); 29 | int brother_conn_sendto(struct brother_conn *conn, const void *buf, size_t len, 30 | in_addr_t dest_addr, in_port_t dest_port); 31 | size_t brother_conn_data_available(struct brother_conn *conn); 32 | int brother_conn_receive(struct brother_conn *conn, void *buf, size_t len); 33 | int brother_conn_fill_buffer(struct brother_conn *conn, size_t size, 34 | int timeout_seconds); 35 | int brother_conn_get_client_ip(struct brother_conn *conn, char ip[16]); 36 | int brother_conn_get_local_ip(struct brother_conn *conn, char ip[16]); 37 | void brother_conn_close(struct brother_conn *conn); 38 | 39 | void *brother_conn_peek(struct brother_conn *conn, size_t len); 40 | void *brother_conn_read(struct brother_conn *conn, size_t len); 41 | 42 | #endif //BROTHER_CONNECTION_H 43 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10.2) 2 | project(brother_scanner_driver C CXX) 3 | 4 | set(CMAKE_C_STANDARD 11) 5 | 6 | find_package(Threads) 7 | 8 | include_directories(.) 9 | add_library(ber ber/ber.c ber/ber.h ber/snmp.c ber/snmp.h) 10 | 11 | add_library(scanner_driver 12 | con_queue.c 13 | con_queue.h 14 | config.c 15 | config.h 16 | connection.c 17 | connection.h 18 | data_channel.c 19 | data_channel.h 20 | event_thread.c 21 | event_thread.h 22 | log.c 23 | log.h) 24 | target_link_libraries(scanner_driver ${CMAKE_THREAD_LIBS_INIT}) 25 | add_library(button_driver 26 | device_handler.c 27 | device_handler.h 28 | snmp.c 29 | snmp.h) 30 | target_link_libraries(button_driver scanner_driver ber) 31 | 32 | add_executable(brother_scanner_driver main.c) 33 | 34 | add_executable(brother_scanner_cli scanner_cli.c) 35 | 36 | target_link_libraries(brother_scanner_driver button_driver) 37 | target_link_libraries(brother_scanner_cli scanner_driver) 38 | 39 | add_subdirectory("/usr/src/googletest" ${CMAKE_BINARY_DIR}/gtest) 40 | enable_testing() 41 | include_directories(${GTEST_INCLUDE_DIRS}) 42 | 43 | add_executable(tests device_handler_test.cpp connection_test.cpp data_channel_test.cpp connection_base_test.h) 44 | target_link_libraries(tests button_driver gtest gtest_main) 45 | add_test(AllTests tests) 46 | 47 | # Generate clang compilation database 48 | set(CMAKE_EXPORT_COMPILE_COMMANDS ON) 49 | 50 | find_package(PythonInterp) 51 | find_program(iwyu_tool_path NAMES iwyu_tool) 52 | if (iwyu_tool_path AND PYTHONINTERP_FOUND) 53 | add_custom_target(iwyu 54 | COMMAND "${PYTHON_EXECUTABLE}" "${iwyu_tool_path}" -p "${CMAKE_BINARY_DIR}" -- --mapping_file ../check_mk.imp 55 | COMMENT "Running include-what-you-use tool" 56 | VERBATIM 57 | ) 58 | endif() 59 | -------------------------------------------------------------------------------- /out/remote_upload.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ################################################################################ 3 | # 4 | # The following environment variables will be set: 5 | # 6 | # SCANNER_XDPI 7 | # SCANNER_YDPI 8 | # SCANNER_HEIGHT 9 | # SCANNER_WIDTH 10 | # SCANNER_PAGE (starts from 1) 11 | # SCANNER_IP 12 | # SCANNER_SCANID (to group a set of scanned pages) 13 | # SCANNER_HOSTNAME (as selected on the device) 14 | # SCANNER_FUNC (as selected on the device) 15 | # SCANNER_FILENAME (where the received data was stored), e.g. scan123.jpg 16 | # 17 | # The script is also called after all pages of a set have been received, 18 | # in this case SCANNER_FILENAME will not be set. 19 | # For a set of pages, you can expect the variables to stay the same (except for 20 | # SCANNER_PAGE and SCANNER_FILENAME). 21 | # 22 | ################################################################################ 23 | # 24 | # Do the heavy lifting of scan processing remotely. 25 | # Invokes a remote script and passes the environment params as HTTP headers. 26 | # 27 | # If SCANNER_FILENAME is set, sends a POST request with the files’ contents 28 | # as binary data. Otherwise (for the final invocation), sends a GET request. 29 | # 30 | set -x -e 31 | URL=$1 32 | 33 | if [ ! -z "$SCANNER_FILENAME" ]; then 34 | UPLOAD_CMD="--data-binary @$SCANNER_FILENAME" 35 | fi 36 | 37 | curl -v $UPLOAD_CMD $URL \ 38 | -H "SCANNER_XDPI: $SCANNER_XDPI" \ 39 | -H "SCANNER_YDPI: $SCANNER_YDPI" \ 40 | -H "SCANNER_HEIGHT: $SCANNER_HEIGHT" \ 41 | -H "SCANNER_WIDTH: $SCANNER_WIDTH" \ 42 | -H "SCANNER_PAGE: $SCANNER_PAGE" \ 43 | -H "SCANNER_IP: $SCANNER_IP" \ 44 | -H "SCANNER_SCANID: $SCANNER_SCANID" \ 45 | -H "SCANNER_HOSTNAME: $SCANNER_HOSTNAME" \ 46 | -H "SCANNER_FUNC: $SCANNER_FUNC" \ 47 | -H 'Expect:' 48 | 49 | if [ ! -z "$SCANNER_FILENAME" ]; then 50 | rm $SCANNER_FILENAME 51 | fi 52 | -------------------------------------------------------------------------------- /scanner_cli.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2019 Hagen Fritsch. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "config.h" 14 | #include "data_channel.h" 15 | #include "log.h" 16 | 17 | static void print_usage(char **argv) { 18 | printf( 19 | "Usage: %s [-c path/to/config/file]\n" 20 | " -h print this help\n" 21 | " -d debug output\n", 22 | argv[0]); 23 | } 24 | 25 | static void print_version(void) { 26 | printf("Brother scanner cli. Build " __DATE__ " " __TIME__ "\n"); 27 | } 28 | 29 | int main(int argc, char *argv[]) { 30 | int option = 0; 31 | const char *config_path = "brother.config"; 32 | 33 | while ((option = getopt(argc, argv, "c:dh")) != -1) { 34 | switch (option) { 35 | case 'c': 36 | config_path = optarg; 37 | break; 38 | case 'd': 39 | log_set_level(LEVEL_DEBUG); 40 | break; 41 | case 'h': 42 | print_version(); 43 | exit(EXIT_SUCCESS); 44 | default: 45 | print_usage(argv); 46 | exit(EXIT_FAILURE); 47 | } 48 | } 49 | 50 | if (config_init(config_path) != 0) { 51 | fprintf(stderr, "Fatal: could not init config.\n"); 52 | return -1; 53 | } 54 | 55 | struct data_channel data_channel; 56 | bzero(&data_channel, sizeof(data_channel)); 57 | data_channel.config = TAILQ_FIRST(&g_config.devices); 58 | // TODO: choose preset by name and func 59 | data_channel_set_item(&data_channel, 60 | TAILQ_FIRST(&data_channel.config->items)); 61 | data_channel_init(&data_channel); 62 | data_channel_init_connection(&data_channel); 63 | while (data_channel.process_cb != data_channel_set_paused) { 64 | data_channel_loop(&data_channel); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /main.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "config.h" 13 | #include "device_handler.h" 14 | #include "event_thread.h" 15 | #include "log.h" 16 | 17 | static void 18 | sig_handler(int signo) 19 | { 20 | printf("Received signal %d, quitting..\n", signo); 21 | event_thread_lib_shutdown(); 22 | } 23 | 24 | static void print_usage(char **argv) { 25 | printf( 26 | "Usage: %s [-c path/to/config/file]\n" 27 | " -h print this help\n" 28 | " -d debug output\n", 29 | argv[0]); 30 | } 31 | 32 | static void 33 | print_version(void) 34 | { 35 | printf("Brother scanner daemon. Build " __DATE__ " " __TIME__ "\n"); 36 | } 37 | 38 | int 39 | main(int argc, char *argv[]) 40 | { 41 | int option = 0; 42 | const char *config_path = "brother.config"; 43 | 44 | while ((option = getopt(argc, argv, "c:dh")) != -1) { 45 | switch (option) { 46 | case 'c': 47 | config_path = optarg; 48 | break; 49 | case 'd': 50 | log_set_level(LEVEL_DEBUG); 51 | break; 52 | case 'h': 53 | print_version(); 54 | exit(EXIT_SUCCESS); 55 | default: 56 | print_usage(argv); 57 | exit(EXIT_FAILURE); 58 | } 59 | } 60 | 61 | event_thread_lib_init(); 62 | 63 | if (signal(SIGINT, sig_handler) == SIG_ERR) { 64 | fprintf(stderr, "Failed to bind SIGINT handler.\n"); 65 | return 1; 66 | } 67 | 68 | if (config_init(config_path) != 0) { 69 | fprintf(stderr, "Fatal: could not init config.\n"); 70 | return -1; 71 | } 72 | 73 | device_handler_init(); 74 | 75 | event_thread_lib_wait(); 76 | return 0; 77 | } 78 | -------------------------------------------------------------------------------- /config.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #ifndef BROTHER_CONFIG_H 8 | #define BROTHER_CONFIG_H 9 | 10 | #include 11 | 12 | #define CONFIG_HOSTNAME_LENGTH 16 13 | #define CONFIG_SCAN_MAX_PARAMS 16 14 | #define CONFIG_SCAN_MAX_FUNCS 4 15 | #define CONFIG_NETWORK_DEFAULT_TIMEOUT_SEC 5 16 | #define CONFIG_NETWORK_DEFAULT_PAGE_INIT_TIMEOUT 15 17 | #define CONFIG_NETWORK_DEFAULT_PAGE_FINISH_TIMEOUT 60 18 | 19 | struct scan_param { 20 | char id; 21 | char value[16]; 22 | }; 23 | 24 | struct device_config { 25 | char *ip; 26 | unsigned timeout; 27 | TAILQ_HEAD(, item_config) items; 28 | TAILQ_ENTRY(device_config) tailq; 29 | }; 30 | 31 | enum scan_func { 32 | SCAN_FUNC_INVALID = -1, 33 | SCAN_FUNC_IMAGE = 0, 34 | SCAN_FUNC_OCR = 1, 35 | SCAN_FUNC_EMAIL = 2, 36 | SCAN_FUNC_FILE = 3 37 | }; 38 | 39 | struct item_config { 40 | char *password; 41 | char *hostname; // the name displayed on the device 42 | 43 | enum scan_func scan_func; 44 | 45 | unsigned page_init_timeout; 46 | unsigned page_finish_timeout; 47 | struct scan_param scan_params[CONFIG_SCAN_MAX_PARAMS]; 48 | // Which script to execute after receiving a page or set of pages. 49 | char *scan_command; 50 | TAILQ_ENTRY(item_config) tailq; 51 | 52 | // Registration number for this device. Filled in device_handler.c 53 | unsigned appnum; 54 | }; 55 | 56 | struct brother_config { 57 | TAILQ_HEAD(, device_config) devices; 58 | }; 59 | 60 | extern struct brother_config g_config; 61 | extern const char *g_scan_func_str[CONFIG_SCAN_MAX_FUNCS]; 62 | 63 | enum scan_func config_get_scan_func_by_name(const char *name); 64 | const struct item_config *config_find_by_func_and_name( 65 | const struct device_config *dev_config, enum scan_func func, 66 | const char *name); 67 | int config_init(const char *config_path); 68 | 69 | #endif //BROTHER_CONFIG_H 70 | -------------------------------------------------------------------------------- /out/ocr.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ################################################################################ 3 | # 4 | # The following environment variables will be set: 5 | # 6 | # SCANNER_XDPI 7 | # SCANNER_YDPI 8 | # SCANNER_HEIGHT 9 | # SCANNER_WIDTH 10 | # SCANNER_PAGE (starts from 1) 11 | # SCANNER_IP 12 | # SCANNER_SCANID (to group a set of scanned pages) 13 | # SCANNER_HOSTNAME (as selected on the device) 14 | # SCANNER_FUNC (as selected on the device) 15 | # SCANNER_FILENAME (where the received data was stored), e.g. scan123.jpg 16 | # 17 | # The script is also called after all pages of a set have been received, 18 | # in this case SCANNER_FILENAME will not be set. 19 | # For a set of pages, you can expect the variables to stay the same (except for 20 | # SCANNER_PAGE and SCANNER_FILENAME). 21 | # 22 | ################################################################################ 23 | 24 | # The following is an example script, that receives RLENGTH encoded monochrome 25 | # scans, creates a tiff file out of them and then runs tesseract to perform OCR. 26 | 27 | set -x -e 28 | 29 | if [ ! -z "$SCANNER_FILENAME" ]; then 30 | mv $SCANNER_FILENAME tmp_${SCANNER_SCANID}_${SCANNER_PAGE}.rle 31 | exit 0 32 | fi 33 | 34 | # Every time we received a set of pages, record a unique destination filename. 35 | : "${DEST_FILENAME:=$(date "+%Y-%m-%d_%H%M%S")}" 36 | 37 | # Convert to TIFF 38 | python3 ../rle_to_tiff.py $SCANNER_XDPI $SCANNER_YDPI $SCANNER_WIDTH \ 39 | $(for i in $(seq 1 $SCANNER_PAGE); do echo tmp_${SCANNER_SCANID}_$i.rle; done) \ 40 | > tmp_$DEST_FILENAME.tiff 41 | 42 | # For black & white scans, nothing beats FAX group4 compression. 43 | mogrify -compress group4 tmp_$DEST_FILENAME.tiff 44 | 45 | # Store final results in a separate output directory. Make sure it exists. 46 | mkdir -p scans 47 | 48 | # Run tesseract for OCR. Update the language for better results. 49 | TESSERACT_LANGUAGE=deu 50 | tesseract -l $TESSERACT_LANGUAGE tmp_$DEST_FILENAME.tiff scans/$DEST_FILENAME pdf 51 | rm tmp_$DEST_FILENAME.tiff 52 | 53 | notify-send "Received $SCANNER_PAGE page(s) from $SCANNER_IP (${SCANNER_WIDTH}x${SCANNER_HEIGHT} px; ${SCANNER_XDPI}x${SCANNER_YDPI} DPI)" 54 | -------------------------------------------------------------------------------- /out/ocr-fake-duplex-adf.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | ################################################################################ 3 | # 4 | # The following environment variables will be set: 5 | # 6 | # SCANNER_XDPI 7 | # SCANNER_YDPI 8 | # SCANNER_HEIGHT 9 | # SCANNER_WIDTH 10 | # SCANNER_PAGE (starts from 1) 11 | # SCANNER_IP 12 | # SCANNER_SCANID (to group a set of scanned pages) 13 | # SCANNER_HOSTNAME (as selected on the device) 14 | # SCANNER_FUNC (as selected on the device) 15 | # SCANNER_FILENAME (where the received data was stored), e.g. scan123.jpg 16 | # 17 | # The script is also called after all pages of a set have been received, 18 | # in this case SCANNER_FILENAME will not be set. 19 | # For a set of pages, you can expect the variables to stay the same (except for 20 | # SCANNER_PAGE and SCANNER_FILENAME). 21 | # 22 | ################################################################################ 23 | 24 | # The following is an example script, that builds upon ocr.sh and adds fake 25 | # duplex support. If you scanned a set of >1 pages with the same number in 26 | # succession, assume it's front and back and create a merged file. 27 | 28 | set -e -x 29 | 30 | if [ -z "$SCANNER_FILENAME" ]; then 31 | # We need to define the destination filename and remember the previous one. 32 | [ -e destfilename ] && cp destfilename prevfilename 33 | echo $(date "+%Y-%m-%d_%H%M%S") > destfilename 34 | DEST_FILENAME=$(cat destfilename) 35 | fi 36 | 37 | # This performs tiff conversion and OCR, exits unless SCANNER_FILENAME is empty. 38 | . ocr.sh 39 | 40 | # Fake ADF support. 41 | if [ -e prevfilename ]; then 42 | PREV_FILENAME=$(cat prevfilename) 43 | A=scans/$PREV_FILENAME.pdf 44 | B=scans/$DEST_FILENAME.pdf 45 | if [ -e $A ]; then 46 | PREV_PAGES=$(pdftk $A dump_data | awk '/NumberOfPages/{print $2}') 47 | # Heuristic: if there were multiple pages scanned and the previous doc has the 48 | # same number of pages: assume it's front and back of an ADF scan and merge them 49 | if [ $SCANNER_PAGE -eq $PREV_PAGES ] && [ $SCANNER_PAGE -gt 1 ]; then 50 | pdftk A=$A B=$B shuffle A Bend-1 output $A-combined.pdf 51 | mv $A-combined.pdf $A 52 | rm $B 53 | fi 54 | fi 55 | fi 56 | -------------------------------------------------------------------------------- /check_mk.imp: -------------------------------------------------------------------------------- 1 | # Default include mappings for Check_MK 2 | [ 3 | { ref: gcc.libc.imp }, 4 | { ref: gcc.symbols.imp }, 5 | { ref: gcc.stl.headers.imp }, 6 | { ref: stl.c.headers.imp }, 7 | { ref: boost-all.imp }, 8 | { ref: boost-all-private.imp }, 9 | { include: ["", private, "", public ] }, 10 | { include: ["", private, "", public ] }, 11 | { include: ["", private, "", public ] }, 12 | { include: ["", private, "", public ] }, 13 | { include: ["", private, "", public ] }, 14 | { include: ["", private, "", public ] }, 15 | { include: ["", private, "", public ] }, 16 | { include: ["", private, "", public ] }, 17 | { include: ["", private, "", public ] }, 18 | { include: ["", private, "", public ] }, 19 | { include: ["", private, "", public ] }, 20 | { include: ["", private, "", public ] }, 21 | { include: ["", private, "", public ] }, 22 | { include: ["", private, "", public ] }, 23 | { include: ["", private, "", public ] }, 24 | { include: ["", private, "", public ] }, 25 | { include: ["", private, "", public ] }, 26 | { include: ['@"gmock/.*"', private, '"gmock/gmock.h"', public ] }, 27 | { include: ["@", private, "", public ] }, 28 | { include: ['@"gtest/.*"', private, '"gtest/gtest.h"', public ] }, 29 | { include: ["@", private, "", public ] }, 30 | { symbol: ["int32_t", private, "", public ] }, 31 | { symbol: ["std::ofstream", private, "", public ] }, 32 | { symbol: ["std::ostringstream", private, "", public ] }, 33 | { symbol: ["std::stringstream", private, "", public ] }, 34 | { symbol: ["strcasecmp", private, "", public ] }, 35 | { symbol: ["timeval", private, "", public ] } 36 | ] 37 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | CC = gcc 2 | CFLAGS += -std=gnu11 -pedantic -Wall -Wextra \ 3 | -Werror -Wno-missing-braces -Wno-missing-field-initializers \ 4 | -Wno-unused-variable -Wno-unused-parameter -Wformat=2 -Wswitch-default \ 5 | -Wno-unused-label -Wno-unused-function -Wcast-align -Wpointer-arith -Wbad-function-cast \ 6 | -Wstrict-overflow=5 -Wstrict-prototypes -Winline -Wundef -Wnested-externs \ 7 | -Wcast-qual -Wshadow -Wunreachable-code -Wfloat-equal \ 8 | -Wstrict-aliasing=2 -Wredundant-decls -Wold-style-definition 9 | LDFLAGS = -pthread 10 | SOURCES = con_queue.c log.c device_handler.c event_thread.c config.c connection.c \ 11 | data_channel.c snmp.c 12 | EXEC_SOURCES = main.c scanner_cli.c 13 | SOURCES += ber/ber.c ber/snmp.c 14 | OBJECTS = $(patsubst %.c, build/%.o, $(SOURCES)) 15 | DEPS := $(OBJECTS:.o=.d) 16 | EXECUTABLES = build/brother-scand build/brother-scan-cli 17 | FIX_INCLUDE = fix_include 18 | ETCDIR := /etc/brother-scand 19 | BINDIR := /usr/bin 20 | PRINTUSER := brother-scand 21 | SYSTEMDPATH := /etc/systemd/system/$(PRINTUSER).service 22 | SYSLOGPATH := /etc/rsyslog.d/$(PRINTUSER).conf 23 | 24 | all: $(SOURCES) $(EXECUTABLES) 25 | 26 | test: 27 | mkdir -p cmake 28 | (cd cmake; CC=clang CXX=clang++ cmake .. && make && CTEST_OUTPUT_ON_FAILURE=1 ctest) 29 | 30 | iwyu: $(SOURCES) 31 | mkdir -p cmake 32 | (cd cmake; CC=clang CXX=clang++ cmake .. && make iwyu) 2>&1 | tee iwyu 33 | 34 | fix_include: iwyu 35 | $(FIX_INCLUDE) --nosafe_headers < iwyu 36 | 37 | -include $(DEPS) 38 | 39 | ber/ber.c: 40 | git submodule init 41 | git submodule update 42 | 43 | build/brother-scand: build/main.o $(OBJECTS) 44 | $(CC) $^ -o $@ $(LDFLAGS) 45 | 46 | build/brother-scan-cli: build/scanner_cli.o $(OBJECTS) 47 | $(CC) $^ -o $@ $(LDFLAGS) 48 | 49 | build/%.o: %.c 50 | @mkdir -p $(@D) 51 | $(CC) -c -MM -MF $(patsubst %.o,%.d,$@) $< 52 | $(CC) $(CFLAGS) -c -o $@ $< 53 | 54 | install: all 55 | adduser --system --no-create-home $(PRINTUSER) 56 | cp $(EXECUTABLES) $(BINDIR)/ 57 | mkdir -p $(ETCDIR) 58 | [ -e $(ETCDIR)/scanner.conf ] || cp out/brother.config $(ETCDIR)/scanner.conf 59 | for file in out/*.sh; do [ -e $(ETCDIR)/$$file ] || cp $$file $(ETCDIR)/; done 60 | chown -R $(PRINTUSER) $(ETCDIR) 61 | cp brother-scand.service $(SYSTEMDPATH) 62 | cp syslog.conf $(SYSLOGPATH) 63 | echo -e "Now edit $(ETCDIR)/scanner.conf. Then run:\n systemctl restart rsyslog\n systemctl enable $(PRINTUSER)" 64 | 65 | install_ufw: 66 | cp brscand.ufw_profile /etc/ufw/applications.d/brscand 67 | ufw app update brscand 68 | ufw allow app brscand 69 | 70 | uninstall: 71 | rm -ir $(ETCDIR) $(BINDIR)/brother-scan* $(SYSTEMDDPATH) $(SYSLOGPATH) 72 | deluser $(PRINTUSER) 73 | 74 | .PHONY: clean test 75 | 76 | clean: 77 | rm -f $(OBJECTS) $(DEPS) $(EXECUTABLES) build/*.o iwyu 78 | -------------------------------------------------------------------------------- /log.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #include 8 | #include 9 | 10 | #include "log.h" 11 | 12 | static const char *level_names[] = { 13 | [LEVEL_DEBUG] = "DEBUG", 14 | [LEVEL_INFO] = "INFO", 15 | [LEVEL_WARN] = "WARN", 16 | [LEVEL_ERR] = "ERR", 17 | [LEVEL_FATAL] = "FATAL", 18 | }; 19 | 20 | static int g_loglevel = LEVEL_INFO; 21 | 22 | void log_set_level(int level) { g_loglevel = level; } 23 | 24 | void log_printf(int level, const char *file, int line, const char *fmt, ...) 25 | { 26 | va_list args; 27 | 28 | if (level < g_loglevel) { 29 | return; 30 | } 31 | 32 | fprintf(stderr, "%-5s %s:%d: ", level_names[level], file, line); 33 | 34 | va_start(args, fmt); 35 | vfprintf(stderr, fmt, args); 36 | va_end(args); 37 | } 38 | 39 | 40 | static char 41 | to_printable(int n) 42 | { 43 | static const char *trans_table = "0123456789abcdef"; 44 | 45 | return trans_table[n & 0xf]; 46 | } 47 | 48 | int 49 | hexdump_line(const char *data, const char *data_start, const char *data_end) 50 | { 51 | static char buf[256] = {0}; 52 | 53 | char *buf_ptr = buf; 54 | int relative_addr = (int)(data - data_start); 55 | size_t i, j; 56 | 57 | for (i = 0; i < 2; ++i) { 58 | buf_ptr[i] = ' '; 59 | } 60 | buf_ptr += i; 61 | 62 | for (i = 0; i < sizeof(void *); ++i) { 63 | buf_ptr[i] = to_printable(relative_addr >> 64 | (sizeof(void *) * 4 - 4 - i * 4)); 65 | } 66 | buf_ptr += i; 67 | 68 | buf_ptr[0] = ':'; 69 | buf_ptr[1] = ' '; 70 | buf_ptr += 2; 71 | 72 | for (j = 0; j < 8; ++j) { 73 | for (i = 0; i < 2; ++i) { 74 | if (data < data_end) { 75 | buf[10 + 5 * 8 + 4 + i + 2 * j] = 76 | (char)(isprint(*data) ? *data : '.'); 77 | 78 | buf_ptr[i * 2] = to_printable(*data >> 4); 79 | buf_ptr[i * 2 + 1] = to_printable(*data); 80 | 81 | ++data; 82 | } else { 83 | buf[10 + 5 * 8 + 4 + i + 2 * j] = 0; 84 | 85 | buf_ptr[i * 2] = ' '; 86 | buf_ptr[i * 2 + 1] = ' '; 87 | } 88 | } 89 | 90 | buf_ptr[4] = ' '; 91 | buf_ptr += 5; 92 | } 93 | 94 | buf[10 + 5 * 8 + 2] = '|'; 95 | buf[10 + 5 * 8 + 3] = ' '; 96 | 97 | fprintf(stderr, "%s\n", buf); 98 | 99 | return (int)(i * j); 100 | } 101 | 102 | void 103 | hexdump(int level, const void *data, size_t len) 104 | { 105 | const char *data_ptr = data; 106 | const char *data_start = data_ptr; 107 | const char *data_end = data_ptr + len; 108 | 109 | if (level < g_loglevel) { 110 | return; 111 | } 112 | 113 | fprintf(stderr, "{\n"); 114 | while (data_ptr < data_end) { 115 | data_ptr += hexdump_line(data_ptr, data_start, data_end); 116 | } 117 | fprintf(stderr, "}\n"); 118 | } 119 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Brother scanner network driver 2 | 3 | ![Status](https://img.shields.io/badge/status-stable-green.svg) 4 | [![License](https://img.shields.io/github/license/darsto/brother-scanner-driver.svg)](LICENSE.md) 5 | 6 | Functional userspace driver for Brother scanners. 7 | 8 | This is a server for brother click-to-scan feature. 9 | To scan, simply press a button on the scanner. 10 | 11 | This is a cross-platform, open-source and headless equivalent of Brother's Control Center 4. 12 | 13 | Written in C11. Does not use any external dependencies. 14 | 15 | ## Advantages over the official Linux driver 16 | 17 | Brother released a Linux driver for their scanners, however... 18 | 19 | The official Linux driver uses click-to-scan Brother protocol only to notify about the button press event. 20 | After receiving such event, the driver closes the connection and starts up separate SANE application 21 | that will establish connection with the same scanner (again!) and will request single page scan. 22 | 23 | As for DCP-J105 scanner model, the connection establishment + handshake takes around 3 seconds. 24 | To scan a single page, one would have to wait at least 6 seconds before the actual scanning starts. 25 | 26 | The press-to-scan protocol offers much more than. It can be used to receive entire 27 | image data within the same (original) connection. But only on Windows... Well, not anymore! 28 | 29 | ## Features 30 | 31 | * All the Windows Control Center 4 features, including: 32 | * Scan to IMAGE, OCR, EMAIL, FILE 33 | * Configurable scan params (DPI, Dimensions, Brightness, Contrast, Color, Compression) 34 | * Scan multiple pages with almost no interval (~ 0 sec) 35 | * Multiple scanners support 36 | * Password-protected hosts (a feature apparently not implemented in Control Center 4) 37 | * External scan hooks 38 | * Minimal resource usage when idle 39 | * Configurable hostname :) 40 | 41 | ## Installation 42 | ``` 43 | git clone https://github.com/rumpeltux/brother-scand.git 44 | cd brother-scand 45 | git submodule init 46 | git submodule update 47 | make && sudo make install 48 | ``` 49 | 50 | The driver **should** work for the most of Brother devices. 51 | However, it has only been tested on the DCP-J105, MFC-J430W, and HL-L2380DW. 52 | 53 | If you have successfully run this driver with a different model, 54 | please open a github issue and provide debug output logs if possible, so that 55 | we can confirm behavior and add a testcase. 56 | 57 | ## Troubleshooting 58 | 59 | * Use `build/brother-scan-cli -c your.config` to test your configuration 60 | without having to run the service. 61 | * Add `-d` to your commandline (both for `build/brother-scan-cli` or in the 62 | `/etc/brother-scand/scanner.config` file) to collect debug output. 63 | * Logs of the system service should be located in `/var/log/brother-scand.log` 64 | 65 | ## Running tests 66 | 67 | Tests are written in C++ for convenience and use the GoogleTest framework. 68 | The goal of those tests is to make sure the project keeps working even in the 69 | absence of actual scanners to test with. 70 | 71 | sudo apt install cmake googletest clang 72 | make test 73 | 74 | NOTE: Since tests rely on specific ports and their cleanup isn't always clean, 75 | running them repeatly may be flaky. 76 | -------------------------------------------------------------------------------- /connection_base_test.h: -------------------------------------------------------------------------------- 1 | #ifndef BROTHER_CONNECTION_BASE_TEST_H 2 | #define BROTHER_CONNECTION_BASE_TEST_H 3 | 4 | #include 5 | #include 6 | 7 | extern "C" { 8 | #include "connection.h" 9 | #include "log.h" 10 | } 11 | 12 | #define ASSERT_C_OK(x) \ 13 | { \ 14 | int res = x; \ 15 | if (res < 0) perror(#x); \ 16 | ASSERT_GE(res, 0); \ 17 | } 18 | 19 | #define BROTHER_CONNECTION_BASE_TEST_PORT 54921 20 | 21 | struct ClientConnection { 22 | public: 23 | explicit ClientConnection(int server_fd) 24 | : server_thread([this, server_fd]() { 25 | struct sockaddr cli_addr; 26 | int one = 1; 27 | socklen_t clilen = sizeof(cli_addr); 28 | client_fd = accept(server_fd, &cli_addr, &clilen); 29 | ASSERT_C_OK(setsockopt(client_fd, SOL_SOCKET, SO_REUSEADDR, &one, 30 | sizeof(int))); 31 | LOG_INFO("accepted fd: %d, %x\n", client_fd, this); 32 | }) {} 33 | 34 | ~ClientConnection() { 35 | if (client_fd > 0) ::close(client_fd); 36 | } 37 | 38 | void write(std::string data) { 39 | ::write(client_fd, data.c_str(), data.size()); 40 | } 41 | 42 | std::thread write_async(std::string data, int delay_us) { 43 | return std::thread([this, data, delay_us] { 44 | ASSERT_C_OK(get_client_fd()); 45 | usleep(delay_us); 46 | write(data); 47 | }); 48 | } 49 | 50 | std::string read() { 51 | char buf[1024]; 52 | int len = ::read(client_fd, buf, sizeof(buf)); 53 | return std::string(buf, len); 54 | } 55 | 56 | int get_client_fd() { 57 | if (server_thread.joinable()) server_thread.join(); 58 | return client_fd; 59 | } 60 | 61 | private: 62 | std::thread server_thread; 63 | int client_fd; 64 | }; 65 | 66 | struct ConnectionBaseTest : public ::testing::Test { 67 | protected: 68 | ConnectionBaseTest(int port) : port_(port), ::testing::Test() {} 69 | ConnectionBaseTest() 70 | : ConnectionBaseTest(BROTHER_CONNECTION_BASE_TEST_PORT) {} 71 | 72 | void SetUp() { 73 | start_server(); 74 | conn_ = brother_conn_open(BROTHER_CONNECTION_TYPE_TCP, /*timeout_sec=*/1); 75 | ASSERT_TRUE(conn_); 76 | } 77 | 78 | void TearDown() { 79 | LOG_INFO("TearDown\n"); 80 | brother_conn_close(conn_); 81 | close(server_); 82 | } 83 | 84 | void start_server() { 85 | int one = 1; 86 | server_ = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); 87 | ASSERT_C_OK(server_); 88 | ASSERT_C_OK( 89 | setsockopt(server_, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int))); 90 | struct sockaddr_in serv_addr; 91 | memset(&serv_addr, 0, sizeof(serv_addr)); 92 | serv_addr.sin_family = AF_INET; 93 | serv_addr.sin_addr.s_addr = INADDR_ANY; 94 | serv_addr.sin_port = htons(port_); 95 | ASSERT_C_OK( 96 | bind(server_, (struct sockaddr *)&serv_addr, sizeof(serv_addr))); 97 | ASSERT_C_OK(listen(server_, 1)); 98 | } 99 | 100 | std::unique_ptr accept_client() { 101 | return std::make_unique(server_); 102 | } 103 | 104 | int port_; 105 | int server_ = -1; // the listening server 106 | struct brother_conn *conn_; 107 | }; 108 | 109 | #endif // BROTHER_CONNECTION_BASE_TEST_H 110 | -------------------------------------------------------------------------------- /out/brother.config: -------------------------------------------------------------------------------- 1 | # Brother scanner driver config 2 | 3 | # This file mostly serves as a reference for the config format. 4 | # Copy one of the examples and come here for help on how to customize. 5 | 6 | ################################################################################ 7 | # Default Scan Params 8 | # These are values that are used to scan image with unless the scanner sends 9 | # different ones. Invalid (or unsupported) values should be corrected by the 10 | # scanner. This preset is named 'default'. 11 | 12 | # First character is shortcut 13 | # of parameter type, the following string is a 14 | # max. 15-letter string. 15 | # e.g. scan.param X value123 16 | # 17 | # Scan area dimensions in the following format: 18 | # ,,, 19 | #scan.param A 20 | # Brightness in range <0,100>, where 21 | # 0 - darkest, 100 - brightest 22 | #scan.param B 50 23 | # Compression method: 24 | # RAW: uncompressed pixel data 25 | # RLENGTH: runlength encoded pixel data. Use rle_to_tiff.py for M=TEXT 26 | # (other color depths are not yet supported) 27 | # JPEG: jpeg data (produces invalid images when used from ADF: see 28 | # https://github.com/darsto/brother-scand/issues/8) 29 | #scan.param C JPEG 30 | # ? 31 | #scan.param D SIN 32 | # ? 33 | #scan.param E SHO 34 | # ? 35 | #scan.param G 1 36 | # ? 37 | #scan.param J MID 38 | # ? 39 | #scan.param L 128 40 | # Scan mode / color depth 41 | # TEXT: Black & White 42 | # ERRDIF: Gray[Error Diffusion] 43 | # GRAY64: True Gray 44 | # CGRAY: 24bit Color 45 | # C256: 24bit Color[Fast] 46 | #scan.param M CGRAY 47 | # ? 48 | #scan.param N 50 49 | # Scanned page real size format 50 | #scan.param P A4 51 | # Scan DPI in X,Y format 52 | #scan.param R 300,300 53 | # Image type (unused?) 54 | #scan.param T JPEG 55 | 56 | # The shell-script that is called after receiving a page. 57 | # See scanhooh.sh for documentation. 58 | #scan.func ./savejpeg.sh 59 | 60 | # Optional PIN that will have to be given on 61 | # the scanner panel before scanning any document 62 | # series. Must be exactly 4 digits, otherwise 63 | # password won't be set. 64 | #password 1234 65 | 66 | 67 | ################################################################################ 68 | # Default network configuration (can be overridden per device). 69 | 70 | # Maximum time for receiving the page scan init 71 | # message. Some scanners might need extra time to 72 | # prepare for scanning. 73 | # Values less than 15 are discouraged. 74 | #network.page.init.timeout 15 75 | 76 | # Maximum time for receiving the page scan finish 77 | # message. Some scanners might need extra time to 78 | # finalize sending page data or allow users to 79 | # place additional pages manually. 80 | # Values less than 30 are discouraged. 81 | #network.page.finish.timeout 60 82 | 83 | ################################################################################ 84 | # Preset definitions 85 | # You can define a number of presets that you can then reuse for the per-device 86 | # and per-scan-function configuration. 87 | 88 | #define-preset text 89 | #scan.param C RLENGTH 90 | #scan.param M TEXT 91 | #scan.func ./ocr-processing.sh 92 | 93 | #define-preset grayscale 94 | #scan.param M GRAY64 95 | 96 | ################################################################################ 97 | # Device configurations 98 | # A scanner typically offers the options to scan as FILE, IMAGE, EMAIL, OCR. 99 | # For each of these functions a preset can be configured and individual params 100 | # can be overridden. The name as specified by 'hostname' will be shown on the 101 | # scanner as the destination. 102 | # 103 | # The default designation that'll be displayed on the printer. 104 | # Can be overridden per device / function 105 | #hostname scan 106 | # 107 | # Device 1 108 | # The printer IPv4 109 | #ip 192.168.1.2 110 | 111 | # Max number of seconds for receiving a single 112 | # network message. After this time, the scanning 113 | # session will be terminated. 114 | # Values less than 2 are discouraged. 115 | #network.timeout 5 116 | 117 | # Per-Device functions: 118 | #preset text OCR 119 | #preset default IMAGE 120 | #preset text EMAIL 121 | #hostname john 122 | #scan.func ./ocr-email.sh john@doe.org 123 | #preset text EMAIL 124 | #hostname jane 125 | #scan.func ./ocr-email.sh jane@doe.org 126 | #preset text FILE 127 | #scan.R 150,150 128 | #scan.func ./save_rle_to_tiff.sh 129 | # 130 | # Device 2 131 | #ip 1.2.3.4 132 | #... 133 | -------------------------------------------------------------------------------- /snmp.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include "ber/snmp.h" 14 | #include "connection.h" 15 | #include "log.h" 16 | #include "snmp.h" 17 | 18 | struct brother_conn; 19 | 20 | #define SNMP_PORT 161 21 | 22 | static atomic_int g_request_id; 23 | static uint32_t g_brInfoPrinterUStatusOID[] = 24 | { 1, 3, 6, 1, 4, 1, 2435, 2, 3, 9, 4, 2, 1, 5, 5, 6, 0, SNMP_MSG_OID_END }; 25 | static uint32_t g_brRegisterKeyInfoOID[] = 26 | { 1, 3, 6, 1, 4, 1, 2435, 2, 3, 9, 2, 11, 1, 1, 0, SNMP_MSG_OID_END }; 27 | static uint32_t g_brUnregisterKeyInfoOID[] = 28 | { 1, 3, 6, 1, 4, 1, 2435, 2, 3, 9, 2, 11, 1, 2, 0, SNMP_MSG_OID_END }; 29 | 30 | static void 31 | init_msg_header(struct snmp_msg_header *msg_header, const char *community, 32 | enum snmp_data_type type) 33 | { 34 | msg_header->snmp_ver = 0; 35 | msg_header->community = community; 36 | msg_header->pdu_type = type; 37 | msg_header->request_id = atomic_fetch_add(&g_request_id, 1); 38 | } 39 | 40 | int 41 | snmp_get_printer_status(struct brother_conn *conn, uint8_t *buf, size_t buf_len, 42 | in_addr_t dest_addr) 43 | { 44 | uint8_t *buf_end = buf + buf_len - 1; 45 | struct snmp_msg_header msg_header = {0}; 46 | struct snmp_varbind varbind = {{0}}; 47 | size_t snmp_len; 48 | uint8_t *out; 49 | int msg_len, rc = -1; 50 | uint32_t varbind_num = 1; 51 | 52 | init_msg_header(&msg_header, "public", SNMP_DATA_T_PDU_GET_REQUEST); 53 | memcpy(varbind.oid, g_brInfoPrinterUStatusOID, 54 | sizeof(g_brInfoPrinterUStatusOID)); 55 | varbind.value_type = SNMP_DATA_T_NULL; 56 | 57 | out = snmp_encode_msg(buf_end, &msg_header, varbind_num, &varbind); 58 | snmp_len = buf_end - out + 1; 59 | 60 | msg_len = brother_conn_sendto(conn, out, snmp_len, dest_addr, htons(SNMP_PORT)); 61 | if (msg_len < 0 || (size_t) msg_len != snmp_len) { 62 | perror("sendto"); 63 | return -1; 64 | } 65 | 66 | rc = brother_conn_poll(conn, 3); 67 | if (rc <= 0) { 68 | LOG_ERR("Failed to receive SNMP status reponse.\n"); 69 | return -1; 70 | } 71 | 72 | msg_len = brother_conn_receive(conn, buf, buf_len); 73 | if (msg_len < 6) { 74 | perror("recvfrom"); 75 | return -1; 76 | } 77 | 78 | snmp_decode_msg(buf, msg_len, &msg_header, &varbind_num, &varbind); 79 | if (msg_header.error_index != 0 && msg_header.error_status != 0) { 80 | LOG_ERR("Received invalid printer status SNMP response\n"); 81 | DUMP_ERR(buf, (size_t) msg_len); 82 | return -1; 83 | } 84 | 85 | rc = (int) varbind.value.i; 86 | return rc; 87 | } 88 | 89 | int snmp_register_scanner_driver(struct brother_conn *conn, bool enabled, 90 | const char **functions, 91 | size_t functions_length, in_addr_t dest_addr) { 92 | size_t buf_len = 512 + functions_length * 128; 93 | uint8_t buf[buf_len]; 94 | uint8_t *buf_end = buf + buf_len - 1; 95 | 96 | struct snmp_msg_header msg_header = {0}; 97 | struct snmp_varbind varbind[functions_length]; 98 | size_t snmp_len; 99 | uint8_t *out; 100 | uint32_t i, varbind_num; 101 | int msg_len, rc = -1; 102 | 103 | init_msg_header(&msg_header, "internal", SNMP_DATA_T_PDU_SET_REQUEST); 104 | 105 | for (i = 0; i < functions_length; ++i) { 106 | if (functions[i] == NULL || functions[i][0] == 0) { 107 | break; 108 | } 109 | 110 | if (enabled) { 111 | memcpy(varbind[i].oid, g_brRegisterKeyInfoOID, 112 | sizeof(g_brRegisterKeyInfoOID)); 113 | } else { 114 | memcpy(varbind[i].oid, g_brUnregisterKeyInfoOID, 115 | sizeof(g_brUnregisterKeyInfoOID)); 116 | } 117 | 118 | varbind[i].value_type = SNMP_DATA_T_OCTET_STRING; 119 | varbind[i].value.s = functions[i]; 120 | } 121 | 122 | varbind_num = i; 123 | out = snmp_encode_msg(buf_end, &msg_header, varbind_num, varbind); 124 | snmp_len = buf_end - out + 1; 125 | 126 | msg_len = 127 | brother_conn_sendto(conn, out, snmp_len, dest_addr, htons(SNMP_PORT)); 128 | if (msg_len < 0 || (size_t)msg_len != snmp_len) { 129 | perror("sendto"); 130 | return -1; 131 | } 132 | 133 | rc = brother_conn_poll(conn, 3); 134 | if (rc <= 0) { 135 | LOG_ERR("Failed to receive SNMP status reponse.\n"); 136 | return -1; 137 | } 138 | 139 | msg_len = brother_conn_receive(conn, buf, buf_len); 140 | if (msg_len < 6) { 141 | perror("recvfrom"); 142 | return -1; 143 | } 144 | 145 | if (!enabled) { 146 | /* unregister msg is not implemented for some scanners, 147 | * ignore all errors */ 148 | return 0; 149 | } 150 | 151 | snmp_decode_msg(buf, msg_len, &msg_header, &varbind_num, varbind); 152 | if (msg_header.error_index != 0 && msg_header.error_status != 0) { 153 | LOG_ERR( 154 | "Received invalid register SNMP response: error_index=%d, " 155 | "error_status=%d\n", 156 | msg_header.error_index, msg_header.error_status); 157 | DUMP_ERR(buf, (size_t)msg_len); 158 | return -1; 159 | } 160 | 161 | rc = msg_len; 162 | return rc; 163 | } 164 | -------------------------------------------------------------------------------- /rle_to_tiff.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """Creates a standard tiff image from RLENGTH data sent by Brother scanners.""" 3 | import struct 4 | import sys 5 | 6 | 7 | def rle_decode(data): 8 | """Decodes PackBits encoded data.""" 9 | i = 0 10 | output = bytearray() 11 | while i < len(data): 12 | val = data[i] 13 | i += 1 14 | if val == 0x80: 15 | continue 16 | if val > 0x80: 17 | repeats = 0x101 - val 18 | output += data[i:i + 1] * repeats 19 | i += 1 20 | else: 21 | output += data[i:i + val + 1] 22 | i += val + 1 23 | return output 24 | 25 | 26 | EXIF_TAGS = { 27 | 0x100: "ImageWidth", 28 | 0x101: "ImageLength", 29 | 0x102: "BitsPerSample", 30 | 0x103: "Compression", 31 | 0x106: "PhotometricInterpretation", 32 | 0x10A: "FillOrder", 33 | 0x10D: "DocumentName", 34 | 0x10E: "ImageDescription", 35 | 0x10F: "Make", 36 | 0x110: "Model", 37 | 0x111: "StripOffsets", 38 | 0x112: "Orientation", 39 | 0x115: "SamplesPerPixel", 40 | 0x116: "RowsPerStrip", 41 | 0x117: "StripByteCounts", 42 | 0x11A: "XResolution", 43 | 0x11B: "YResolution", 44 | 0x11C: "PlanarConfiguration", 45 | 0x128: "ResolutionUnit", 46 | 0x129: "PageNumber", 47 | 0x12D: "TransferFunction", 48 | 0x131: "Software", 49 | 0x132: "DateTime", 50 | 0x13B: "Artist", 51 | 0x13E: "WhitePoint", 52 | 0x13F: "PrimaryChromaticities", 53 | 0x156: "TransferRange", 54 | 0x200: "JPEGProc", 55 | 0x201: "JPEGInterchangeFormat", 56 | 0x202: "JPEGInterchangeFormatLength", 57 | 0x211: "YCbCrCoefficients", 58 | 0x212: "YCbCrSubSampling", 59 | 0x213: "YCbCrPositioning", 60 | 0x214: "ReferenceBlackWhite", 61 | 0x828F: "BatteryLevel", 62 | 0x8298: "Copyright", 63 | 0x829A: "ExposureTime", 64 | 0x829D: "FNumber", 65 | 0x83BB: "IPTC/NAA", 66 | 0x8769: "ExifIFDPointer", 67 | 0x8773: "InterColorProfile", 68 | 0x8822: "ExposureProgram", 69 | 0x8824: "SpectralSensitivity", 70 | 0x8825: "GPSInfoIFDPointer", 71 | 0x8827: "ISOSpeedRatings", 72 | 0x8828: "OECF", 73 | 0x9000: "ExifVersion", 74 | 0x9003: "DateTimeOriginal", 75 | 0x9004: "DateTimeDigitized", 76 | 0x9101: "ComponentsConfiguration", 77 | 0x9102: "CompressedBitsPerPixel", 78 | 0x9201: "ShutterSpeedValue", 79 | 0x9202: "ApertureValue", 80 | 0x9203: "BrightnessValue", 81 | 0x9204: "ExposureBiasValue", 82 | 0x9205: "MaxApertureValue", 83 | 0x9206: "SubjectDistance", 84 | 0x9207: "MeteringMode", 85 | 0x9208: "LightSource", 86 | 0x9209: "Flash", 87 | 0x920A: "FocalLength", 88 | 0x9214: "SubjectArea", 89 | 0x927C: "MakerNote", 90 | 0x9286: "UserComment", 91 | 0x9290: "SubSecTime", 92 | 0x9291: "SubSecTimeOriginal", 93 | 0x9292: "SubSecTimeDigitized", 94 | 0xA000: "FlashPixVersion", 95 | 0xA001: "ColorSpace", 96 | 0xA002: "PixelXDimension", 97 | 0xA003: "PixelYDimension", 98 | 0xA004: "RelatedSoundFile", 99 | 0xA005: "InteroperabilityIFDPointer", 100 | 0xA20B: "FlashEnergy", # 0x920B in TIFF/EP 101 | 0xA20C: "SpatialFrequencyResponse", # 0x920C - - 102 | 0xA20E: "FocalPlaneXResolution", # 0x920E - - 103 | 0xA20F: "FocalPlaneYResolution", # 0x920F - - 104 | 0xA210: "FocalPlaneResolutionUnit", # 0x9210 - - 105 | 0xA214: "SubjectLocation", # 0x9214 - - 106 | 0xA215: "ExposureIndex", # 0x9215 - - 107 | 0xA217: "SensingMethod", # 0x9217 - - 108 | 0xA300: "FileSource", 109 | 0xA301: "SceneType", 110 | 0xA302: "CFAPattern", # 0x828E in TIFF/EP 111 | 0xA401: "CustomRendered", 112 | 0xA402: "ExposureMode", 113 | 0xA403: "WhiteBalance", 114 | 0xA404: "DigitalZoomRatio", 115 | 0xA405: "FocalLengthIn35mmFilm", 116 | 0xA406: "SceneCaptureType", 117 | 0xA407: "GainControl", 118 | 0xA408: "Contrast", 119 | 0xA409: "Saturation", 120 | 0xA40A: "Sharpness", 121 | 0xA40B: "DeviceSettingDescription", 122 | 0xA40C: "SubjectDistanceRange", 123 | 0xA420: "ImageUniqueID", 124 | } 125 | TIFF_TAGS = {v: k for k, v in EXIF_TAGS.items()} 126 | SHORT = 3 127 | INT = 4 128 | 129 | 130 | def tiff_tag(name, datatype, *values): 131 | """Packs a tiff_tag header""" 132 | n = len(values) 133 | dt = 'I' if n == 1 else 'H' 134 | assert n <= 2, "this stub assumes that values fit into the for byte payload" 135 | return struct.pack(' out.tiff' % sys.argv[0], file=sys.stderr) 173 | exit(1) 174 | xdpi, ydpi, width = map(int, sys.argv[1:4]) 175 | filenames = sys.argv[4:] 176 | tiff_writer = TiffWriter(sys.stdout.buffer, pages=len(filenames)) 177 | for i, filename in enumerate(filenames): 178 | rle_data = open(filename, 'rb').read() 179 | tiff_writer.addPage(rle_decode(rle_data), width, xdpi, ydpi) 180 | -------------------------------------------------------------------------------- /data_channel_test.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include "connection_base_test.h" 11 | #include "gtest/gtest.h" 12 | 13 | using namespace std::literals::string_literals; 14 | 15 | extern "C" { 16 | #include "config.h" 17 | #include "data_channel.h" 18 | #include "log.h" 19 | } 20 | 21 | // Sleep 2ms to avoid packets being grouped into 1. 22 | constexpr int SOCKET_MESSAGE_DELAY_US = 2000; 23 | 24 | struct DataChannelTest : public ConnectionBaseTest { 25 | protected: 26 | DataChannelTest() : ConnectionBaseTest(DATA_CHANNEL_TARGET_PORT) {} 27 | 28 | void init_data_channel() { 29 | ASSERT_C_OK(config_init("../testdata/brother.config")); 30 | data_channel.config = TAILQ_FIRST(&g_config.devices); 31 | data_channel_set_item(&data_channel, 32 | TAILQ_FIRST(&data_channel.config->items)); 33 | ASSERT_C_OK(data_channel_init(&data_channel)); 34 | } 35 | 36 | void init_connection() { 37 | client = accept_client(); 38 | std::thread server_thread = client->write_async("+200\0OK"s, 0); 39 | data_channel_init_connection(&data_channel); 40 | server_thread.join(); 41 | } 42 | 43 | void client_write(std::string data) { 44 | replay_messages.push_back(data); 45 | client->write(data); 46 | } 47 | 48 | std::thread client_write_async(std::string data, int delay_us) { 49 | replay_messages.push_back(data); 50 | return client->write_async(data, delay_us); 51 | } 52 | 53 | void exchange_params() { 54 | // Replay the initialization for Brother MFC-J430W 55 | EXPECT_EQ(client->read(), "\x1BK\n\x80"s); 56 | client_write("\x30\x14\000F=FILE\nD=SIN\nE=SHO\n\x80"s); 57 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); // exchange_params1 58 | EXPECT_EQ(client->read(), "\x1bI\nD=SIN\nM=CGRAY\nR=300,300\n\x80"s); 59 | client_write("\x00\x16\000300,300,1,209,2480,0,0"s); 60 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); // exchange_params2 61 | EXPECT_EQ( 62 | client->read(), 63 | "\x1BX\nA=0,0,2480,0\nB=50\nC=JPEG\nD=SIN\nG=1\nL=128\nM=CGRAY\nN=50\nR=300,300\n\x80"s); 64 | } 65 | 66 | void dump(std::string s) { DUMP_DEBUG(s.c_str(), s.size()); } 67 | 68 | struct data_channel data_channel = {}; 69 | std::unique_ptr client; 70 | // Messages sent by the server (i.e. this test). 71 | std::vector replay_messages; 72 | }; 73 | 74 | TEST_F(DataChannelTest, ChunkedReceiveTest) { 75 | init_data_channel(); 76 | init_connection(); 77 | exchange_params(); 78 | 79 | // Now send some RLE encoded payloads 80 | // RLE (0x42), Page 1 (0x01), Payload-Len (0x06) 81 | std::string header = "\x42\x07\x00\x01\x00\x84\x00\x00\x00\x00\x06\x00"s; 82 | std::string rle_payload = "\x81\x00\x81\x00\xcb\x00"s; // 461 * "\x00" 83 | 84 | client_write(header + rle_payload); 85 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); // receive_initial_data 86 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); // process_chunk_header 87 | // the header shoud be successfully processed. 88 | ASSERT_EQ(data_channel.page_data.remaining_chunk_bytes, 6); 89 | // we wrote a complete chunk, so should be all good 90 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); // process_page_payload 91 | ASSERT_EQ(data_channel.page_data.remaining_chunk_bytes, 0); 92 | 93 | client_write(header); 94 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); 95 | // we wrote the header only, so the data channel will be waiting for the 96 | // payload 97 | ASSERT_EQ(data_channel.page_data.remaining_chunk_bytes, 6); 98 | client_write(rle_payload); 99 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); 100 | ASSERT_EQ(data_channel.page_data.remaining_chunk_bytes, 0); 101 | 102 | client_write(header + rle_payload.substr(0, 1)); 103 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); // process_header 104 | ASSERT_EQ(data_channel.page_data.remaining_chunk_bytes, 6); 105 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); // process_page_payload 106 | // we wrote the header + 1 payload byte, so the data channel will be waiting 107 | // for the rest of the payload 108 | ASSERT_EQ(data_channel.page_data.remaining_chunk_bytes, 5); 109 | client_write(rle_payload.substr(1)); 110 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); 111 | ASSERT_EQ(data_channel.page_data.remaining_chunk_bytes, 0); 112 | 113 | // Make sure that if the header is split at various positions, everything 114 | // still works. 115 | auto header_partitions = {0, 1, 2, 6, 10, 11, 12}; 116 | for (int partition : header_partitions) { 117 | if (partition == 0) continue; 118 | client_write(header.substr(0, partition)); 119 | // write the remainder of the header with some delay, so the client has to 120 | // poll for it 121 | auto thread = 122 | client_write_async(header.substr(partition), SOCKET_MESSAGE_DELAY_US); 123 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); // process_header 124 | thread.join(); 125 | ASSERT_EQ(data_channel.page_data.remaining_chunk_bytes, 6); 126 | client_write(rle_payload); 127 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); 128 | ASSERT_EQ(data_channel.page_data.remaining_chunk_bytes, 0); 129 | } 130 | 131 | client_write(header); 132 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); 133 | 134 | // Now do the same, but add the header to the respective payload string. 135 | for (int partition : header_partitions) { 136 | client_write(rle_payload + header.substr(0, partition)); 137 | ASSERT_C_OK( 138 | data_channel.process_cb(&data_channel)); // process_chunk_header 139 | // the payload should have been completely received 140 | ASSERT_EQ(data_channel.page_data.remaining_chunk_bytes, 0); 141 | // write the remainder of the header with some delay, so the client has to 142 | // poll for it 143 | auto thread = 144 | client_write_async(header.substr(partition), SOCKET_MESSAGE_DELAY_US); 145 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); 146 | thread.join(); 147 | ASSERT_EQ(data_channel.page_data.remaining_chunk_bytes, 6); 148 | } 149 | client_write(rle_payload); 150 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); 151 | ASSERT_EQ(data_channel.page_data.remaining_chunk_bytes, 0); 152 | 153 | std::string page1_complete_packet = 154 | "\x82\x07\x00\x01\x00\x84\x00\x00\x00\x00"s; 155 | client_write(page1_complete_packet); 156 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); 157 | 158 | // Empty data on page 2 159 | std::string page2_data_packet = 160 | "\x42\x07\x00\x02\x00\x84\x00\x00\x00\x00\x00\x00"s; 161 | client_write(page2_data_packet); 162 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); // process_header 163 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); // process_page_payload 164 | 165 | std::string page2_complete_packet = 166 | "\x82\x07\x00\x02\x00\x84\x00\x00\x00\x00"s; 167 | client_write(page2_complete_packet); 168 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); 169 | 170 | client_write("\x80"); // scan process end marker 171 | ASSERT_C_OK(data_channel.process_cb(&data_channel)); 172 | 173 | // This closes the existing connection. 174 | client = nullptr; 175 | 176 | // Now replay the same sequence for the standalone test. 177 | init_connection(); 178 | std::thread main_loop([this] { 179 | while (data_channel.process_cb != data_channel_set_paused) { 180 | data_channel_loop(&data_channel); 181 | } 182 | }); 183 | for (auto msg : replay_messages) { 184 | LOG_DEBUG("write: %d bytes\n", msg.size()); 185 | client->write(msg); 186 | usleep(SOCKET_MESSAGE_DELAY_US); // sleep a bit to make sure these are 187 | // separate packets 188 | } 189 | main_loop.join(); 190 | } 191 | -------------------------------------------------------------------------------- /event_thread.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "con_queue.h" 16 | #include "event_thread.h" 17 | #include "log.h" 18 | 19 | #define MAX_EVENT_THREADS 32 20 | 21 | struct event { 22 | void (*callback)(void *, void *); 23 | void *arg1; 24 | void *arg2; 25 | }; 26 | 27 | enum event_thread_state { 28 | EVENT_THREAD_RUNNING, 29 | EVENT_THREAD_SLEEPING, 30 | EVENT_THREAD_STOPPED, 31 | }; 32 | 33 | struct event_thread { 34 | enum event_thread_state state; 35 | char *name; 36 | void (*update_cb)(void *); 37 | void (*stop_cb)(void *); 38 | void *arg; 39 | struct con_queue *events; 40 | pthread_t tid; 41 | sem_t sem; 42 | }; 43 | 44 | static atomic_int g_thread_cnt; 45 | static struct event_thread g_threads[MAX_EVENT_THREADS]; 46 | 47 | static struct event * 48 | allocate_event(void (*callback)(void *, void *), void *arg1, void *arg2) 49 | { 50 | struct event *event; 51 | 52 | event = calloc(1, sizeof(*event)); 53 | if (!event) { 54 | LOG_FATAL("Failed to allocate memory for event.\n"); 55 | return NULL; 56 | } 57 | 58 | event->callback = callback; 59 | event->arg1 = arg1; 60 | event->arg2 = arg2; 61 | 62 | return event; 63 | } 64 | 65 | int 66 | event_thread_enqueue_event(struct event_thread *thread, 67 | void (*callback)(void *, void *), void *arg1, void *arg2) 68 | { 69 | struct event *event; 70 | 71 | if (!thread) { 72 | LOG_FATAL("Trying to enqueue event to inexistent thread.\n"); 73 | return -1; 74 | } 75 | 76 | event = allocate_event(callback, arg1, arg2); 77 | if (event == NULL) { 78 | LOG_FATAL("Failed to allocate event for enqueuing.\n"); 79 | return -1; 80 | } 81 | 82 | con_queue_push(thread->events, event); 83 | return 0; 84 | } 85 | 86 | static void 87 | event_thread_destroy(struct event_thread *thread) 88 | { 89 | struct event *event; 90 | 91 | while (con_queue_pop(thread->events, (void **) &event) == 0) { 92 | free(event); 93 | } 94 | 95 | free(thread->events); 96 | free(thread->name); 97 | sem_destroy(&thread->sem); 98 | } 99 | 100 | static void 101 | event_thread_set_state_cb(void *arg1, void *arg2) 102 | { 103 | struct event_thread *thread = arg1; 104 | 105 | if (thread->state == EVENT_THREAD_STOPPED) { 106 | /* thread will be stopped with current loop tick */ 107 | return; 108 | } 109 | 110 | thread->state = (enum event_thread_state)(intptr_t) arg2; 111 | } 112 | 113 | int 114 | event_thread_pause(struct event_thread *thread) 115 | { 116 | if (thread->state == EVENT_THREAD_STOPPED) { 117 | LOG_FATAL("Can't stop thread %p. It's not running.\n", thread); 118 | return -1; 119 | } 120 | 121 | if (event_thread_enqueue_event(thread, event_thread_set_state_cb, thread, 122 | (void *)(intptr_t) EVENT_THREAD_SLEEPING) != 0) { 123 | LOG_FATAL("Failed to pause thread %p.\n", thread); 124 | return -1; 125 | } 126 | 127 | return 0; 128 | } 129 | 130 | int 131 | event_thread_kick(struct event_thread *thread) 132 | { 133 | if (event_thread_enqueue_event(thread, event_thread_set_state_cb, thread, 134 | (void *)(intptr_t) EVENT_THREAD_RUNNING) != 0) { 135 | LOG_FATAL("Failed to wake thread %p.\n", thread); 136 | return -1; 137 | } 138 | 139 | sem_post(&thread->sem); 140 | return 0; 141 | } 142 | 143 | static void * 144 | event_thread_loop(void *arg) 145 | { 146 | struct event_thread *thread = arg; 147 | struct event *event; 148 | 149 | sem_init(&thread->sem, 0, 0); 150 | 151 | while (thread->state != EVENT_THREAD_STOPPED) { 152 | while (con_queue_pop(thread->events, (void **) &event) == 0) { 153 | event->callback(event->arg1, event->arg2); 154 | free(event); 155 | } 156 | 157 | if (thread->state == EVENT_THREAD_RUNNING && thread->update_cb) { 158 | thread->update_cb(thread->arg); 159 | } 160 | 161 | if (thread->state == EVENT_THREAD_SLEEPING) { 162 | sem_wait(&thread->sem); 163 | } 164 | } 165 | 166 | if (thread->stop_cb) { 167 | thread->stop_cb(thread->arg); 168 | } 169 | 170 | event_thread_destroy(thread); 171 | pthread_exit(NULL); 172 | return NULL; 173 | } 174 | 175 | struct event_thread * 176 | event_thread_create(const char *name, void (*update_cb)(void *), 177 | void (*stop_cb)(void *), void *arg) 178 | { 179 | struct event_thread *thread; 180 | int thread_id, rc; 181 | 182 | thread_id = atomic_fetch_add(&g_thread_cnt, 1); 183 | if (thread_id >= MAX_EVENT_THREADS) { 184 | LOG_FATAL("Reached the thread limit (%d).\n", MAX_EVENT_THREADS); 185 | goto err; 186 | } 187 | 188 | thread = &g_threads[thread_id]; 189 | 190 | thread->state = EVENT_THREAD_RUNNING; 191 | thread->name = strdup(name); 192 | if (!thread->name) { 193 | LOG_ERR("strdup() failed.\n"); 194 | goto err; 195 | } 196 | 197 | thread->events = calloc(1, sizeof(*thread->events) + 32 * sizeof(void *)); 198 | if (!thread->events) { 199 | LOG_ERR("calloc() failed.\n"); 200 | goto name_err; 201 | } 202 | 203 | thread->events->size = 32; 204 | thread->update_cb = update_cb; 205 | thread->stop_cb = stop_cb; 206 | thread->arg = arg; 207 | 208 | rc = pthread_create(&thread->tid, NULL, event_thread_loop, thread); 209 | if (rc != 0) { 210 | LOG_ERR("pthread_create() failed: %s.\n", strerror(-rc)); 211 | goto events_err; 212 | } 213 | 214 | return thread; 215 | 216 | events_err: 217 | free(thread->events); 218 | name_err: 219 | free(thread->name); 220 | err: 221 | return NULL; 222 | } 223 | 224 | int 225 | event_thread_stop(struct event_thread *thread) 226 | { 227 | if (!thread) { 228 | LOG_ERR("No thread to stop.\n"); 229 | return -1; 230 | } 231 | if (thread->state == EVENT_THREAD_STOPPED) { 232 | LOG_ERR("Thread %p is not running.\n", (void *)thread); 233 | return -1; 234 | } 235 | 236 | if (event_thread_enqueue_event(thread, event_thread_set_state_cb, thread, 237 | (void *)(intptr_t) EVENT_THREAD_STOPPED) != 0) { 238 | LOG_ERR("Failed to stop thread %p.\n", (void *)thread); 239 | return -1; 240 | } 241 | 242 | sem_post(&thread->sem); 243 | return 0; 244 | } 245 | 246 | struct event_thread * 247 | event_thread_self(void) 248 | { 249 | struct event_thread *thread_tmp; 250 | int i; 251 | 252 | for (i = 0; i < MAX_EVENT_THREADS; ++i) { 253 | thread_tmp = &g_threads[i]; 254 | if (thread_tmp->tid == pthread_self()) { 255 | return thread_tmp; 256 | } 257 | } 258 | 259 | return NULL; 260 | } 261 | 262 | void 263 | event_thread_lib_init(void) 264 | { 265 | atomic_init(&g_thread_cnt, 0); 266 | } 267 | 268 | void 269 | event_thread_lib_wait(void) 270 | { 271 | struct event_thread *thread; 272 | int i; 273 | 274 | for (i = 0; i < MAX_EVENT_THREADS; ++i) { 275 | thread = &g_threads[i]; 276 | if (thread->tid && thread->state != EVENT_THREAD_STOPPED) { 277 | pthread_join(thread->tid, NULL); 278 | event_thread_lib_wait(); 279 | return; 280 | } 281 | } 282 | 283 | fflush(stdout); 284 | } 285 | 286 | static void * 287 | event_thread_lib_shutdown_cb(void *arg) 288 | { 289 | struct event_thread *thread; 290 | int i; 291 | 292 | for (i = 0; i < MAX_EVENT_THREADS; ++i) { 293 | thread = &g_threads[i]; 294 | if (thread->tid && thread->state != EVENT_THREAD_STOPPED) { 295 | event_thread_stop(thread); 296 | } 297 | } 298 | 299 | return NULL; 300 | } 301 | 302 | void 303 | event_thread_lib_shutdown(void) 304 | { 305 | pthread_t tid; 306 | if (pthread_create(&tid, NULL, event_thread_lib_shutdown_cb, NULL) != 0) { 307 | LOG_FATAL("pthread_create() failed, cannot start shutdown thread.\n"); 308 | abort(); 309 | } 310 | pthread_detach(tid); 311 | } 312 | -------------------------------------------------------------------------------- /config.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | 11 | #include "config.h" 12 | 13 | struct brother_config g_config; 14 | 15 | void init_item_config(struct item_config *item_config, 16 | const struct item_config *template, const char *name); 17 | 18 | const char *g_scan_func_str[CONFIG_SCAN_MAX_FUNCS] = { 19 | [SCAN_FUNC_IMAGE] = "IMAGE", 20 | [SCAN_FUNC_OCR] = "OCR", 21 | [SCAN_FUNC_EMAIL] = "EMAIL", 22 | [SCAN_FUNC_FILE] = "FILE", 23 | }; 24 | 25 | static void init_default_device_config(struct item_config *item_config, 26 | char *default_hostname) { 27 | struct scan_param *param; 28 | int i = 0; 29 | 30 | item_config->hostname = strdup(default_hostname); 31 | item_config->page_init_timeout = CONFIG_NETWORK_DEFAULT_PAGE_INIT_TIMEOUT; 32 | item_config->page_finish_timeout = CONFIG_NETWORK_DEFAULT_PAGE_FINISH_TIMEOUT; 33 | 34 | #define ADD_SCAN_PARAM(ID, VAL) \ 35 | param = &item_config->scan_params[i++]; \ 36 | param->id = ID; \ 37 | strcpy(param->value, VAL); 38 | 39 | ADD_SCAN_PARAM('A', ""); 40 | ADD_SCAN_PARAM('B', "50"); 41 | ADD_SCAN_PARAM('C', "JPEG"); 42 | ADD_SCAN_PARAM('D', "SIN"); 43 | ADD_SCAN_PARAM('E', ""); 44 | ADD_SCAN_PARAM('F', ""); 45 | ADD_SCAN_PARAM('G', "1"); 46 | ADD_SCAN_PARAM('J', ""); 47 | ADD_SCAN_PARAM('L', "128"); 48 | ADD_SCAN_PARAM('M', "CGRAY"); 49 | ADD_SCAN_PARAM('N', "50"); 50 | ADD_SCAN_PARAM('P', "A4"); 51 | ADD_SCAN_PARAM('R', "300,300"); 52 | ADD_SCAN_PARAM('T', "JPEG"); 53 | 54 | #undef ADD_SCAN_PARAM 55 | } 56 | 57 | void init_item_config(struct item_config *item_config, 58 | const struct item_config *template, const char *name) { 59 | memcpy(item_config, template, sizeof(*item_config)); 60 | item_config->hostname = strdup(name); 61 | } 62 | 63 | const struct item_config *find_preset(const struct item_config *head, 64 | char *name) { 65 | while (head != NULL) { 66 | if (!strcmp(name, head->hostname)) return head; 67 | head = TAILQ_NEXT(head, tailq); 68 | } 69 | return NULL; 70 | } 71 | 72 | const struct item_config *config_find_by_func_and_name( 73 | const struct device_config *dev_config, enum scan_func func, 74 | const char *name) { 75 | const struct item_config *head = TAILQ_FIRST(&dev_config->items); 76 | while (head != NULL) { 77 | if (func == head->scan_func && !strcmp(head->hostname, name)) return head; 78 | head = TAILQ_NEXT(head, tailq); 79 | } 80 | return NULL; 81 | } 82 | 83 | enum scan_func config_get_scan_func_by_name(const char *name) { 84 | int i; 85 | for (i = 0; i < CONFIG_SCAN_MAX_FUNCS; ++i) { 86 | if (strcmp(name, g_scan_func_str[i]) == 0) { 87 | break; 88 | } 89 | } 90 | 91 | if (i == CONFIG_SCAN_MAX_FUNCS) { 92 | fprintf(stderr, "Error: invalid scan.func type '%s'.\n", name); 93 | return SCAN_FUNC_INVALID; 94 | } 95 | return i; 96 | } 97 | 98 | int 99 | config_init(const char *config_path) 100 | { 101 | FILE *config; 102 | struct device_config *dev_config = NULL; 103 | 104 | char buf[1024]; 105 | char var_str[1024]; 106 | char var2_str[16]; 107 | char var_char; 108 | unsigned var_uint; 109 | int rc = -1, param_count = 0, i; 110 | 111 | TAILQ_INIT(&g_config.devices); 112 | 113 | TAILQ_HEAD(, item_config) presets; 114 | TAILQ_INIT(&presets); 115 | struct item_config *default_preset = calloc(1, sizeof(*default_preset)); 116 | struct item_config *preset_config = default_preset; 117 | init_default_device_config(default_preset, "default"); 118 | TAILQ_INSERT_TAIL(&presets, default_preset, tailq); 119 | 120 | config = fopen(config_path, "r"); 121 | if (config == NULL) { 122 | fprintf(stderr, "Could not open config file '%s'.\n", config_path); 123 | abort(); 124 | } 125 | 126 | while (fgets((char *) buf, sizeof(buf), config)) { 127 | if (sscanf(buf, "define-preset %15s", var_str) == 1) { 128 | // Create a new preset. Insert it into the list. 129 | preset_config = calloc(1, sizeof(*default_preset)); 130 | init_item_config(preset_config, default_preset, var_str); 131 | TAILQ_INSERT_TAIL(&presets, preset_config, tailq); 132 | } else if (sscanf((char *)buf, "ip %64s", var_str) == 1) { 133 | // Create a new device entry. No items by default. 134 | dev_config = calloc(1, sizeof(*dev_config)); 135 | preset_config = NULL; 136 | if (dev_config == NULL) { 137 | fprintf(stderr, 138 | "Error: could not alloc memory for device config '%s'.\n", 139 | var_str); 140 | goto out; 141 | } 142 | dev_config->ip = strdup(var_str); 143 | dev_config->timeout = CONFIG_NETWORK_DEFAULT_TIMEOUT_SEC; 144 | TAILQ_INIT(&dev_config->items); 145 | TAILQ_INSERT_TAIL(&g_config.devices, dev_config, tailq); 146 | } else if (sscanf(buf, "preset %15s %15s", var_str, var2_str) == 2) { 147 | // Create a new item within the current device. 148 | if (dev_config == NULL) { 149 | fprintf(stderr, 150 | "Cannot use a preset %s before configuring a device (start " 151 | "with 'ip x.x.x.x')\n", 152 | var_str); 153 | goto out; 154 | } 155 | const struct item_config *existing_preset = 156 | find_preset(TAILQ_FIRST(&presets), var_str); 157 | if (existing_preset == NULL) { 158 | fprintf(stderr, "preset %s wasn't defined yet\n", var_str); 159 | goto out; 160 | } 161 | enum scan_func scan_func = config_get_scan_func_by_name(var2_str); 162 | if (scan_func == SCAN_FUNC_INVALID) { 163 | goto out; 164 | } 165 | preset_config = calloc(1, sizeof(*preset_config)); 166 | init_item_config(preset_config, existing_preset, 167 | existing_preset->hostname); 168 | preset_config->scan_func = scan_func; 169 | TAILQ_INSERT_TAIL(&dev_config->items, preset_config, tailq); 170 | } else if (sscanf((char *)buf, "network.timeout %u", &var_uint) == 1) { 171 | if (dev_config == NULL) { 172 | fprintf(stderr, "Error: timeout specified without a device.\n"); 173 | goto out; 174 | } 175 | dev_config->timeout = var_uint; 176 | } else if (sscanf((char *)buf, "hostname %15s", var_str) == 1) { 177 | if (preset_config == NULL) { 178 | fprintf(stderr, "Error: hostname specified without a preset.\n"); 179 | goto out; 180 | } 181 | char *old_hostname = preset_config->hostname; 182 | preset_config->hostname = strdup(var_str); 183 | free(old_hostname); 184 | } else if (sscanf((char *)buf, "password %4s", var_str) == 1) { 185 | if (preset_config == NULL) { 186 | fprintf(stderr, "Error: password specified without a preset.\n"); 187 | goto out; 188 | } 189 | char *old_password = preset_config->password; 190 | preset_config->password = strdup(var_str); 191 | if (old_password) free(old_password); 192 | } else if (sscanf((char *)buf, "network.page.init.timeout %u", 193 | &var_uint) == 1) { 194 | if (preset_config == NULL) { 195 | fprintf( 196 | stderr, 197 | "Error: network.page.init.timeout specified without a preset.\n"); 198 | goto out; 199 | } 200 | preset_config->page_init_timeout = var_uint; 201 | } else if (sscanf((char *)buf, "network.page.finish.timeout %u", 202 | &var_uint) == 1) { 203 | if (preset_config == NULL) { 204 | fprintf(stderr, 205 | "Error: network.page.finish.timeout specified without a " 206 | "preset.\n"); 207 | goto out; 208 | } 209 | preset_config->page_finish_timeout = var_uint; 210 | } else if (sscanf((char *)buf, "scan.param %c %15s", &var_char, 211 | var_str) == 2) { 212 | if (preset_config == NULL) { 213 | fprintf(stderr, "Error: scan.param specified without a preset.\n"); 214 | goto out; 215 | } 216 | 217 | if (param_count >= CONFIG_SCAN_MAX_PARAMS) { 218 | fprintf(stderr, "Error: too many scan.params. Max %d.\n", 219 | CONFIG_SCAN_MAX_PARAMS); 220 | goto out; 221 | } 222 | 223 | for (i = 0; i < CONFIG_SCAN_MAX_PARAMS; ++i) { 224 | if (preset_config->scan_params[i].id == var_char) { 225 | strcpy(preset_config->scan_params[i].value, var_str); 226 | break; 227 | } 228 | } 229 | 230 | if (i == CONFIG_SCAN_MAX_PARAMS) { 231 | fprintf(stderr, "Error: invalid scan.param type '%c'.\n", var_char); 232 | goto out; 233 | } 234 | 235 | ++param_count; 236 | } else if (sscanf((char *)buf, "scan.func %1024[^\n]", var_str) == 1) { 237 | if (preset_config == NULL) { 238 | fprintf(stderr, "Error: scan.param specified without a preset.\n"); 239 | goto out; 240 | } 241 | preset_config->scan_command = strdup(var_str); 242 | } else if (*buf == '#' || !*buf || *buf == '\n') { 243 | // Ignore empty or comment-only lines. 244 | continue; 245 | } else { 246 | fprintf(stderr, "Invalid configuration option: %s", buf); 247 | goto out; 248 | } 249 | } 250 | rc = 0; 251 | out: 252 | fclose(config); 253 | return rc; 254 | } 255 | -------------------------------------------------------------------------------- /connection.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "connection.h" 19 | #include "log.h" 20 | 21 | struct brother_conn { 22 | enum brother_connection_type type; 23 | int fd; 24 | bool connected; 25 | bool is_stream; 26 | struct sockaddr_in sin_me; 27 | struct sockaddr_in sin_oth; 28 | struct timeval timeout; 29 | char *buf; // buffered output 30 | size_t buf_position; 31 | size_t buf_filled; 32 | }; 33 | 34 | static int 35 | create_socket(struct brother_conn *conn, unsigned timeout_sec) 36 | { 37 | int one = 1; 38 | 39 | if (conn->type == BROTHER_CONNECTION_TYPE_UDP) { 40 | conn->fd = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); 41 | } else { 42 | conn->fd = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); 43 | conn->is_stream = true; 44 | if (conn->buf) free(conn->buf); 45 | conn->buf = calloc(1, BROTHER_CONN_READ_BUFSIZE); 46 | conn->buf_position = 0; 47 | conn->buf_filled = 0; 48 | } 49 | 50 | if (conn->fd == -1) { 51 | perror("socket"); 52 | return -1; 53 | } 54 | 55 | conn->timeout.tv_sec = timeout_sec; 56 | conn->timeout.tv_usec = 0; 57 | 58 | if (setsockopt(conn->fd, SOL_SOCKET, SO_RCVTIMEO, (char *)&conn->timeout, 59 | sizeof(conn->timeout)) != 0) { 60 | perror("setsockopt recv"); 61 | } 62 | 63 | if (setsockopt(conn->fd, SOL_SOCKET, SO_SNDTIMEO, (char *)&conn->timeout, 64 | sizeof(conn->timeout)) != 0) { 65 | perror("setsockopt send"); 66 | } 67 | 68 | if (setsockopt(conn->fd, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(int)) != 0) { 69 | perror("setsockopt"); 70 | } 71 | 72 | return 0; 73 | } 74 | 75 | struct brother_conn * 76 | brother_conn_open(enum brother_connection_type type, unsigned timeout_sec) 77 | { 78 | struct brother_conn *conn; 79 | 80 | conn = calloc(1, sizeof(*conn)); 81 | if (conn == NULL) { 82 | return NULL; 83 | } 84 | 85 | conn->type = type; 86 | if (create_socket(conn, timeout_sec) != 0) { 87 | return NULL; 88 | } 89 | 90 | return conn; 91 | } 92 | 93 | int 94 | brother_conn_bind(struct brother_conn *conn, in_port_t local_port) 95 | { 96 | conn->sin_me.sin_family = AF_INET; 97 | conn->sin_me.sin_addr.s_addr = htonl(INADDR_ANY); 98 | conn->sin_me.sin_port = local_port; 99 | 100 | if (bind(conn->fd, (struct sockaddr *)&conn->sin_me, 101 | sizeof(conn->sin_me)) != 0) { 102 | perror("bind"); 103 | return -1; 104 | } 105 | 106 | return 0; 107 | } 108 | 109 | void brother_conn_disconnect(struct brother_conn *conn) { 110 | if (conn->connected) { 111 | close(conn->fd); 112 | conn->connected = false; 113 | conn->fd = 0; 114 | } 115 | } 116 | 117 | int 118 | brother_conn_reconnect(struct brother_conn *conn, in_addr_t dest_addr, 119 | in_port_t dest_port) 120 | { 121 | int retries; 122 | brother_conn_disconnect(conn); 123 | if (!conn->fd) { 124 | if (create_socket(conn, (unsigned)conn->timeout.tv_sec) != 0) { 125 | return -1; 126 | } 127 | 128 | if (conn->sin_me.sin_port && 129 | brother_conn_bind(conn, conn->sin_me.sin_port) != 0) { 130 | return -1; 131 | } 132 | } 133 | 134 | conn->is_stream = true; 135 | conn->sin_oth.sin_addr.s_addr = dest_addr; 136 | conn->sin_oth.sin_family = AF_INET; 137 | conn->sin_oth.sin_port = dest_port; 138 | 139 | const int max_retries = 3; 140 | for (retries = 0; retries < max_retries; ++retries) { 141 | usleep(1000 * 25); 142 | if (connect(conn->fd, (struct sockaddr *)&conn->sin_oth, 143 | sizeof(conn->sin_oth)) == 0) { 144 | break; 145 | } 146 | perror("connect"); 147 | } 148 | 149 | if (retries == max_retries) { 150 | perror("connect"); 151 | return -1; 152 | } 153 | 154 | conn->connected = true; 155 | return 0; 156 | } 157 | 158 | int 159 | brother_conn_sendto(struct brother_conn *conn, const void *buf, size_t len, 160 | in_addr_t dest_addr, in_port_t dest_port) 161 | { 162 | struct sockaddr_in sin_oth; 163 | ssize_t sent_bytes; 164 | 165 | if (conn->type == BROTHER_CONNECTION_TYPE_TCP) { 166 | LOG_ERR("sendto can't be used with TCP sockets\n"); 167 | return -1; 168 | } 169 | 170 | sin_oth.sin_addr.s_addr = dest_addr; 171 | sin_oth.sin_family = AF_INET; 172 | sin_oth.sin_port = dest_port; 173 | 174 | do { 175 | sent_bytes = sendto(conn->fd, buf, len, 0, 176 | (struct sockaddr *) &sin_oth, 177 | sizeof(sin_oth)); 178 | } while (errno == EINTR); 179 | 180 | if (sent_bytes < 0) { 181 | perror("sendto"); 182 | } 183 | 184 | LOG_DEBUG("sent %zd/%zu bytes to :%d\n", sent_bytes, len, 185 | ntohs(sin_oth.sin_port)); 186 | DUMP_DEBUG(buf, len); 187 | 188 | return (int) sent_bytes; 189 | } 190 | 191 | int 192 | brother_conn_poll(struct brother_conn *conn, unsigned timeout_sec) 193 | { 194 | struct pollfd pfd; 195 | int rc; 196 | 197 | pfd.fd = conn->fd; 198 | pfd.events = POLLIN | POLLERR | POLLHUP | POLLNVAL; 199 | 200 | do { 201 | rc = poll(&pfd, 1, timeout_sec * 1000); 202 | } while (rc == -EINTR); 203 | if (rc < 0) { 204 | return rc; 205 | } 206 | 207 | return pfd.revents & (POLLIN | POLLERR | POLLHUP | POLLNVAL); 208 | } 209 | 210 | int 211 | brother_conn_send(struct brother_conn *conn, const void *buf, size_t len) 212 | { 213 | ssize_t sent_bytes; 214 | 215 | do { 216 | if (conn->type == BROTHER_CONNECTION_TYPE_UDP) { 217 | sent_bytes = sendto(conn->fd, buf, len, 0, 218 | (struct sockaddr *) &conn->sin_oth, 219 | sizeof(conn->sin_oth)); 220 | } else { 221 | sent_bytes = send(conn->fd, buf, len, 0); 222 | } 223 | } while (errno == EINTR); 224 | 225 | if (sent_bytes < 0) { 226 | perror("sendto"); 227 | } 228 | 229 | LOG_DEBUG("sent %zd/%zu bytes to %d", sent_bytes, len, 230 | ntohs(conn->sin_oth.sin_port)); 231 | DUMP_DEBUG(buf, len); 232 | 233 | return (int) sent_bytes; 234 | } 235 | 236 | int 237 | brother_conn_receive(struct brother_conn *conn, void *buf, size_t len) 238 | { 239 | ssize_t recv_bytes; 240 | struct sockaddr_in sin_oth_tmp; 241 | socklen_t slen; 242 | 243 | slen = sizeof(sin_oth_tmp); 244 | 245 | do { 246 | if (conn->type == BROTHER_CONNECTION_TYPE_UDP) { 247 | recv_bytes = recvfrom(conn->fd, buf, len, 0, 248 | (struct sockaddr *) &sin_oth_tmp, &slen); 249 | } else { 250 | recv_bytes = recv(conn->fd, buf, len, 0); 251 | } 252 | } while (errno == EINTR); 253 | 254 | if (recv_bytes < 0) { 255 | if (errno != EAGAIN && errno != EWOULDBLOCK) { 256 | perror("recvfrom"); 257 | } 258 | return -1; 259 | } 260 | 261 | if (conn->type == BROTHER_CONNECTION_TYPE_UDP && !conn->is_stream) { 262 | memcpy(&conn->sin_oth, &sin_oth_tmp, sizeof(conn->sin_oth)); 263 | } 264 | 265 | LOG_DEBUG("received %zd bytes from :%d\n", recv_bytes, 266 | ntohs(conn->sin_oth.sin_port)); 267 | DUMP_DEBUG(buf, recv_bytes); 268 | 269 | return (int) recv_bytes; 270 | } 271 | 272 | size_t brother_conn_data_available(struct brother_conn *conn) { 273 | return conn->buf_filled - conn->buf_position; 274 | } 275 | 276 | int brother_conn_fill_buffer(struct brother_conn *conn, size_t size, 277 | int timeout_seconds) { 278 | if (brother_conn_data_available(conn) >= size) { 279 | return 0; 280 | } 281 | int rc = brother_conn_poll(conn, timeout_seconds); 282 | if (rc < 0) { 283 | return -1; 284 | } 285 | if (brother_conn_peek(conn, size - brother_conn_data_available(conn)) == 286 | NULL) { 287 | return -1; 288 | } 289 | return 0; 290 | } 291 | 292 | void *brother_conn_peek(struct brother_conn *conn, size_t len) { 293 | int rc; 294 | if (conn->type == BROTHER_CONNECTION_TYPE_UDP) { 295 | fprintf(stderr, "Buffered reading is only available to TCP sockets"); 296 | return NULL; 297 | } 298 | if (BROTHER_CONN_READ_BUFSIZE < len) { 299 | fprintf(stderr, "Can't read more than %zd bytes at once.", len); 300 | return NULL; 301 | } 302 | if (BROTHER_CONN_READ_BUFSIZE - conn->buf_position < len) { 303 | memcpy(conn->buf, conn->buf + conn->buf_position, 304 | brother_conn_data_available(conn)); 305 | conn->buf_filled -= conn->buf_position; 306 | conn->buf_position = 0; 307 | } 308 | size_t available_chars = conn->buf_filled - conn->buf_position; 309 | if (available_chars < len) { 310 | rc = brother_conn_receive(conn, conn->buf + conn->buf_filled, 311 | BROTHER_CONN_READ_BUFSIZE - conn->buf_filled); 312 | if (rc < 0) { 313 | return NULL; 314 | } 315 | conn->buf_filled += rc; 316 | } 317 | return conn->buf + conn->buf_position; 318 | } 319 | 320 | void *brother_conn_read(struct brother_conn *conn, size_t len) { 321 | void *result = brother_conn_peek(conn, len); 322 | if (result != NULL) { 323 | conn->buf_position += len; 324 | } 325 | return result; 326 | } 327 | 328 | int 329 | brother_conn_get_client_ip(struct brother_conn *conn, char ip[16]) 330 | { 331 | const char *ret; 332 | 333 | ret = inet_ntop(AF_INET, &conn->sin_oth.sin_addr, ip, 16); 334 | return ret != NULL ? 0 : -1; 335 | } 336 | 337 | int 338 | brother_conn_get_local_ip(struct brother_conn *conn, char ip[16]) 339 | { 340 | const char *ret; 341 | struct sockaddr_in name; 342 | socklen_t namelen = sizeof(name); 343 | 344 | if (getsockname(conn->fd, (struct sockaddr *) &name, &namelen) != 0) { 345 | perror("getsockname"); 346 | return -1; 347 | } 348 | 349 | ret = inet_ntop(AF_INET, &name.sin_addr, ip, 16); 350 | return ret != NULL ? 0 : -1; 351 | } 352 | 353 | void 354 | brother_conn_close(struct brother_conn *conn) 355 | { 356 | close(conn->fd); 357 | if (conn->buf) free(conn->buf); 358 | free(conn); 359 | } 360 | -------------------------------------------------------------------------------- /device_handler.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "config.h" 19 | #include "connection.h" 20 | #include "data_channel.h" 21 | #include "device_handler.h" 22 | #include "event_thread.h" 23 | #include "log.h" 24 | #include "snmp.h" 25 | 26 | #define DEVICE_REGISTER_DURATION_SEC 360 27 | #define DEVICE_KEEPALIVE_DURATION_SEC 5 28 | #define DEVICE_OFFLINE_RETRY_DURATION_SEC 5 29 | #define DEVICE_SCAN_MAX_FUNCS_PER_PACKET 4 30 | #define BUTTON_HANDLER_PORT 54925 31 | 32 | struct device { 33 | in_addr_t ip; 34 | struct data_channel *channel; 35 | int status; 36 | char local_ip[16]; 37 | time_t next_ping_time; 38 | time_t next_register_time; 39 | const struct device_config *config; 40 | TAILQ_ENTRY(device) tailq; 41 | }; 42 | 43 | struct device_handler { 44 | struct brother_conn *button_conn; 45 | struct event_thread *thread; 46 | struct brother_poll_group *devices_poll_group; 47 | TAILQ_HEAD(, device) devices; 48 | }; 49 | 50 | #define BUTTON_HANDLER_NETWORK_TIMEOUT 3 51 | 52 | static atomic_int g_appnum; 53 | static struct device_handler g_dev_handler; 54 | 55 | static char 56 | digit_to_hex(int n) 57 | { 58 | const char *trans_table = "0123456789ABCDEF"; 59 | 60 | return trans_table[n & 0xf]; 61 | } 62 | 63 | static int 64 | encode_password(const char *pass, char *buf) 65 | { 66 | const uint8_t g_pass_shuffle_table[] = { 67 | 0x05, 0x0A, 0x1F, 0x18, 0x08, 0x1E, 0x1C, 0x01, 68 | 0x11, 0x0D, 0x0C, 0x0E, 0x1B, 0x03, 0x15, 0x16, 69 | 0x1D, 0x14, 0x00, 0x07, 0x10, 0x0B, 0x19, 0x04, 70 | 0x13, 0x12, 0x06, 0x1A, 0x09, 0x02, 0x0F, 0x17 71 | }; 72 | const uint8_t g_pass_key[] = { 0xCA, 0xFE, 0x28, 0xA9 }; 73 | uint8_t tmp_buf[32] = {0}; 74 | char tmp; 75 | int i, j; 76 | 77 | for (i = 0; i < 4; ++i) { 78 | tmp = pass[i]; 79 | for (j = 0; j < 8; ++j) { 80 | tmp_buf[g_pass_shuffle_table[8 * i + j] >> 3] |= 81 | ((tmp & 1) << (g_pass_shuffle_table[8 * i + j] & 7)); 82 | tmp >>= 1; 83 | } 84 | } 85 | 86 | for (i = 0; i < 4; ++i) { 87 | tmp_buf[i] ^= g_pass_key[i]; 88 | } 89 | 90 | for (i = 0; i < 4; ++i) { 91 | *buf++ = digit_to_hex((unsigned char) tmp_buf[i] >> 4); 92 | *buf++ = digit_to_hex(tmp_buf[i] & 0xF); 93 | } 94 | 95 | *buf = 0; 96 | 97 | return 0; 98 | } 99 | 100 | static int 101 | register_scanner_driver(struct device *dev, char local_ip[16], bool enabled) 102 | { 103 | const char *functions[DEVICE_SCAN_MAX_FUNCS_PER_PACKET] = {0}; 104 | char msg[DEVICE_SCAN_MAX_FUNCS_PER_PACKET][128]; 105 | char pass_buf[9] = {0}; 106 | int num_funcs = 0, rc; 107 | struct item_config *item; 108 | 109 | TAILQ_FOREACH(item, &dev->config->items, tailq) { 110 | if (item->password != NULL && strlen(item->password) == 4) { 111 | encode_password(item->password, pass_buf); 112 | } 113 | if (!item->appnum) { 114 | item->appnum = atomic_fetch_add(&g_appnum, 1); 115 | } 116 | 117 | rc = snprintf(msg[num_funcs], sizeof(msg), 118 | "TYPE=BR;" 119 | "BUTTON=SCAN;" 120 | "USER=\"%s\";" 121 | "FUNC=%s;" 122 | "HOST=%s:%d;" 123 | "APPNUM=%d;" 124 | "DURATION=%d;" 125 | "BRID=%s;" 126 | "CC=1;", 127 | item->hostname, g_scan_func_str[item->scan_func], local_ip, 128 | BUTTON_HANDLER_PORT, item->appnum, 129 | DEVICE_REGISTER_DURATION_SEC, pass_buf); 130 | 131 | if (rc < 0 || rc == 255) { 132 | return -1; 133 | } 134 | 135 | functions[num_funcs] = msg[num_funcs]; 136 | num_funcs++; 137 | if (num_funcs == DEVICE_SCAN_MAX_FUNCS_PER_PACKET) { 138 | rc = snmp_register_scanner_driver(g_dev_handler.button_conn, enabled, 139 | functions, num_funcs, dev->ip); 140 | if (rc < 0) { 141 | return rc; 142 | } 143 | num_funcs = 0; 144 | } 145 | } 146 | if (num_funcs > 0) { 147 | return snmp_register_scanner_driver(g_dev_handler.button_conn, enabled, 148 | functions, num_funcs, dev->ip); 149 | ; 150 | } 151 | return 0; 152 | } 153 | 154 | struct device * 155 | device_handler_add_device(struct device_config *config) 156 | { 157 | struct device *dev; 158 | struct brother_conn *conn; 159 | uint8_t buf[1024]; 160 | int status, rc; 161 | char local_ip[16]; 162 | 163 | conn = brother_conn_open(BROTHER_CONNECTION_TYPE_UDP, 164 | BUTTON_HANDLER_NETWORK_TIMEOUT); 165 | if (conn == NULL) { 166 | LOG_ERR("Cannot open an UDP socket for device %s.\n", config->ip); 167 | return NULL; 168 | } 169 | 170 | rc = brother_conn_reconnect(conn, inet_addr(config->ip), htons(161)); 171 | if (rc != 0) { 172 | LOG_ERR("Can't connect to %s:161.\n", config->ip); 173 | brother_conn_close(conn); 174 | return NULL; 175 | } 176 | 177 | rc = brother_conn_get_local_ip(conn, local_ip); 178 | brother_conn_close(conn); 179 | if (rc != 0) { 180 | LOG_ERR("Can't get the local ip address that connected to %s:161.\n", 181 | config->ip); 182 | return NULL; 183 | 184 | } 185 | 186 | status = snmp_get_printer_status(g_dev_handler.button_conn, buf, 187 | sizeof(buf), inet_addr(config->ip)); 188 | 189 | if (status != 10001 && status != 10006 && status != 40000 && status != 40038 ) { // 10001: normal. 10006: low ink. 40038: empty ink. 40000: unknown but OK. 190 | LOG_ERR("Error: device at %s is unreachable. (status: %d)\n", config->ip, 191 | status); 192 | return NULL; 193 | } 194 | 195 | dev = calloc(1, sizeof(*dev)); 196 | if (dev == NULL) { 197 | LOG_ERR("Could not calloc memory for device at %s.\n", config->ip); 198 | return NULL; 199 | } 200 | 201 | dev->ip = inet_addr(config->ip); 202 | snprintf(dev->local_ip, sizeof(dev->local_ip), "%s", local_ip); 203 | dev->config = config; 204 | dev->channel = data_channel_create(config); 205 | if (dev->channel == NULL) { 206 | LOG_ERR("Failed to create data_channel for device %s.\n", config->ip); 207 | free(dev); 208 | return NULL; 209 | } 210 | 211 | TAILQ_INSERT_TAIL(&g_dev_handler.devices, dev, tailq); 212 | return dev; 213 | } 214 | 215 | static char *device_handler_extract_string(const char *buf, char *before, 216 | char *after) { 217 | char *startDelim = strstr(buf, before); 218 | if (!startDelim) { 219 | LOG_ERR("Could not extract find (%s...%s) in packet:\n", before, after); 220 | DUMP_ERR(buf, strlen(buf)); 221 | return NULL; 222 | } 223 | startDelim += strlen(before); 224 | char *endDelim = strstr(startDelim, after); 225 | if (!endDelim) { 226 | LOG_ERR("Could not extract find (%s...%s) in packet:\n", before, after); 227 | DUMP_ERR(buf, strlen(buf)); 228 | return NULL; 229 | } 230 | return strndup(startDelim, endDelim - startDelim); 231 | } 232 | 233 | char *device_handler_extract_hostname(const char *buf) { 234 | return device_handler_extract_string(buf, "USER=\"", "\""); 235 | } 236 | 237 | static enum scan_func device_handler_extract_func(const char *buf) { 238 | char *scan_func_name = device_handler_extract_string(buf, "FUNC=", ";"); 239 | if (!scan_func_name) return SCAN_FUNC_INVALID; 240 | enum scan_func scan_func = config_get_scan_func_by_name(scan_func_name); 241 | free(scan_func_name); 242 | return scan_func; 243 | } 244 | 245 | static void 246 | device_handler_loop(void *arg) 247 | { 248 | struct device *dev; 249 | time_t time_now; 250 | char client_ip[16]; 251 | uint8_t buf[1024]; 252 | int msg_len, rc; 253 | 254 | TAILQ_FOREACH(dev, &g_dev_handler.devices, tailq) { 255 | time_now = time(NULL); 256 | int need_ping = difftime(time_now, dev->next_ping_time) > 0; 257 | int need_register = difftime(time_now, dev->next_register_time) > 0; 258 | 259 | if (need_ping) { 260 | /* only ping once per DEVICE_KEEPALIVE_DURATION_SEC */ 261 | dev->next_ping_time = time_now + DEVICE_KEEPALIVE_DURATION_SEC; 262 | dev->status = snmp_get_printer_status(g_dev_handler.button_conn, buf, 263 | sizeof(buf), dev->ip); 264 | if (dev->status != 10001 && dev->status != 10006 265 | && dev->status != 40000 && dev->status != 40038) { 266 | if (need_register) { 267 | // If the device is offline try re-establishing the connection 268 | // more frequently, so that we can re-register when it comes back 269 | dev->next_ping_time = 270 | time_now + DEVICE_OFFLINE_RETRY_DURATION_SEC; 271 | } 272 | LOG_WARN("Warn: device at %s is currently unreachable.\n", 273 | dev->config->ip); 274 | } 275 | } 276 | 277 | if (dev->status != 10001 && dev->status != 10006 278 | && dev->status != 40000 && dev->status != 40038) { 279 | continue; 280 | } 281 | 282 | if (need_register) { 283 | /* only register once per DEVICE_REGISTER_DURATION_SEC */ 284 | dev->next_register_time = time_now + DEVICE_REGISTER_DURATION_SEC; 285 | register_scanner_driver(dev, dev->local_ip, true); 286 | } 287 | } 288 | 289 | rc = brother_conn_poll(g_dev_handler.button_conn, 1); 290 | if (rc <= 0) { 291 | return; 292 | } 293 | 294 | /* try to receive scan event */ 295 | msg_len = brother_conn_receive(g_dev_handler.button_conn, buf, sizeof(buf)); 296 | if (msg_len < 0) { 297 | return; 298 | } 299 | 300 | rc = brother_conn_get_client_ip(g_dev_handler.button_conn, client_ip); 301 | if (rc < 0) { 302 | LOG_ERR("Invalid client IP. (IPv6 not supported yet)\n"); 303 | return; 304 | } 305 | 306 | char *hostname = device_handler_extract_hostname((char *)(buf + 4)); 307 | if (!hostname) { 308 | return; 309 | } 310 | 311 | enum scan_func scan_func = device_handler_extract_func((char *)(buf + 4)); 312 | if (scan_func < 0) { 313 | return; 314 | } 315 | 316 | // Find the device and associated item_config based on ip, scan_func and 317 | // hostname. 318 | TAILQ_FOREACH(dev, &g_dev_handler.devices, tailq) { 319 | if (strncmp(dev->config->ip, client_ip, 16) == 0) { 320 | const struct item_config *item = 321 | config_find_by_func_and_name(dev->config, scan_func, hostname); 322 | if (item != NULL) { 323 | msg_len = 324 | brother_conn_send(g_dev_handler.button_conn, buf, msg_len); 325 | if (msg_len < 0) { 326 | perror("sendto"); 327 | goto out; 328 | } 329 | data_channel_set_item(dev->channel, item); 330 | data_channel_kick(dev->channel); 331 | goto out; 332 | } 333 | } 334 | } 335 | 336 | LOG_WARN("Received scan button event from unknown device %s hostname:%s.\n", 337 | client_ip, hostname); 338 | out: 339 | free(hostname); 340 | } 341 | 342 | static void 343 | device_handler_stop(void *arg) 344 | { 345 | struct device *dev; 346 | char ip[16]; 347 | uint8_t buf[1024]; 348 | 349 | while ((dev = TAILQ_FIRST(&g_dev_handler.devices))) { 350 | TAILQ_REMOVE(&g_dev_handler.devices, dev, tailq); 351 | snmp_get_printer_status(g_dev_handler.button_conn, buf, sizeof(buf), 352 | dev->ip); 353 | brother_conn_get_local_ip(g_dev_handler.button_conn, ip); 354 | register_scanner_driver(dev, ip, false); 355 | free(dev); 356 | } 357 | } 358 | 359 | void device_handler_init(void) { 360 | struct device_config *dev_config; 361 | 362 | atomic_store(&g_appnum, 1); 363 | TAILQ_INIT(&g_dev_handler.devices); 364 | 365 | g_dev_handler.button_conn = brother_conn_open(BROTHER_CONNECTION_TYPE_UDP, 366 | BUTTON_HANDLER_NETWORK_TIMEOUT); 367 | if (g_dev_handler.button_conn == NULL) { 368 | LOG_FATAL("Failed to open a socket for the button handler.\n"); 369 | return; 370 | } 371 | 372 | if (brother_conn_bind(g_dev_handler.button_conn, 373 | htons(BUTTON_HANDLER_PORT)) != 0) { 374 | LOG_FATAL("Could not bind to the button handler port %d.\n", 375 | BUTTON_HANDLER_PORT); 376 | brother_conn_close(g_dev_handler.button_conn); 377 | return; 378 | } 379 | 380 | TAILQ_FOREACH(dev_config, &g_config.devices, tailq) { 381 | if (device_handler_add_device(dev_config) == NULL) { 382 | fprintf(stderr, "Error: could not load device '%s'.\n", dev_config->ip); 383 | return; 384 | } 385 | } 386 | 387 | g_dev_handler.thread = event_thread_create( 388 | "device_handler", device_handler_loop, device_handler_stop, NULL); 389 | if (g_dev_handler.thread == NULL) { 390 | LOG_FATAL("Could not init device_handler thread.\n"); 391 | brother_conn_close(g_dev_handler.button_conn); 392 | return; 393 | } 394 | } 395 | -------------------------------------------------------------------------------- /data_channel.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2017 Dariusz Stojaczyk. All Rights Reserved. 3 | * The following source code is released under an MIT-style license, 4 | * that can be found in the LICENSE file. 5 | */ 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | 21 | #include "connection.h" 22 | #include "data_channel.h" 23 | #include "event_thread.h" 24 | #include "log.h" 25 | 26 | #define DATA_CHANNEL_CHUNK_MAX_PROGRESS 0x1000 27 | #define DATA_CHANNEL_LOCAL_PORT 49424 28 | 29 | #define SET_CALLBACK(x) \ 30 | { \ 31 | LOG_DEBUG("data_channel->process_cb = " #x ";\n"); \ 32 | data_channel->process_cb = x; \ 33 | } 34 | 35 | // Unique ID (per scand instance) that groups a set of scanned pages. 36 | static atomic_int scan_id; 37 | 38 | struct data_packet_header { 39 | uint8_t id; 40 | uint16_t magic; 41 | uint16_t page_id; 42 | uint8_t unk2; 43 | uint16_t progress; 44 | uint16_t unk3; 45 | }; 46 | 47 | static int receive_initial_data(struct data_channel *data_channel); 48 | static int process_header(struct data_channel *data_channel); 49 | 50 | int data_channel_set_paused(struct data_channel *data_channel) { 51 | event_thread_pause(data_channel->thread); 52 | sleep(1); 53 | return 0; 54 | } 55 | 56 | static struct scan_param * 57 | get_scan_param_by_index(struct data_channel *data_channel, uint8_t index) 58 | { 59 | if (index >= CONFIG_SCAN_MAX_PARAMS) { 60 | return NULL; 61 | } 62 | 63 | return data_channel->params + index; 64 | } 65 | 66 | static struct scan_param * 67 | get_scan_param_by_id(struct data_channel *data_channel, char id) 68 | { 69 | struct scan_param *ret; 70 | uint8_t i = 0; 71 | 72 | do { 73 | ret = get_scan_param_by_index(data_channel, i++); 74 | } while (ret != NULL && ret->id != id); 75 | 76 | return ret; 77 | } 78 | 79 | static int 80 | read_scan_params(struct data_channel *data_channel, uint8_t *buf, uint8_t *buf_end, 81 | const char *whitelist) 82 | { 83 | struct scan_param *param; 84 | char id; 85 | size_t i; 86 | 87 | while (buf < buf_end) { 88 | id = *buf++; 89 | if (*buf != '=') { 90 | LOG_ERR("Received invalid scan param (missing '=' sign).\n"); 91 | return -1; 92 | } 93 | ++buf; 94 | 95 | param = get_scan_param_by_id(data_channel, id); 96 | if (param == NULL) { 97 | LOG_ERR("Received invalid scan param (unknown id '%c').\n", id); 98 | return -1; 99 | } 100 | 101 | i = 0; 102 | while (*buf != 0x0a) { 103 | if (whitelist == NULL || strchr(whitelist, id) != NULL) { 104 | param->value[i++] = *buf; 105 | } 106 | 107 | if (i >= sizeof(param->value) - 1) { 108 | LOG_ERR("Received data_channel param longer than %zu bytes!\n", 109 | sizeof(param->value) - 1); 110 | return -1; 111 | } 112 | 113 | ++buf; 114 | } 115 | param->value[i] = 0; 116 | 117 | ++buf; 118 | } 119 | 120 | return 0; 121 | } 122 | 123 | static uint8_t * 124 | write_scan_params(struct data_channel *data_channel, uint8_t *buf, 125 | const char *whitelist) 126 | { 127 | struct scan_param *param; 128 | size_t len; 129 | uint8_t i = 0; 130 | 131 | while ((param = get_scan_param_by_index(data_channel, i++)) != NULL) { 132 | len = strlen(param->value); 133 | if (len == 0) { 134 | continue; 135 | } 136 | 137 | if (whitelist != NULL && strchr(whitelist, param->id) == NULL) { 138 | continue; 139 | } 140 | 141 | *buf++ = (uint8_t) param->id; 142 | *buf++ = '='; 143 | memcpy(buf, param->value, len); 144 | buf += len; 145 | *buf++ = 0x0a; 146 | } 147 | 148 | return buf; 149 | } 150 | 151 | static int invoke_callback(struct data_channel *data_channel, 152 | const char *filename) { 153 | char buf[1024]; 154 | int rc; 155 | char *script = data_channel->item->scan_command; 156 | const char *scan_func = g_scan_func_str[data_channel->item->scan_func]; 157 | if (!script) { 158 | LOG_WARN("No hook configured for %s. (Received %s)\n", scan_func, filename); 159 | return 0; 160 | } 161 | LOG_INFO("Running hook: %s\n", script); 162 | char *args[] = {"/bin/sh", "-c", script, NULL}; 163 | char *envp[11]; 164 | 165 | int env_idx = 0; 166 | #define SET_ENVP(attr, value) \ 167 | rc = snprintf(buf, sizeof(buf), attr, value); \ 168 | if (rc < 0 || rc == sizeof(*envp)) { \ 169 | LOG_ERR("couldn't write env. snprintf failed: %d", errno); \ 170 | return -1; \ 171 | } \ 172 | envp[env_idx++] = strdup(buf); 173 | 174 | SET_ENVP("SCANNER_XDPI=%d", data_channel->xdpi); 175 | SET_ENVP("SCANNER_YDPI=%d", data_channel->ydpi); 176 | SET_ENVP("SCANNER_HEIGHT=%d", data_channel->height); 177 | SET_ENVP("SCANNER_WIDTH=%d", data_channel->width); 178 | SET_ENVP("SCANNER_PAGE=%d", data_channel->page_data.id); 179 | SET_ENVP("SCANNER_IP=%s", data_channel->config->ip); 180 | SET_ENVP("SCANNER_SCANID=%d", data_channel->scan_id); 181 | SET_ENVP("SCANNER_HOSTNAME=%s", data_channel->item->hostname); 182 | SET_ENVP("SCANNER_FUNC=%s", scan_func); 183 | if (filename) { 184 | SET_ENVP("SCANNER_FILENAME=%s", filename); 185 | } 186 | envp[env_idx] = NULL; 187 | 188 | rc = fork(); 189 | if (rc == 0) { // child process: run the user script 190 | execve(args[0], args, envp); 191 | perror("execve"); // we only get here if execve fails. 192 | exit(1); 193 | } 194 | if (rc < 0) { 195 | perror("fork"); 196 | return -1; 197 | } 198 | // parent process 199 | if (filename) { 200 | LOG_DEBUG("Waiting for hook to finish: %d\n", rc); 201 | waitpid(rc, NULL, 0); // wait for child to finish 202 | LOG_DEBUG("Hook finished: %d\n", rc); 203 | } 204 | char **envp_p = envp; 205 | while (*envp_p != NULL) { 206 | free(*envp_p); 207 | envp_p++; 208 | } 209 | return 0; 210 | } 211 | 212 | static int process_page_end_header(struct data_channel *data_channel, 213 | struct data_packet_header *header) { 214 | FILE *destfile; 215 | char filename[64]; 216 | char buf[2048]; 217 | size_t size; 218 | 219 | if (header->page_id != data_channel->page_data.id) { 220 | LOG_ERR("%s: packet page_id mismatch (got %u, expected %u)\n", 221 | data_channel->config->ip, header->page_id, 222 | data_channel->scanned_pages + 1); 223 | return -1; 224 | } 225 | 226 | sprintf(filename, "scan%u.%s", data_channel->scanned_pages++, 227 | data_channel->file_format); 228 | destfile = fopen(filename, "w"); 229 | if (destfile == NULL) { 230 | LOG_ERR("Cannot create file '%s' on data_channel %s\n", filename, 231 | data_channel->config->ip); 232 | return -1; 233 | } 234 | 235 | fseek(data_channel->tempfile, 0, SEEK_SET); 236 | while ((size = fread(buf, 1, sizeof(buf), data_channel->tempfile))) { 237 | fwrite(buf, 1, size, destfile); 238 | } 239 | 240 | fclose(destfile); 241 | fclose(data_channel->tempfile); 242 | data_channel->tempfile = NULL; 243 | 244 | SET_CALLBACK(receive_initial_data); 245 | LOG_INFO("%s: successfully received page %u\n", data_channel->config->ip, 246 | header->page_id); 247 | 248 | return invoke_callback(data_channel, filename); 249 | } 250 | 251 | static void process_scan_end_header(struct data_channel *data_channel) { 252 | brother_conn_disconnect(data_channel->conn); 253 | SET_CALLBACK(data_channel_set_paused); 254 | invoke_callback(data_channel, NULL); 255 | } 256 | 257 | static int process_page_payload(struct data_channel *data_channel) { 258 | if (brother_conn_fill_buffer(data_channel->conn, 1, 259 | data_channel->config->timeout) < 0) { 260 | LOG_ERR("%s: Incomplete data from scanner\n", data_channel->config->ip); 261 | SET_CALLBACK(data_channel_set_paused); 262 | return -1; 263 | } 264 | size_t msg_len = MIN(data_channel->page_data.remaining_chunk_bytes, 265 | brother_conn_data_available(data_channel->conn)); 266 | char *buf = brother_conn_read(data_channel->conn, msg_len); 267 | if (buf == NULL) { 268 | LOG_ERR("%s: incomplete scan\n", data_channel->config->ip); 269 | return -1; 270 | } 271 | if (fwrite(buf, 1, msg_len, data_channel->tempfile) != msg_len) { 272 | LOG_ERR("%s: couldn’t write data to temporary file\n", 273 | data_channel->config->ip); 274 | return -1; 275 | } 276 | data_channel->page_data.remaining_chunk_bytes -= msg_len; 277 | if (data_channel->page_data.remaining_chunk_bytes == 0) { 278 | SET_CALLBACK(process_header); 279 | } 280 | return 0; 281 | } 282 | 283 | static int process_chunk_header(struct data_channel *data_channel, 284 | struct data_packet_header *header) { 285 | int progress_percent; 286 | 287 | uint8_t *buf = brother_conn_read(data_channel->conn, 2); 288 | if (!buf) { 289 | LOG_ERR("%s: couldn't read payload length\n", data_channel->config->ip); 290 | return -1; 291 | } 292 | 293 | if (header->page_id == data_channel->page_data.id + 1) { 294 | // The assumption is that pages start with id 1 and increment linearly. 295 | // If that's wrong we'll need to update receive scripts too. 296 | LOG_INFO("%s: now scanning page id %u\n", data_channel->config->ip, 297 | header->page_id); 298 | data_channel->page_data.id = header->page_id; 299 | } else if (header->page_id != data_channel->page_data.id) { 300 | LOG_ERR("%s: packet page_id mismatch (packet %u != local %u)\n", 301 | data_channel->config->ip, header->page_id, 302 | data_channel->page_data.id); 303 | return -1; 304 | } 305 | 306 | progress_percent = header->progress * 100 / DATA_CHANNEL_CHUNK_MAX_PROGRESS; 307 | LOG_DEBUG("%s: receiving data: %d%%\n", data_channel->config->ip, 308 | progress_percent); 309 | 310 | data_channel->page_data.remaining_chunk_bytes = buf[0] | (buf[1] << 8); 311 | LOG_DEBUG("remaining_chunk_bytes: %d\n", 312 | data_channel->page_data.remaining_chunk_bytes); 313 | SET_CALLBACK(process_page_payload); 314 | return 0; 315 | } 316 | 317 | static int process_header(struct data_channel *data_channel) { 318 | struct data_packet_header header; 319 | int rc; 320 | 321 | if (brother_conn_fill_buffer(data_channel->conn, 1, 322 | data_channel->item->page_finish_timeout) < 0) { 323 | LOG_ERR("%s: Incomplete data from scanner. Didn't receive chunk header", 324 | data_channel->config->ip); 325 | SET_CALLBACK(data_channel_set_paused); 326 | return -1; 327 | } 328 | uint8_t *buf = brother_conn_peek(data_channel->conn, 1); 329 | 330 | if (buf && *buf == 0x80) { // page end marker 331 | brother_conn_read(data_channel->conn, 1); 332 | process_scan_end_header(data_channel); 333 | return 1; 334 | } 335 | 336 | if (brother_conn_fill_buffer(data_channel->conn, 10, 337 | data_channel->item->page_finish_timeout) < 0) { 338 | LOG_ERR("%s: Incomplete data from scanner. Didn't receive chunk header\n", 339 | data_channel->config->ip); 340 | SET_CALLBACK(data_channel_set_paused); 341 | return -1; 342 | } 343 | buf = brother_conn_read(data_channel->conn, 10); 344 | header.id = buf[0]; 345 | header.magic = buf[1] | (buf[2] << 8); 346 | header.page_id = buf[3] | (buf[4] << 8); 347 | header.unk2 = buf[5]; 348 | header.progress = buf[6] | (buf[7] << 8); 349 | header.unk3 = buf[8] | (buf[9] << 8); 350 | 351 | if (header.magic != 0x07) { 352 | LOG_ERR("%s: invalid header magic number (%u != 0x07)\n", 353 | data_channel->config->ip, header.magic); 354 | DUMP_DEBUG(buf, 10); 355 | return -1; 356 | } 357 | 358 | switch (header.id) { 359 | case 0x40: // RAW 360 | data_channel->file_format = "raw"; 361 | break; 362 | case 0x42: // RLENGTH (packbits) 363 | data_channel->file_format = "rle"; 364 | break; 365 | case 0x64: 366 | data_channel->file_format = "jpeg"; 367 | break; 368 | default: 369 | break; 370 | } 371 | 372 | switch (header.id) { 373 | case 0x40: // RAW 374 | case 0x42: // RLENGTH 375 | case 0x64: 376 | if (brother_conn_fill_buffer(data_channel->conn, 2, 377 | data_channel->item->page_finish_timeout) < 378 | 0) { 379 | LOG_ERR("%s: Incomplete data from scanner. Didn't receive chunk size", 380 | data_channel->config->ip); 381 | SET_CALLBACK(data_channel_set_paused); 382 | return -1; 383 | } 384 | rc = process_chunk_header(data_channel, &header); 385 | break; 386 | case 0x82: 387 | rc = process_page_end_header(data_channel, &header); 388 | if (rc == 0) { 389 | rc = 1; 390 | } 391 | break; 392 | default: 393 | LOG_ERR("%s: received unsupported header (id = %u)\n", 394 | data_channel->config->ip, header.id); 395 | rc = -1; 396 | } 397 | 398 | return rc; 399 | } 400 | 401 | static int 402 | receive_initial_data(struct data_channel *data_channel) 403 | { 404 | int rc; 405 | 406 | if (brother_conn_data_available(data_channel->conn) == 0) { 407 | int is_first_page = data_channel->page_data.id == 0; 408 | rc = brother_conn_poll(data_channel->conn, 409 | is_first_page 410 | ? data_channel->item->page_init_timeout 411 | : data_channel->item->page_finish_timeout); 412 | if (rc <= 0) { 413 | /* a failed scan attempt */ 414 | LOG_WARN("Waited %ds for page, but didn't receive scan data.\n", 415 | data_channel->item->page_init_timeout); 416 | if (!is_first_page) { 417 | // assume the scan is finished. 418 | process_scan_end_header(data_channel); 419 | } 420 | return -1; 421 | } 422 | } 423 | data_channel->tempfile = tmpfile(); 424 | if (data_channel->tempfile == NULL) { 425 | LOG_ERR("Cannot create temp file on data_channel %s\n", 426 | data_channel->config->ip); 427 | return -1; 428 | } 429 | SET_CALLBACK(process_header); 430 | return 0; 431 | } 432 | 433 | static int 434 | exchange_params2(struct data_channel *data_channel) 435 | { 436 | struct scan_param *param; 437 | uint8_t buf[1024], *buf_end; 438 | long recv_params[7]; 439 | int msg_len, rc; 440 | size_t i; 441 | long tmp; 442 | 443 | rc = brother_conn_poll(data_channel->conn, 3); 444 | if (rc <= 0) { 445 | LOG_ERR("Couldn't receive scan params on data_channel %s\n", 446 | data_channel->config->ip); 447 | return -1; 448 | } 449 | 450 | msg_len = brother_conn_receive(data_channel->conn, buf, sizeof(buf) - 1); 451 | if (msg_len < 5) { 452 | LOG_ERR("Failed to receive scan params on data_channel %s\n", 453 | data_channel->config->ip); 454 | return -1; 455 | } 456 | 457 | /* process received data */ 458 | if (buf[0] != 0x00) { 459 | LOG_ERR( 460 | "%s: received invalid exchange params msg (invalid first byte " 461 | "'%c').\n", 462 | data_channel->config->ip, buf[0]); 463 | return -1; 464 | } 465 | 466 | if (buf[1] != msg_len - 3) { 467 | LOG_ERR("%s: invalid second exchange params msg (invalid length '%c').\n", 468 | data_channel->config->ip, buf[1]); 469 | return -1; 470 | } 471 | 472 | if (buf[2] != 0x00) { 473 | LOG_ERR( 474 | "%s: received invalid exchange params msg (invalid third byte " 475 | "'%c').\n", 476 | data_channel->config->ip, buf[2]); 477 | return -1; 478 | } 479 | // Make sure it's \0 terminated. 480 | buf[msg_len] = 0; 481 | 482 | i = 0; 483 | uint8_t *buf_p = buf + 3; 484 | buf_end = buf_p; 485 | 486 | while (i < sizeof(recv_params) / sizeof(recv_params[0])) { 487 | tmp = strtol((char *)buf_p, (char **)&buf_end, 10); 488 | if (buf_end == buf_p || (*buf_end != ',' && *buf_end != 0) || 489 | ((tmp == LONG_MIN || tmp == LONG_MAX) && errno == ERANGE)) { 490 | LOG_ERR("%s: received invalid exchange params msg (invalid params).\n", 491 | data_channel->config->ip); 492 | return -1; 493 | } 494 | 495 | recv_params[i++] = tmp; 496 | if (*buf_end) buf_end++; 497 | buf_p = buf_end; 498 | } 499 | data_channel->xdpi = recv_params[0]; 500 | data_channel->ydpi = recv_params[1]; 501 | data_channel->width = recv_params[4]; 502 | data_channel->height = recv_params[6]; 503 | 504 | if (*buf_p != 0x00) { 505 | LOG_ERR("%s: received invalid exchange params msg (message too long).\n", 506 | data_channel->config->ip); 507 | return -1; 508 | } 509 | 510 | param = get_scan_param_by_id(data_channel, 'R'); 511 | assert(param); 512 | 513 | /* previously sent and just received dpi should match */ 514 | sprintf((char *)buf, "%ld,%ld", recv_params[0], recv_params[1]); 515 | if (strncmp((char *)(buf), param->value, sizeof(param->value)) != 0) { 516 | LOG_INFO( 517 | "Scanner does not support requested dpi: %s." 518 | " %s will be used instead\n", 519 | param->value, (char *)(buf)); 520 | 521 | strncpy(param->value, (const char *)buf, sizeof(param->value)); 522 | param->value[sizeof(param->value) - 1] = 0; 523 | } 524 | 525 | param = get_scan_param_by_id(data_channel, 'A'); 526 | assert(param); 527 | sprintf(param->value, "0,0,%ld,%ld", recv_params[4], recv_params[6]); 528 | 529 | /* prepare a response */ 530 | buf_p = buf; 531 | *buf_p++ = 0x1b; // magic sequence 532 | *buf_p++ = 0x58; // packet id (?) 533 | *buf_p++ = 0x0a; // header end 534 | 535 | buf_p = write_scan_params(data_channel, buf_p, "RMCJBNADGL"); 536 | if (buf_p == NULL) { 537 | LOG_ERR("Failed to write scan params on data_channel %s\n", 538 | data_channel->config->ip); 539 | return -1; 540 | } 541 | 542 | *buf_p++ = 0x80; // end of message 543 | 544 | msg_len = brother_conn_send(data_channel->conn, buf, buf_p - buf); 545 | if (msg_len < 0 || msg_len != buf_p - buf) { 546 | LOG_ERR("Couldn't send scan params on data_channel %s\n", 547 | data_channel->config->ip); 548 | return -1; 549 | } 550 | 551 | // A new scan always starts from 0. 552 | data_channel->page_data.id = 0; 553 | data_channel->scan_id = atomic_fetch_add(&scan_id, 1); 554 | 555 | SET_CALLBACK(receive_initial_data); 556 | return 0; 557 | } 558 | 559 | static int data_channel_send_scan_params(struct data_channel *data_channel) { 560 | /* prepare a response */ 561 | uint8_t buffer[1024]; 562 | uint8_t *buf = buffer; 563 | *buf++ = 0x1b; // magic sequence 564 | *buf++ = 0x49; // packet id (?) 565 | *buf++ = 0x0a; // header end 566 | 567 | // TODO: double-check why "RMCJBNADGL" isn't good 568 | buf = write_scan_params(data_channel, buf, "RMD"); 569 | if (buf == NULL) { 570 | LOG_ERR("Failed to write initial scan params on data_channel %s\n", 571 | data_channel->config->ip); 572 | return -1; 573 | } 574 | 575 | *buf++ = 0x80; // end of message 576 | 577 | int msg_len = brother_conn_send(data_channel->conn, buffer, buf - buffer); 578 | if (msg_len < 0) { 579 | LOG_ERR("Couldn't send initial scan params on data_channel %s\n", 580 | data_channel->config->ip); 581 | return -1; 582 | } 583 | 584 | SET_CALLBACK(exchange_params2); 585 | return 0; 586 | } 587 | 588 | static int 589 | exchange_params1(struct data_channel *data_channel) 590 | { 591 | struct scan_param *param; 592 | uint8_t *buf, *buf_end; 593 | int msg_len; 594 | size_t str_len; 595 | int i, rc; 596 | uint8_t buffer[1024]; 597 | 598 | rc = brother_conn_poll(data_channel->conn, data_channel->config->timeout); 599 | if (rc <= 0) { 600 | LOG_ERR("%s: couldn't receive initial scan params\n", 601 | data_channel->config->ip); 602 | return -1; 603 | } 604 | 605 | msg_len = brother_conn_receive(data_channel->conn, buffer, sizeof(buffer)); 606 | 607 | if (buffer[0] == 0xD0 && msg_len == 1) { 608 | LOG_INFO("No params to parse from remote."); 609 | return data_channel_send_scan_params(data_channel); 610 | } 611 | if (msg_len < 5) { 612 | LOG_ERR("%s: failed to receive initial scan params\n", 613 | data_channel->config->ip); 614 | return -1; 615 | } 616 | 617 | /* process received data */ 618 | if (buffer[0] != 0x30) { 619 | LOG_ERR( 620 | "%s: received invalid initial exchange params msg" 621 | " (invalid first byte %02x).\n", 622 | data_channel->config->ip, buffer[0]); 623 | return -1; 624 | } 625 | //buf[1] == 0x15 or 0x55 (might refer to automatic/manual scan) 626 | //buf[2] == 0x30 or 0x00 ?? 627 | 628 | if (buffer[msg_len - 2] != 0x0a) { // end of param 629 | LOG_ERR( 630 | "%s: received invalid initial exchange params msg" 631 | " (invalid second-last byte '%c').\n", 632 | data_channel->config->ip, buffer[msg_len - 2]); 633 | return -1; 634 | } 635 | 636 | if (buffer[msg_len - 1] != 0x80) { // end of message 637 | LOG_ERR( 638 | "%s: received invalid initial exchange params msg" 639 | " (invalid last byte '%c').\n", 640 | data_channel->config->ip, buffer[msg_len - 1]); 641 | return -1; 642 | } 643 | 644 | buf = buffer + 3; 645 | buf_end = buffer + msg_len - 2; 646 | 647 | if (read_scan_params(data_channel, buf, buf_end, NULL) != 0) { 648 | LOG_ERR("%s: failed to process initial scan params\n", 649 | data_channel->config->ip); 650 | return -1; 651 | } 652 | 653 | param = get_scan_param_by_id(data_channel, 'R'); 654 | if (strchr(param->value, ',') == NULL) { 655 | str_len = strlen(param->value); 656 | if (str_len >= sizeof(param->value) - 1) { 657 | LOG_ERR("%s: received invalid resolution %s\n", 658 | data_channel->config->ip, param->value); 659 | return -1; 660 | } 661 | 662 | param->value[str_len] = ','; 663 | memcpy(¶m->value[str_len + 1], param->value, str_len); 664 | } 665 | 666 | param = get_scan_param_by_id(data_channel, 'F'); 667 | for (i = 0; i < CONFIG_SCAN_MAX_FUNCS; ++i) { 668 | if (strcmp(param->value, g_scan_func_str[i]) == 0) { 669 | break; 670 | } 671 | } 672 | 673 | if (i == CONFIG_SCAN_MAX_FUNCS) { 674 | LOG_ERR("%s: received invalid scan function %s.\n", 675 | data_channel->config->ip, param->value); 676 | return -1; 677 | } 678 | return data_channel_send_scan_params(data_channel); 679 | } 680 | 681 | int data_channel_init_connection(struct data_channel *data_channel) { 682 | int rc, msg_len; 683 | uint8_t buffer[1024]; 684 | 685 | if (brother_conn_reconnect(data_channel->conn, 686 | inet_addr(data_channel->config->ip), 687 | htons(DATA_CHANNEL_TARGET_PORT)) != 0) { 688 | LOG_ERR("Could not connect to scanner.\n"); 689 | return -1; 690 | } 691 | 692 | rc = brother_conn_poll(data_channel->conn, data_channel->config->timeout); 693 | if (rc <= 0) { 694 | LOG_ERR("Couldn't receive welcome message on data_channel %s\n", 695 | data_channel->config->ip); 696 | return -1; 697 | } 698 | 699 | msg_len = brother_conn_receive(data_channel->conn, buffer, sizeof(buffer)); 700 | if (msg_len < 1) { 701 | if (errno == ENOTCONN || errno == ECONNRESET) { 702 | LOG_WARN("Lost connection on data_channel %s\n", 703 | data_channel->config->ip); 704 | // Let's reconnect. 705 | return data_channel_init_connection(data_channel); 706 | } 707 | LOG_ERR("Failed to receive welcome message on data_channel %s\n", 708 | data_channel->config->ip); 709 | return -1; 710 | } 711 | 712 | // "-NG 401\r\n": Scanner not ready? 713 | 714 | if (buffer[0] != '+') { 715 | LOG_ERR("Received invalid welcome message on data_channel %s\n", 716 | data_channel->config->ip); 717 | return -1; 718 | } 719 | 720 | msg_len = brother_conn_send(data_channel->conn, "\x1b\x4b\x0a\x80", 4); 721 | 722 | if (msg_len < 0) { 723 | LOG_ERR("Couldn't send welcome message on data_channel %s\n", 724 | data_channel->config->ip); 725 | return -1; 726 | } 727 | 728 | SET_CALLBACK(exchange_params1); 729 | return 0; 730 | } 731 | 732 | void data_channel_loop(void *arg) { 733 | struct data_channel *data_channel = arg; 734 | int rc; 735 | 736 | rc = data_channel->process_cb(data_channel); 737 | if (rc < 0) { 738 | LOG_ERR("%s: failed to process data. The channel will be closed.\n", 739 | data_channel->config->ip); 740 | 741 | if (data_channel->tempfile) { 742 | fclose(data_channel->tempfile); 743 | data_channel->tempfile = NULL; 744 | } 745 | 746 | SET_CALLBACK(data_channel_set_paused); 747 | } 748 | } 749 | 750 | static void 751 | data_channel_stop(void *arg) 752 | { 753 | struct data_channel *data_channel = arg; 754 | 755 | if (data_channel->tempfile) { 756 | fclose(data_channel->tempfile); 757 | data_channel->tempfile = NULL; 758 | } 759 | 760 | brother_conn_close(data_channel->conn); 761 | free(data_channel); 762 | } 763 | 764 | int data_channel_init(struct data_channel *data_channel) { 765 | data_channel->thread = event_thread_self(); 766 | SET_CALLBACK(data_channel_set_paused); 767 | data_channel->file_format = "unk"; 768 | 769 | data_channel->conn = brother_conn_open(BROTHER_CONNECTION_TYPE_TCP, 770 | data_channel->config->timeout); 771 | if (data_channel->conn == NULL) { 772 | LOG_ERR("Failed to init a data_channel.\n"); 773 | event_thread_stop(data_channel->thread); 774 | return -1; 775 | } 776 | return 0; 777 | } 778 | 779 | void 780 | data_channel_kick_cb(void *arg1, void *arg2) 781 | { 782 | struct data_channel *data_channel = arg1; 783 | 784 | if (data_channel->process_cb != data_channel_set_paused) { 785 | LOG_ERR("Trying to kick non-sleeping data_channel %s.\n", 786 | data_channel->config->ip); 787 | return; 788 | } 789 | 790 | data_channel->process_cb = data_channel_init_connection; 791 | } 792 | 793 | void data_channel_set_item(struct data_channel *data_channel, 794 | const struct item_config *item) { 795 | data_channel->item = item; 796 | memcpy(data_channel->params, data_channel->item->scan_params, 797 | sizeof(data_channel->item->scan_params)); 798 | } 799 | 800 | void 801 | data_channel_kick(struct data_channel *data_channel) 802 | { 803 | struct event_thread *thread = data_channel->thread; 804 | int rc; 805 | 806 | rc = event_thread_enqueue_event(thread, data_channel_kick_cb, data_channel, NULL); 807 | if (rc != 0) { 808 | goto err; 809 | } 810 | 811 | rc = event_thread_kick(thread); 812 | if (rc != 0) { 813 | goto err; 814 | } 815 | 816 | return; 817 | 818 | err: 819 | LOG_ERR("Failed to kick data_channel %s.\n", 820 | data_channel->config->ip); 821 | } 822 | 823 | struct data_channel * 824 | data_channel_create(struct device_config *config) 825 | { 826 | struct data_channel *data_channel; 827 | struct event_thread *thread; 828 | 829 | data_channel = calloc(1, sizeof(*data_channel)); 830 | if (data_channel == NULL) { 831 | LOG_ERR("Failed to calloc data_channel.\n"); 832 | return NULL; 833 | } 834 | 835 | data_channel->config = config; 836 | SET_CALLBACK(data_channel_init); 837 | 838 | thread = event_thread_create("data_channel", data_channel_loop, 839 | data_channel_stop, data_channel); 840 | if (thread == NULL) { 841 | LOG_ERR("Failed to create data_channel thread.\n"); 842 | free(data_channel); 843 | return NULL; 844 | } 845 | 846 | return data_channel; 847 | } 848 | --------------------------------------------------------------------------------