Fixed smtp.lua loading.

Adjusted tftp module.
Added some comments.
master
Diego Nehab 2004-06-16 01:02:07 +00:00
parent 843a431ef9
commit d46f7a09a7
20 changed files with 162 additions and 139 deletions

76
TODO
View File

@ -1,52 +1,31 @@
ajeitar os README.* ajeitar os README.*
ajeitar select. upvalue nao tem nada a ver...
make sure filter.chain fails gracefully.
ajeitar o manual sobre select, mais liberal agora
falar sobre o novo esquema de namespace
tirar socket.url socket.ftp etc do manual. agora os namespaces estao
liberados.
ajeitar as referencias a RFCS e LTNS em todos os arquivos. ajeitar as referencias a RFCS e LTNS em todos os arquivos.
proxy no ftp
ajeitar < e-mail > no smtp?
ajeitar referencias a LTN12 nos manuais
make sure sockets are closed when exceptions are raised make sure sockets are closed when exceptions are raised
falar sobre encodet/wrapt/decodet no manual sobre mime
RECEIVE MUDOU!!! (partial stuff) COLOCAR NO MANUAL.
HTTP.lua mudou bastante também.
pump.step usado em todo mundo que recebe source ou sink
sources ans sinks are always simple in http and ftp and smtp
expose encode/decode tables to provide extensibility for mime module
use coroutines instead of fancy filters
check garbage collection in test*.lua check garbage collection in test*.lua
add socket.TIMEOUT to be default timeout?
manual manual
compatibility: select sets are associative * compatibility: select sets are associative
add socket.connect and socket.bind to the manual * add socket.connect and socket.bind to the manual
say what a nil callback does for http * add shutdown
* add gethostname
check all occurences of it's check all occurences of it's
- add shutdown * the need of a content-length header in the post method...
- add gethostname * notice the change in callback conventions
the need of a content-length header in the post method... * the callback.lua module and the new mime module.
notice the change in callback conventions * escape and unescape in url, not in code!
the callback.lua module and the new mime module. * add timeout and proxy to request table
escape and unescape in url, not in code! * change stay to redirect
add timeout and proxy to request table * socket.time and socket.sleep
change stay to redirect * - connect with timeout
socket.time and socket.sleep
- connect with timeout
local connect local connect
add thanks to 'carlos cassino' and 'david burgess' * add thanks to 'carlos cassino' and 'david burgess'
add new ip- options and reuseaddr option * add new ip- options and reuseaddr option
- add listen to manual * - add listen to manual
bind method doesn't do listen anymore * bind method doesn't do listen anymore
bind doesn't turn an object into a server object: listen does. * bind doesn't turn an object into a server object: listen does.
tests tests
checar todos os metodos checar todos os metodos
@ -59,9 +38,6 @@ wrp can't break lines in the middle of a line break.
add comments into each C module. add comments into each C module.
testar os options! testar os options!
use gethostname it in SMTP
make sure modules know if their dependencies are there.
Read about Read about
250-ENHANCEDSTATUSCODES 250-ENHANCEDSTATUSCODES
@ -108,6 +84,12 @@ Ajeitar o protocolo da luaopen_socket()... sei l
these are done these are done
-------------- --------------
* tirar socket.url socket.ftp etc do manual. agora os namespaces
estao liberados.
* falar sobre o novo esquema de namespace
* ajeitar o manual sobre select, mais liberal agora
* make sure filter.chain fails gracefully.
* ajeitar select. upvalue nao tem nada a ver...
* should be interrupt-safe * should be interrupt-safe
* notice the change in callback conventions * notice the change in callback conventions
* new mime module replacing old code module (faster, more functionality) * new mime module replacing old code module (faster, more functionality)
@ -161,3 +143,15 @@ these are done
* change all modules to use the new namespace scheme * change all modules to use the new namespace scheme
* write some utilities that use the code.lua module and put them * write some utilities that use the code.lua module and put them
* in etc, modify the README.etc file and makefile.dist (eol.lua is done) * in etc, modify the README.etc file and makefile.dist (eol.lua is done)
* proxy no ftp? no
* ajeitar < e-mail > no smtp? no
* ajeitar referencias a LTN12 nos manuais
* RECEIVE MUDOU!!! (partial stuff) COLOCAR NO MANUAL.
* HTTP.lua mudou bastante também.
* falar sobre encodet/wrapt/decodet no manual sobre mime? no.
* pump.step usado em todo mundo que recebe source ou sink
* sources ans sinks are always simple in http and ftp and smtp
* expose encode/decode tables to provide extensibility for mime module
* use coroutines instead of fancy filters
* add socket.TIMEOUT to be default timeout? no.
* use gethostname it in SMTP

