Simple NDEF Exchange Protocol¶
The NFC Forum Simple NDEF Exchange Protocol (SNEP) allows two NFC devices to exchange NDEF Messages. It is implemented in many smartphones and typically used to push phonebook contacts or web page URLs to another phone.
SNEP is a stateless request/response protocol. The client sends a request to the server, the server processes that request and returns a response. On the protocol level both the request and response have no consequences for further request/response exchanges. Information units transmitted through SNEP are NDEF messages. The client may use a SNEP PUT request to send an NDEF message and a SNEP GET request to retrieve an NDEF message. The message to retrieve with a GET request depends on an NDEF message sent with the GET request but the rules to determine equivalence are an application layer contract and not specified by SNEP.
NDEF messages can easily be larger than the maximum information unit
(MIU) supported by the LLCP data link connection that a SNEP client
establishes with a SNEP server. The SNEP layer handles fragmentation
and reassembly so that an application must not be concerned. To avoid
exhaustion of the limited NFC bandwidth if an NDEF message would
exceed the SNEP receiver’s capabilities, the receiver must acknowledge
the first fragment of an NDEF message that can not be transmitted in a
single MIU. The acknowledge can be either the request/response codes
CONTINUE or REJECT. If CONTINUE is received, the SNEP sender shall
transmit all further fragments without further acknowledgement (the
LLCP data link connection guarantees successful transmission). If
REJECT isreceived, the SNEP sender shall abort
tranmsission. Fragmentation and reassembly are handled transparently
by the nfc.snep.SnepClient
and nfc.snep.SnepServer
implementation and only a REJECT would be visible to the user.
A SNEP server may return other response codes depending on the result of a request:
- A SUCCESS response indicates that the request has succeeded. For a GET request the response will include an NDEF message. For a PUT request the response is empty.
- A NOT FOUND response says that the server has not found anything matching the request. This may be a temporary of permanent situation, i.e. the same request send later could yield a different response.
- An EXCESS DATA response may be received if the server has found a matching response but sending it would exhaust the SNEP client’s receive capabilities.
- A BAD REQUEST response indicates that the server detected a syntax error in the client’s request. This should almost never be seen.
- The NOT IMPLEMENTED response will be returned if the client sent a request that the server has not implemented. It applies to existing as well as yet undefined (future) request codes. The client can learn the difference from the version field transmitted withnthe response, but in reality it doesn’t matter - it’s just not supported.
- With UNSUPPORTED VERSION the server reacts to a SNEP version number sent with the request that it doesn’t support or refuses to support. This should be seen only if the client sends with a higher major version number than the server has implemented. It could be received also if the client sends with a lower major version number but SNEP servers are likely to support historic major versions if that ever happens (the current SNEP version is 1.0).
Besides the protocol layer the SNEP specification also defines a
Default SNEP Server with the well-known LLCP service access point
address 4 and service name urn:nfc:sn:snep
. Certified NFC Forum
Devices must have the Default SNEP Server implemented. Due to that
requirement the feature set and guarantees of the Default SNEP
Server are quite limited - it only implements the PUT request and the
NDEF message to put could be rejected if it is more than 1024 octets,
though smartphones generally seem to support more.
Default Server¶
A basic Default SNEP Server can be built with nfcpy like in the following example, where all error and exception handling has been sacrified for brevity.
import nfc
import nfc.snep
class DefaultSnepServer(nfc.snep.SnepServer):
def __init__(self, llc):
nfc.snep.SnepServer.__init__(self, llc, "urn:nfc:sn:snep")
def put(self, ndef_message):
print "client has put an NDEF message"
print ndef_message.pretty()
return nfc.snep.Success
def startup(llc):
global my_snep_server
my_snep_server = DefaultSnepServer(llc)
return llc
def connected(llc):
my_snep_server.start()
return True
my_snep_server = None
clf = nfc.ContactlessFrontend("usb")
clf.connect(llcp={'on-startup': startup, 'on-connect': connected})
This server will accept PUT requests with NDEF messages up to 1024
octets and return NOT IMPLEMENTED for any GET request. To increase the
size of NDEF messages that can be received, the
max_ndef_message_recv_size parameter can be passed to the
nfc.snep.SnepServer
class.
class DefaultSnepServer(nfc.snep.SnepServer):
def __init__(self, llc):
nfc.snep.SnepServer.__init__(self, llc, "urn:nfc:sn:snep", 10*1024)
Using SNEP Put¶
Sending an NDEF message to the Default SNEP Server is easily done
with an instance of nfc.snep.SnepClient
and is basically to
call nfc.snep.SnepClient.put()
with the message to send. The
example below shows how the function to send the NDEF message is
started as a separate thread - it cannot be directly called in
connected()
because the main thread context is used to run the
LLCP link.
import nfc
import nfc.snep
import threading
def send_ndef_message(llc):
sp = nfc.ndef.SmartPosterRecord('http://nfcpy.org', title='nfcpy home')
snep = nfc.snep.SnepClient(llc)
snep.put( nfc.ndef.Message(sp) )
def connected(llc):
threading.Thread(target=send_ndef_message, args=(llc,)).start()
return True
clf = nfc.ContactlessFrontend("usb")
clf.connect(llcp={'on-connect': connected})
Some phones require that a SNEP be present even if they are not going
to send anything (Windows Phone 8 is such example). The solution is to
also run a SNEP server on urn:nfc:sn:snep
which may just do
nothing.
import nfc
import nfc.snep
import threading
server = None
def send_ndef_message(llc):
sp = nfc.ndef.SmartPosterRecord('http://nfcpy.org', title='nfcpy home')
snep = nfc.snep.SnepClient(llc)
snep.put( nfc.ndef.Message(sp) )
def startup(clf, llc):
global server
server = nfc.snep.SnepServer(llc, "urn:nfc:sn:snep")
return llc
def connected(llc):
server.start()
threading.Thread(target=send_ndef_message, args=(llc,)).start()
return True
clf = nfc.ContactlessFrontend("usb")
clf.connect(llcp={'on-startup': startup, 'on-connect': connected})
Private Servers¶
The SNEP protocol can be used for other, non-standard, communication between a server and client component. A private server can be run on a dynamically assigned service access point if a private service name is used. A private server may also implement the GET request if it defines what a GET shall mean other than to return something. Below is an example of a private SNEP server that implements bot PUT and GET with the simple contract that whatever is put to the server will be returned for a GET request that requests the same or empty NDEF type and name values (for anything else the answer is NOT FOUND).
import nfc
import nfc.snep
class PrivateSnepServer(nfc.snep.SnepServer):
def __init__(self, llc):
self.ndef_message = nfc.ndef.Message(nfc.ndef.Record())
service_name = "urn:nfc:xsn:nfcpy.org:x-snep"
nfc.snep.SnepServer.__init__(self, llc, service_name, 2048)
def put(self, ndef_message):
print "client has put an NDEF message"
self.ndef_message = ndef_message
return nfc.snep.Success
def get(self, acceptable_length, ndef_message):
print "client requests an NDEF message"
if ((ndef_message.type == '' and ndef_message.name == '') or
((ndef_message.type == self.ndef_message.type) and
(ndef_message.name == self.ndef_message.name))):
if len(str(self.ndef_message)) > acceptable_length:
return nfc.snep.ExcessData
return self.ndef_message
return nfc.snep.NotFound
def startup(clf, llc):
global my_snep_server
my_snep_server = PrivateSnepServer(llc)
return llc
def connected(llc):
my_snep_server.start()
return True
my_snep_server = None
clf = nfc.ContactlessFrontend("usb")
clf.connect(llcp={'on-startup': startup, 'on-connect': connected})
A client application knowing the private server above may then use PUT
and GET to set an NDEF message on the server and retrieve it back. The
example code below also shows how results other than SUCCESS must be
catched in try-except clauses. Note that max_ndef_msg_recv_size
parameter is a policy sent to the SNEP server with every GET
request. It is a arbitrary restriction of the
nfc.snep.SnepClient
that this parameter can only be set when
the object is created; the SNEP protocol would allow it to be
different for every GET request but unless there’s demand for such
flexibility that won’t change.
import nfc
import nfc.snep
import threading
def send_ndef_message(llc):
sp = nfc.ndef.SmartPosterRecord('http://nfcpy.org', title='nfcpy home')
snep = nfc.snep.SnepClient(llc, max_ndef_msg_recv_size=2048)
snep.connect("urn:nfc:xsn:nfcpy.org:x-snep")
snep.put( nfc.ndef.Message(sp) )
print "*** get whatever the server has ***"
print snep.get().pretty()
print "*** get a smart poster with no name ***"
r = nfc.ndef.Record(record_type="urn:nfc:wkt:Sp", record_name="")
print snep.get( nfc.ndef.Message(r) ).pretty()
print "*** get something that isn't there ***"
r = nfc.ndef.Record(record_type="urn:nfc:wkt:Uri")
try:
snep.get( nfc.ndef.Message(r) )
except nfc.snep.SnepError as error:
print repr(error)
def connected(llc):
threading.Thread(target=send_ndef_message, args=(llc,)).start()
return True
clf = nfc.ContactlessFrontend("usb")
clf.connect(llcp={'on-connect': connected})