{"id":15451504,"url":"https://github.com/davidar/mfc7400c","last_synced_at":"2025-09-19T20:30:36.832Z","repository":{"id":979237,"uuid":"780132","full_name":"davidar/mfc7400c","owner":"davidar","description":"Implementation of the Brother MFC-7400C scanning protocol","archived":false,"fork":false,"pushed_at":"2016-01-30T09:50:25.000Z","size":2323,"stargazers_count":8,"open_issues_count":0,"forks_count":3,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-09-04T05:53:11.839Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/davidar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2010-07-17T06:01:02.000Z","updated_at":"2024-01-29T07:30:08.000Z","dependencies_parsed_at":"2022-08-16T11:40:41.568Z","dependency_job_id":null,"html_url":"https://github.com/davidar/mfc7400c","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/davidar/mfc7400c","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidar%2Fmfc7400c","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidar%2Fmfc7400c/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidar%2Fmfc7400c/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidar%2Fmfc7400c/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/davidar","download_url":"https://codeload.github.com/davidar/mfc7400c/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/davidar%2Fmfc7400c/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":275997870,"owners_count":25567381,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","status":"online","status_checked_at":"2025-09-19T02:00:09.700Z","response_time":108,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-10-01T21:27:10.177Z","updated_at":"2025-09-19T20:30:36.763Z","avatar_url":"https://github.com/davidar.png","language":"C","funding_links":[],"categories":[],"sub_categories":[],"readme":"*Update: also see this more recent [Linux Voice article](http://www.linuxvoice.com/drive-it-yourself-usb-car-6/)*\n\nI had an old Brother MFC-7400C, which didn't work as a printer or fax machine anymore, but the document feeder made it useful for scanning multi-page documents. Unfortunately, it didn't seem to be supported under Linux, so I figured I'd try writing my own implementation by reverse engineering the scanning protocol.\n\n[SniffUSB][1] was used for analysing the USB traffic. By obtaining logs whilst various operations are performed with the scanner, it is possible to determine the characteristics of the protocol relatively easily.\n\n### Reading the SniffUSB Logs\n\n![SniffUSB screenshot](https://raw.githubusercontent.com/davidar/mfc7400c/master/usb-logs/screenshot.png)\n\nAfter installing the filter, logs were obtained for device initialisation, scanning with default settings with 0, 1, and 2 pages in the document feeder, as well as scanning with various other setting configurations with no pages in the feeder. After this, it is a matter of extracting the relevant information (the [libusb Documentation][2] and the [USB 2.0 Specification][3], particularly sections 9.3 and 9.6, come in handy here). For example, in the following snippet of a control transfer:\n\n```\n[136533 ms]  \u003e\u003e\u003e  URB 277 going down  \u003e\u003e\u003e \n-- URB_FUNCTION_VENDOR_DEVICE:\n  TransferFlags          = 00000003 (USBD_TRANSFER_DIRECTION_IN, USBD_SHORT_TRANSFER_OK)\n  TransferBufferLength = 000000ff\n  Request                 = 00000001\n  Value                   = 00000002\n  Index                   = 00000000\n[136535 ms]  \u003c\u003c\u003c  URB 277 coming back  \u003c\u003c\u003c \n-- URB_FUNCTION_CONTROL_TRANSFER:\n  TransferBufferMDL    = 81d6bc88\n    00000000: 05 10 01 02 00\n```\n\nThe following request characteristics can be extracted:\n\n - Data transfer direction = Device-to-host (IN)\n - Type = Vendor\n - Recipient = Device\n - Request = 0x01\n - Value = 0x02\n - Index = 0x00\n - Length = 0xff\n - Device responds 0x05 0x10 0x01 0x02 0x00\n\nWith libusb, this can be performed via the following call:\n\n```c\nNUM_BYTES_READ = libusb_control_transfer(DEVICE_HANDLE,\n        LIBUSB_ENDPOINT_IN |\n        LIBUSB_REQUEST_TYPE_VENDOR |\n        LIBUSB_RECIPIENT_DEVICE,\n        0x01, 0x02, 0x00, BUFFER, 0xff, TIMEOUT);\n/* BUFFER = { 0x05, 0x10, 0x01, 0x02, 0x00 } */\n```\n\nSimilarly, the following outbound bulk transfer to endpoint number 3, with equivalent C code:\n\n```\n[692026 ms]  \u003e\u003e\u003e  URB 3034 going down  \u003e\u003e\u003e \n-- URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER:\n  PipeHandle           = 81d0d24c [endpoint 0x00000003]\n  TransferFlags        = 00000002 (USBD_TRANSFER_DIRECTION_OUT, USBD_SHORT_TRANSFER_OK)\n  TransferBufferLength = 00000004\n  TransferBuffer       = 00000000\n  TransferBufferMDL    = 81e773b8\n    00000000: 1b 58 0a 80\n```\n\n```c\nBUFFER = { 0x1b, 0x58, 0x0a, 0x80 };\nlibusb_bulk_transfer(DEVICE_HANDLE, LIBUSB_ENDPOINT_OUT | 0x03,\n        BUFFER, 0x04, \u0026NUM_BYTES_SENT, TIMEOUT);\n```\n\nAnd an inbound bulk transfer on endpoint number 4:\n\n```\n[137544 ms]  \u003e\u003e\u003e  URB 284 going down  \u003e\u003e\u003e \n-- URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER:\n  PipeHandle           = 81d0d26c [endpoint 0x00000084]\n  TransferFlags        = 00000003 (USBD_TRANSFER_DIRECTION_IN, USBD_SHORT_TRANSFER_OK)\n  TransferBufferLength = 00001000\n[137545 ms]  \u003c\u003c\u003c  URB 284 coming back  \u003c\u003c\u003c \n-- URB_FUNCTION_BULK_OR_INTERRUPT_TRANSFER:\n  TransferBufferLength = 00000002\n  TransferBufferMDL    = 81cf9dd0\n    00000000: c2 00\n```\n\n```c\nlibusb_bulk_transfer(DEVICE_HANDLE, LIBUSB_ENDPOINT_IN | 0x04,\n        BUFFER, 0x1000, \u0026NUM_BYTES_READ, TIMEOUT);\n/* BUFFER = { 0xc2, 0x00 } */\n```\n\nFrom this, the following protocol characteristics can be determined:\n\n 1. While idling a control transfer is sent every 500ms (type=3,value=0,index=0,length=255), to which the device responds 0x04100300\n 2. Scanning is initiated with control transfer (type=1,value=2,index=0,length=255), device responds 0x0510010200\n 3. Configuration data sent to scanner with outbound bulk transfer (endpoint=3)\n 4. Image data is read from the scanner with inbound bulk transfer (endpoint=4,length=0x1000)\n 5. Scanning is ended with control transfer (type=2,value=2,index=0,length=255), device responds 0x0510020200\n\n### Configuration Data\n\nThe configuration data is an ASCII key=value string, which can be decoded with a few lines of Python:\n\n```python\n# set dump to hexdump of configuration data\nout = ''\nfor line in dump.strip().split('\\n'):\n    line = line[line.find(': ')+2:].strip()\n    for byte in line.split():\n        out = out + chr(int(byte, 16))\nprint repr(out)\n```\n\nCorrelating various configurations with their respective configuration strings yields the following pattern: the string always begins with \"\\x1bX\\n\", followed by 7 key=value pairs each terminated by \"\\n\", followed by \"\\x80\". The following key=value combinations are available:\n\n - R=X_RESOLUTION,Y_RESOLUTION (where both resolutions must be a multiple of 100, with the x- and y-resolutions capped at 300 and 600 respectively)\n - M=CGRAY (color mode), M=GRAY64 (grey-scale mode), M=TEXT (text mode)\n - C=RLENGTH (run-length encoding?), C=NONE (uncompressed)\n - B=100\n - N=100\n - U=OFF\n - A=0,0,WIDTH,HEIGHT\n\n### Raw Image Data\n\nInbound bulk transfers usually contain a payload of raw image data, but the following message can also be received:\n\n - empty payload: wait 200ms and try again\n - 0xc200: there is nothing to scan\n - 0x80: finished scanning the page, no more pages\n - 0x81: finished scanning the page, another page ready (in which case an empty configuration string is sent (\"\\x1bX\\n\\x80\"), and the next page is scanned)\n\nThe uncompressed raw image data is relatively easy to decode. The following snippet shows a color scan of an 816 (0x330) pixel wide plain white image. \n\n```\n00000000: 44 30 03 fd fd fd fd fd fd fd fd fd fd fd fd fd\n00000010: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd\n...\n00000320: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd\n00000330: fd fd fd 48 30 03 fd fd fd fd fd fd fd fd fd fd\n00000340: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd\n...\n00000650: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd\n00000660: fd fd fd fd fd fd 4c 30 03 fc fc fc fc fc fc fc\n00000670: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc\n...\n00000980: fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc fc\n00000990: fc fc fc fc fc fc fc fc fc 44 30 03 fd fd fd fd\n000009a0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd\n...\n00000cb0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd\n00000cc0: fd fd fd fd fd fd fd fd fd fd fd fd 48 30 03 fd\n00000cd0: fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd fd\n...\n```\n\nIt can be seen that the data consists of a series of 816-byte rows each prefixed with a 3 byte header. The first byte of the header describes the type of the row (0x40=gray, 0x44=red, 0x48=green, 0x4c=blue), and the following two bytes describe the length of the row in little-endian format.\n\n [1]: http://pcausa.com/Utilities/UsbSnoop/\n [2]: http://libusb.sourceforge.net/api-1.0/\n [3]: http://www.usb.org/developers/docs/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidar%2Fmfc7400c","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdavidar%2Fmfc7400c","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdavidar%2Fmfc7400c/lists"}