|
ProtoCore v0.0.1
Deterministic, zero-heap network stack for embedded targets
|
This sketch makes your ESP32 log in to a file server the way a Windows PC does (SMB2 + NTLMv2), open a file on a shared folder, and print it to the serial monitor. The motivating use case is a CNC machine controller: the shop keeps its .nc part programs on a file server, and the device pulls the one it needs.
You do not need to know anything about Windows networking. Part 1 walks you, from nothing, through turning a spare Raspberry Pi (or any Linux box) into a file server that serves one folder.
"SMB" (Server Message Block, also called CIFS) is the protocol Windows uses for "shared folders" - the \\server\folder paths you see in File Explorer. macOS and Linux speak it too (Linux via Samba). It runs over TCP port 445.
To read a file, a client walks a fixed sequence:
\\server\programs).smb_open() does steps 1-4 for you and returns an SmbHandle; smb_read() / smb_write() do step 5; smb_close() does step 6.
Security note. NTLM relies on the MD4 and MD5 hashes, which are old and cryptographically broken. They are included here only because the NTLM handshake requires them on the wire - never use MD4/MD5 for anything new. SMB is fine on a trusted LAN; do not expose port 445 to the internet.
On the Pi (or any Debian/Ubuntu machine):
Make a folder to share and put a test file in it:
Add the share to Samba's config. Append this to /etc/samba/smb.conf:
Create a Samba user (this is a separate password from the Linux login). Use an existing Linux account name, e.g. cnc - create the Linux user first if needed with sudo adduser cnc:
Find the Pi's IP address with hostname -I, then confirm the share works from the Pi itself:
You should see PART001.NC listed.
Open SmbFileClient.ino and edit the lines marked CHANGE ME:
| Line | Set it to |
|---|---|
SSID | your WiFi network name |
PASSWORD | your WiFi password |
SMB_HOST | the server's IP address (e.g. 192.168.1.50) |
SMB_USER | the Samba username (cnc above) |
SMB_PASS | the Samba password you set with smbpasswd |
SMB_DOMAIN | empty "" for a local Samba account; a domain name for AD |
SMB_SHARE | the UNC path, e.g. \\192.168.1.50\programs (double backslashes) |
SMB_PATH | the file to read, e.g. PART001.NC |
Flash the sketch and open Serial Monitor @ 115200. You should see:
The sketch prints SmbResult codes; here is what each means.
| Code | Name | Likely cause |
|---|---|---|
-1 | SMB_ERR_ARG | a SMB_* field is empty - check SMB_USER / SMB_SHARE / SMB_PATH |
-2 | SMB_ERR_IO | can't reach the server, or it closed the connection (firewall? port 445 open? sudo systemctl status smbd) |
-3 | SMB_ERR_PROTOCOL | share not found (SMB_SHARE name), or file not found (SMB_PATH), or access denied |
-4 | SMB_ERR_AUTH | wrong username / password / domain - re-run smbpasswd -a |
-5 | SMB_ERR_OVERFLOW | a message didn't fit PC_SMB_BUF; raise it (see below) for very large paths |
"connect failed" (before any SmbResult) means the TCP connection never opened - the server is unreachable on port 445. From another machine, test with smbclient //SERVER_IP/programs -U cnc -c ls.
smb_read() fills your buffer up to its capacity; loop it with a growing offset until it returns 0 bytes to stream a whole file.desired_access = SMB2_FILE_GENERIC_WRITE and disposition = SMB2_FILE_OVERWRITE_IF, then call smb_write(&h, 0, data, len, &wrote, cl_send, cl_recv, &x).PC_SMB_BUF bytes; raise PC_SMB_BUF (default 1024) in protocore_config.h for bigger transfers, at the cost of stack.SMB lives inside the library, so the flag must reach the whole build:
(The Arduino IDE reads the flag from build_opt.h beside the sketch automatically.)
smb_client is deliberately transport-agnostic: it moves bytes only through a send/recv seam - two function pointers you supply. That is why the whole protocol is unit-tested on a PC against a scripted mock server, with no real network. To run it on a real device you provide the glue that connects the seam to a socket. In this sketch that glue is cl_send / cl_recv, which sit on top of pc_client, the library's shared outbound TCP transport:
cl_send writes all the bytes with pc_client_send.cl_recv polls pc_client_read until data arrives, the peer closes, or a deadline passes (SMB's messages are length-prefixed, and the engine reads exactly one message at a time).Everything else - the Direct-TCP length framing, building the NTLMv2 response, wrapping the tokens in SPNEGO, threading the session / tree / file identifiers - happens inside smb_open / smb_read / smb_close. Point the seam at a TLS session, a serial link, or a test harness instead, and the same engine runs unchanged.