View File

@ -2,13 +2,20 @@
-- TFTP support for the Lua language -- TFTP support for the Lua language
-- LuaSocket toolkit. -- LuaSocket toolkit.
-- Author: Diego Nehab -- Author: Diego Nehab
-- Conforming to: RFC 783, LTN7
-- RCS ID: $Id$ -- RCS ID: $Id$
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local Public, Private = {}, {} -----------------------------------------------------------------------------
local socket = _G[LUASOCKET_LIBNAME] -- get LuaSocket namespace -- Load required files
socket.tftp = Public -- create tftp sub namespace -----------------------------------------------------------------------------
local socket = require("socket")
local ltn12 = require("ltn12")
local url = require("url")
-----------------------------------------------------------------------------
-- Setup namespace
-----------------------------------------------------------------------------
_LOADED["tftp"] = getfenv(1)
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Program constants -- Program constants
@ -16,33 +23,33 @@ socket.tftp = Public -- create tftp sub namespace
local char = string.char local char = string.char
local byte = string.byte local byte = string.byte
Public.PORT = 69 PORT = 69
Private.OP_RRQ = 1 local OP_RRQ = 1
Private.OP_WRQ = 2 local OP_WRQ = 2
Private.OP_DATA = 3 local OP_DATA = 3
Private.OP_ACK = 4 local OP_ACK = 4
Private.OP_ERROR = 5 local OP_ERROR = 5
Private.OP_INV = {"RRQ", "WRQ", "DATA", "ACK", "ERROR"} local OP_INV = {"RRQ", "WRQ", "DATA", "ACK", "ERROR"}
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Packet creation functions -- Packet creation functions
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
function Private.RRQ(source, mode) local function RRQ(source, mode)
return char(0, Private.OP_RRQ) .. source .. char(0) .. mode .. char(0) return char(0, OP_RRQ) .. source .. char(0) .. mode .. char(0)
end end
function Private.WRQ(source, mode) local function WRQ(source, mode)
return char(0, Private.OP_RRQ) .. source .. char(0) .. mode .. char(0) return char(0, OP_RRQ) .. source .. char(0) .. mode .. char(0)
end end
function Private.ACK(block) local function ACK(block)
local low, high local low, high
low = math.mod(block, 256) low = math.mod(block, 256)
high = (block - low)/256 high = (block - low)/256
return char(0, Private.OP_ACK, high, low) return char(0, OP_ACK, high, low)
end end
function Private.get_OP(dgram) local function get_OP(dgram)
local op = byte(dgram, 1)*256 + byte(dgram, 2) local op = byte(dgram, 1)*256 + byte(dgram, 2)
return op return op
end end
@ -50,13 +57,13 @@ end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Packet analysis functions -- Packet analysis functions
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
function Private.split_DATA(dgram) local function split_DATA(dgram)
local block = byte(dgram, 3)*256 + byte(dgram, 4) local block = byte(dgram, 3)*256 + byte(dgram, 4)
local data = string.sub(dgram, 5) local data = string.sub(dgram, 5)
return block, data return block, data
end end
function Private.get_ERROR(dgram) local function get_ERROR(dgram)
local code = byte(dgram, 3)*256 + byte(dgram, 4) local code = byte(dgram, 3)*256 + byte(dgram, 4)
local msg local msg
_,_, msg = string.find(dgram, "(.*)\000", 5) _,_, msg = string.find(dgram, "(.*)\000", 5)
@ -64,68 +71,81 @@ function Private.get_ERROR(dgram)
end end
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Downloads and returns a file pointed to by url -- High level TFTP API
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
function Public.get(url) local function tget(gett)
local parsed = socket.url.parse(url, {
host = "",
port = Public.PORT,
path ="/",
scheme = "tftp"
})
if parsed.scheme ~= "tftp" then
return nil, string.format("unknown scheme '%s'", parsed.scheme)
end
local retries, dgram, sent, datahost, dataport, code local retries, dgram, sent, datahost, dataport, code
local cat = socket.concat.create()
local last = 0 local last = 0
local udp, err = socket.udp() local con = socket.try(socket.udp())
if not udp then return nil, err end
-- convert from name to ip if needed -- convert from name to ip if needed
parsed.host = socket.dns.toip(parsed.host) gett.host = socket.try(socket.dns.toip(gett.host))
udp:settimeout(1) con:settimeout(1)
-- first packet gives data host/port to be used for data transfers -- first packet gives data host/port to be used for data transfers
retries = 0 retries = 0
repeat repeat
sent, err = udp:sendto(Private.RRQ(parsed.path, "octet"), sent = socket.try(con:sendto(RRQ(gett.path, "octet"),
parsed.host, parsed.port) gett.host, gett.port))
if err then return nil, err end dgram, datahost, dataport = con:receivefrom()
dgram, datahost, dataport = udp:receivefrom()
retries = retries + 1 retries = retries + 1
until dgram or datahost ~= "timeout" or retries > 5 until dgram or datahost ~= "timeout" or retries > 5
if not dgram then return nil, datahost end socket.try(dgram, datahost)
-- associate socket with data host/port -- associate socket with data host/port
udp:setpeername(datahost, dataport) socket.try(con:setpeername(datahost, dataport))
-- default sink
local sink = gett.sink or ltn12.sink.null()
-- process all data packets -- process all data packets
while 1 do while 1 do
-- decode packet -- decode packet
code = Private.get_OP(dgram) code = get_OP(dgram)
if code == Private.OP_ERROR then socket.try(code ~= OP_ERROR, get_ERROR(dgram))
return nil, Private.get_ERROR(dgram) socket.try(code == OP_DATA, "unhandled opcode " .. code)
end
if code ~= Private.OP_DATA then
return nil, "unhandled opcode " .. code
end
-- get data packet parts -- get data packet parts
local block, data = Private.split_DATA(dgram) local block, data = split_DATA(dgram)
-- if not repeated, write -- if not repeated, write
if block == last+1 then if block == last+1 then
cat:addstring(data) socket.try(sink(data))
last = block last = block
end end
-- last packet brings less than 512 bytes of data -- last packet brings less than 512 bytes of data
if string.len(data) < 512 then if string.len(data) < 512 then
sent, err = udp:send(Private.ACK(block)) socket.try(con:send(ACK(block)))
return cat:getresult() socket.try(con:close())
socket.try(sink(nil))
return 1
end end
-- get the next packet -- get the next packet
retries = 0 retries = 0
repeat repeat
sent, err = udp:send(Private.ACK(last)) sent = socket.try(con:send(ACK(last)))
if err then return err end dgram, err = con:receive()
dgram, err = udp:receive()
retries = retries + 1 retries = retries + 1
until dgram or err ~= "timeout" or retries > 5 until dgram or err ~= "timeout" or retries > 5
if not dgram then return err end socket.try(dgram, err)
end end
end end
local default = {
port = PORT,
path ="/",
scheme = "tftp"
}
local function parse(u)
local t = socket.try(url.parse(u, default))
socket.try(t.scheme == "tftp", "invalid scheme '" .. t.scheme .. "'")
socket.try(t.host, "invalid host")
return t
end
local function sget(u)
local gett = parse(u)
local t = {}
gett.sink = ltn12.sink.table(t)
tget(gett)
return table.concat(t)
end
get = socket.protect(function(gett)
if type(gett) == "string" then return sget(gett)
else return tget(gett) end
end)

View File

@ -32,7 +32,7 @@ end
local host = socket.dns.gethostname() local host = socket.dns.gethostname()
local query = "%s?cmd=cddb+read+%s+%s&hello=LuaSocket+%s+LuaSocket+2.0&proto=6" local query = "%s?cmd=cddb+read+%s+%s&hello=LuaSocket+%s+LuaSocket+2.0&proto=6"
local url = string.format(query, server, arg[1], arg[2], host) local url = string.format(query, server, arg[1], arg[2], host)
local body, headers, code, error = http.get(url) local body, headers, code = http.get(url)
if code == 200 then if code == 200 then
local data, code, error = parse(body) local data, code, error = parse(body)

View File

@ -1,12 +1,22 @@
/*=========================================================================*\
* Simple exception support
* LuaSocket toolkit
*
* RCS ID: $Id$
\*=========================================================================*/
#include <lauxlib.h> #include <lauxlib.h>
#include <stdio.h> #include <stdio.h>
#include "except.h" #include "except.h"
/*=========================================================================*\
* Internal function prototypes.
\*=========================================================================*/
static int global_try(lua_State *L); static int global_try(lua_State *L);
static int global_protect(lua_State *L); static int global_protect(lua_State *L);
static int protected(lua_State *L); static int protected(lua_State *L);
/* except functions */
static luaL_reg func[] = { static luaL_reg func[] = {
{"try", global_try}, {"try", global_try},
{"protect", global_protect}, {"protect", global_protect},

View File

@ -2,7 +2,6 @@
-- FTP support for the Lua language -- FTP support for the Lua language
-- LuaSocket toolkit. -- LuaSocket toolkit.
-- Author: Diego Nehab -- Author: Diego Nehab
-- Conforming to: RFC 959, LTN7
-- RCS ID: $Id$ -- RCS ID: $Id$
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
@ -244,5 +243,3 @@ get = socket.protect(function(gett)
if type(gett) == "string" then return sget(gett) if type(gett) == "string" then return sget(gett)
else return tget(gett) end else return tget(gett) end
end) end)
return ftp

View File

@ -2,7 +2,6 @@
-- HTTP/1.1 client support for the Lua language. -- HTTP/1.1 client support for the Lua language.
-- LuaSocket toolkit. -- LuaSocket toolkit.
-- Author: Diego Nehab -- Author: Diego Nehab
-- Conforming to RFC 2616
-- RCS ID: $Id$ -- RCS ID: $Id$
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
@ -23,7 +22,7 @@ _LOADED["http"] = getfenv(1)
-- Program constants -- Program constants
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- connection timeout in seconds -- connection timeout in seconds
TIMEOUT = 4 TIMEOUT = 60
-- default port for document retrieval -- default port for document retrieval
PORT = 80 PORT = 80
-- user agent field sent in request -- user agent field sent in request
@ -146,7 +145,7 @@ end
local function adjustrequest(reqt) local function adjustrequest(reqt)
-- parse url with default fields -- parse url with default fields
local parsed = url.parse(reqt.url, { local parsed = url.parse(reqt.url or "", {
host = "", host = "",
port = PORT, port = PORT,
path ="/", path ="/",
@ -258,5 +257,3 @@ post = socket.protect(function(u, body)
return (table.getn(t) > 0 or nil) and table.concat(t), return (table.getn(t) > 0 or nil) and table.concat(t),
respt.headers, respt.code respt.headers, respt.code
end) end)
return http

View File

@ -254,5 +254,3 @@ function pump.all(src, snk, step)
if not ret then return not err, err end if not ret then return not err, err end
end end
end end
return ltn12

View File

@ -1,5 +1,5 @@
/*=========================================================================*\ /*=========================================================================*\
* Encoding support functions * MIME support functions
* LuaSocket toolkit * LuaSocket toolkit
* *
* RCS ID: $Id$ * RCS ID: $Id$
@ -653,7 +653,7 @@ static int mime_global_eol(lua_State *L)
const char *marker = luaL_optstring(L, 3, CRLF); const char *marker = luaL_optstring(L, 3, CRLF);
luaL_Buffer buffer; luaL_Buffer buffer;
luaL_buffinit(L, &buffer); luaL_buffinit(L, &buffer);
/* if the last character was a candidate, we output a new line */ /* end of input blackhole */
if (!input) { if (!input) {
lua_pushnil(L); lua_pushnil(L);
lua_pushnumber(L, 0); lua_pushnumber(L, 0);

View File

@ -1,7 +1,7 @@
#ifndef MIME_H #ifndef MIME_H
#define MIME_H #define MIME_H
/*=========================================================================*\ /*=========================================================================*\
* Mime support functions * MIME support functions
* LuaSocket toolkit * LuaSocket toolkit
* *
* This module provides functions to implement transfer content encodings * This module provides functions to implement transfer content encodings

View File

@ -73,5 +73,3 @@ wrap = choose(wrapt)
function normalize(marker) function normalize(marker)
return ltn12.filter.cycle(eol, 0, marker) return ltn12.filter.cycle(eol, 0, marker)
end end
return mime

View File

@ -1,3 +1,9 @@
/*=========================================================================*\
* Common option interface
* LuaSocket toolkit
*
* RCS ID: $Id$
\*=========================================================================*/
#include <lauxlib.h> #include <lauxlib.h>
#include <string.h> #include <string.h>
@ -5,6 +11,10 @@
#include "options.h" #include "options.h"
#include "inet.h" #include "inet.h"
/*=========================================================================*\
* Internal functions prototypes
\*=========================================================================*/
static int opt_setmembership(lua_State *L, p_sock ps, int level, int name); static int opt_setmembership(lua_State *L, p_sock ps, int level, int name);
static int opt_setboolean(lua_State *L, p_sock ps, int level, int name); static int opt_setboolean(lua_State *L, p_sock ps, int level, int name);
static int opt_set(lua_State *L, p_sock ps, int level, int name, static int opt_set(lua_State *L, p_sock ps, int level, int name,

View File

@ -1,5 +1,14 @@
#ifndef OPTIONS_H #ifndef OPTIONS_H
#define OPTIONS_H #define OPTIONS_H
/*=========================================================================*\
* Common option interface
* LuaSocket toolkit
*
* This module provides a common interface to socket options, used mainly by
* modules UDP and TCP.
*
* RCS ID: $Id$
\*=========================================================================*/
#include <lua.h> #include <lua.h>
#include "socket.h" #include "socket.h"

View File

@ -4,13 +4,10 @@
* Select implementation * Select implementation
* LuaSocket toolkit * LuaSocket toolkit
* *
* To make the code as simple as possible, the select function is * Each object that can be passed to the select function has to export
* implemented int Lua, with a few helper functions written in C. * method getfd() which returns the descriptor to be passed to the
* * underlying select function. Another method, dirty(), should return
* Each object that can be passed to the select function has to export two * true if there is data ready for reading (required for buffered input).
* methods: fd() and dirty(). Fd returns the descriptor to be passed to the
* select function. Dirty() should return true if there is data ready for
* reading (required for buffered input).
* *
* RCS ID: $Id$ * RCS ID: $Id$
\*=========================================================================*/ \*=========================================================================*/

View File

@ -2,14 +2,13 @@
-- SMTP client support for the Lua language. -- SMTP client support for the Lua language.
-- LuaSocket toolkit. -- LuaSocket toolkit.
-- Author: Diego Nehab -- Author: Diego Nehab
-- Conforming to RFC 2821
-- RCS ID: $Id$ -- RCS ID: $Id$
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
-- Load required modules -- Load required modules
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
local smtp = requirelib("smtp") local smtp = requirelib("smtp", "luaopen_smtp", getfenv(1))
local socket = require("socket") local socket = require("socket")
local ltn12 = require("ltn12") local ltn12 = require("ltn12")
local tp = require("tp") local tp = require("tp")
@ -206,6 +205,7 @@ end
--------------------------------------------------------------------------- ---------------------------------------------------------------------------
-- High level SMTP API -- High level SMTP API
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
socket.protect = function(a) return a end
send = socket.protect(function(mailt) send = socket.protect(function(mailt)
local con = open(mailt.server, mailt.port) local con = open(mailt.server, mailt.port)
con:greet(mailt.domain) con:greet(mailt.domain)
@ -213,5 +213,3 @@ send = socket.protect(function(mailt)
con:quit() con:quit()
return con:close() return con:close()
end) end)
return smtp

View File

@ -166,5 +166,3 @@ end
socket.sourcet["default"] = socket.sourcet["until-closed"] socket.sourcet["default"] = socket.sourcet["until-closed"]
socket.source = socket.choose(socket.sourcet) socket.source = socket.choose(socket.sourcet)
return socket

View File

@ -49,7 +49,6 @@ local metat = { __index = {} }
function metat.__index:check(ok) function metat.__index:check(ok)
local code, reply = get_reply(self.control) local code, reply = get_reply(self.control)
print(reply)
if not code then return nil, reply end if not code then return nil, reply end
if type(ok) ~= "function" then if type(ok) ~= "function" then
if type(ok) == "table" then if type(ok) == "table" then
@ -65,7 +64,6 @@ print(reply)
end end
function metat.__index:command(cmd, arg) function metat.__index:command(cmd, arg)
print(cmd, arg)
if arg then return self.control:send(cmd .. " " .. arg.. "\r\n") if arg then return self.control:send(cmd .. " " .. arg.. "\r\n")
else return self.control:send(cmd .. "\r\n") end else return self.control:send(cmd .. "\r\n") end
end end
@ -113,5 +111,3 @@ connect = socket.protect(function(host, port, timeout)
socket.try(control:connect(host, port)) socket.try(control:connect(host, port))
return setmetatable({control = control}, metat) return setmetatable({control = control}, metat)
end) end)
return tp

View File

@ -2,7 +2,6 @@
-- URI parsing, composition and relative URL resolution -- URI parsing, composition and relative URL resolution
-- LuaSocket toolkit. -- LuaSocket toolkit.
-- Author: Diego Nehab -- Author: Diego Nehab
-- Conforming to: RFC 2396, LTN7
-- RCS ID: $Id$ -- RCS ID: $Id$
----------------------------------------------------------------------------- -----------------------------------------------------------------------------
@ -269,5 +268,3 @@ function build_path(parsed, unsafe)
if parsed.is_absolute then path = "/" .. path end if parsed.is_absolute then path = "/" .. path end
return path return path
end end
return url

View File

@ -1,11 +1,13 @@
smtp = require("smtp")
function test_dot(original, right) function test_dot(original, right)
local result, n = socket.smtp.dot(2, original) local result, n = smtp.dot(2, original)
assert(result == right, "->" .. result .. "<-") assert(result == right, "->" .. result .. "<-")
print("ok") print("ok")
end end
function test_stuff(original, right) function test_stuff(original, right)
local result, n = socket.smtp.dot(2, original) local result, n = smtp.dot(2, original)
assert(result == right, "->" .. result .. "<-") assert(result == right, "->" .. result .. "<-")
print("ok") print("ok")
end end

View File

@ -14,7 +14,7 @@ source = smtp.message{
}, },
body = { body = {
preamble = "If your client doesn't understand attachments, \r\n" .. preamble = "If your client doesn't understand attachments, \r\n" ..
"it will still display the preamble and the epilogue.\r\n", "it will still display the preamble and the epilogue.\r\n" ..
"Preamble might show up even in a MIME enabled client.", "Preamble might show up even in a MIME enabled client.",
-- first part: No headers means plain text, us-ascii. -- first part: No headers means plain text, us-ascii.
-- The mime.eol low-level filter normalizes end-of-line markers. -- The mime.eol low-level filter normalizes end-of-line markers.
@ -55,3 +55,5 @@ r, e = smtp.send{
source = source, source = source,
server = "mail.cs.princeton.edu" server = "mail.cs.princeton.edu"
} }
print(r, e)

View File

@ -1,5 +1,5 @@
-- load tftpclnt.lua -- load tftpclnt.lua
dofile("tftp.lua") local tftp = require("tftp")
-- needs tftp server running on localhost, with root pointing to -- needs tftp server running on localhost, with root pointing to
-- a directory with index.html in it -- a directory with index.html in it
@ -13,7 +13,7 @@ function readfile(file)
end end
host = host or "localhost" host = host or "localhost"
retrieved, err = socket.tftp.get("tftp://" .. host .."/index.html") retrieved, err = tftp.get("tftp://" .. host .."/index.html")
assert(not err, err) assert(not err, err)
original = readfile("test/index.html") original = readfile("test/index.html")
assert(original == retrieved, "files differ!") assert(original == retrieved, "files differ!")