Documentation
¶
Index ¶
- Constants
- Variables
- func ConvertNDEFInput(data *protocol.NDEFMessageInput) (*nfc.NDEFMessage, error)
- func ConvertNDEFRecordInput(data protocol.NDEFRecordInput) (*nfc.NDEFRecord, error)
- func ConvertTagData(data TagData) (nfc.Tag, error)
- func IsDeviceConnection(r *http.Request) bool
- func NegotiateDeviceVersion(declared int) int
- func ParseUID(uid string) (string, error)
- func VersionFromSubprotocol(sub string) int
- type ActiveTagInfo
- type Device
- func (d *Device) AppVersion() string
- func (d *Device) Close() error
- func (d *Device) Connection() string
- func (d *Device) DeclaredCapabilities() (DeviceCapabilities, bool)
- func (d *Device) DeviceID() string
- func (d *Device) DeviceType() string
- func (d *Device) GetTags() ([]nfc.Tag, error)
- func (d *Device) IsActive() bool
- func (d *Device) IsHealthy() error
- func (d *Device) LastSeen() time.Time
- func (d *Device) Metadata() map[string]string
- func (d *Device) PhoneCapabilities() DeviceCapabilities
- func (d *Device) Platform() string
- func (d *Device) ProtocolVersion() int
- func (d *Device) String() string
- func (d *Device) SupportedTagTypes() []string
- func (d *Device) SupportsEvents() bool
- func (d *Device) SupportsTransceive() bool
- func (d *Device) Transceive(txData []byte) ([]byte, error)
- func (d *Device) UpdateLastSeen()
- type DeviceCapabilities
- type DeviceHeartbeat
- type DeviceRegistrationRequest
- type DeviceRegistrationResponse
- type DeviceTagData
- type DeviceTagRemovedData
- type DeviceTransceiveRequest
- type DeviceTransceiveResponse
- type DeviceWriteRequest
- type DeviceWriteResponse
- type DisconnectReason
- type GoodbyeRequest
- type HelloRequest
- type HelloResponse
- type Manager
- func (m *Manager) ActiveTag(deviceID string) (ActiveTagInfo, bool)
- func (m *Manager) ActiveTagDevices() []string
- func (m *Manager) Close()
- func (m *Manager) DeviceChanges() <-chan struct{}
- func (m *Manager) DeviceMaxHoldMs(deviceID string) int
- func (m *Manager) Devices() ([]nfc.DeviceListing, error)
- func (m *Manager) DevicesHoldingTags() []string
- func (m *Manager) DisconnectDevice(deviceID, reason string) bool
- func (m *Manager) Dropped() uint64
- func (m *Manager) GetActiveDeviceCount() int
- func (m *Manager) GetDevice(deviceID string) (*Device, bool)
- func (m *Manager) GetDeviceCount() int
- func (m *Manager) Handler(opts ServerOptions) http.Handler
- func (m *Manager) LockTag(ctx context.Context, deviceID, tagUID, idempotencyKey string) (*nfc.LockResult, error)
- func (m *Manager) OpenDevice(deviceStr string) (nfc.Device, error)
- func (m *Manager) RegisterDevice(req DeviceRegistrationRequest) (*Device, error)
- func (m *Manager) Scans() *event.Signal[nfc.ScannedTag]
- func (m *Manager) SendTagData(deviceID string, tagData TagData) error
- func (m *Manager) SendTagRemoved(deviceID string, data TagRemovedData) error
- func (m *Manager) SendToDevice(deviceID string, message any) error
- func (m *Manager) TagCapabilities(_ context.Context, deviceID, tagUID string) (*nfc.TagCapabilities, error)
- func (m *Manager) TagOn(deviceID string) (string, string, bool)
- func (m *Manager) TransceiveTag(ctx context.Context, deviceID, tagUID string, data []byte, raw bool) ([]byte, error)
- func (m *Manager) TransceiveWithDevice(ctx context.Context, deviceID string, req DeviceTransceiveRequest) (DeviceTransceiveResponse, error)
- func (m *Manager) UnregisterDevice(deviceID string) error
- func (m *Manager) UpdateHeartbeat(deviceID string) error
- func (m *Manager) WriteTag(ctx context.Context, deviceID, tagUID string, msg *nfc.NDEFMessage, lock bool, ...) (*nfc.WriteResult, error)
- func (m *Manager) WriteToDevice(ctx context.Context, deviceID string, req DeviceWriteRequest) (DeviceWriteResponse, error)
- type ServerInfo
- type ServerOptions
- type Tag
- func (t *Tag) CanMakeReadOnly() (bool, error)
- func (t *Tag) Capabilities() nfc.TagCapabilities
- func (t *Tag) GetNDEFMessage() (*nfc.NDEFMessage, error)
- func (t *Tag) IsWritable() (bool, error)
- func (t *Tag) MakeReadOnly() error
- func (t *Tag) NumericType() int
- func (t *Tag) ReadData() ([]byte, error)
- func (t *Tag) ScannedAt() time.Time
- func (t *Tag) SourceDevice() string
- func (t *Tag) Transceive(data []byte) ([]byte, error)
- func (t *Tag) Type() string
- func (t *Tag) UID() string
- func (t *Tag) WriteData(data []byte) error
- func (t *Tag) WriteDataAndLock(data []byte) error
- type TagData
- type TagRemovedData
Constants ¶
const ( HeartbeatInterval = 30 * time.Second // Expected heartbeat frequency DeviceTimeout = 90 * time.Second // Silence after which a device is dropped CleanupInterval = 15 * time.Second // How often silence is checked GetTagsTimeout = 500 * time.Millisecond // GetTags blocking timeout )
Device timing constants.
DeviceTimeout allows three missed heartbeats at HeartbeatInterval. They were previously both 30s, so a device that only heartbeats sat exactly on the sweep boundary and could be dropped mid-interval.
const ( ScanQueueDepth = 256 ScanPublishTimeout = 2 * time.Second )
Scan publishing.
ScanQueueDepth buffers scans and removals between the sessions reporting them and the goroutine broadcasting them. It absorbs a burst, not a subscriber that is permanently behind.
ScanPublishTimeout is how long a session waits for room before giving up. Waiting blocks only the session whose scan it is, so it can be this long.
const ( DeviceProtocolV0 = 0 DeviceProtocolV1 = 1 DeviceProtocolMax = DeviceProtocolV1 )
Device bridge protocol versions. Version 0 is the original dialect (registerDevice/registerDeviceResponse) spoken by every device shipped before versioning existed; version 1 adds the hello handshake.
const ( WSTypeHello = "hello" WSTypeHelloResponse = "helloResponse" WSTypeGoodbye = "goodbye" WSTypeRegisterDevice = "registerDevice" WSTypeRegisterDeviceResponse = "registerDeviceResponse" WSTypeTagScanned = "tagScanned" WSTypeTagRemoved = "tagRemoved" WSTypeDeviceHeartbeat = "deviceHeartbeat" WSTypeDeviceWriteRequest = "deviceWriteRequest" WSTypeDeviceWriteResponse = "deviceWriteResponse" WSTypeDeviceTransceiveRequest = "deviceTransceiveRequest" WSTypeDeviceTransceiveResponse = "deviceTransceiveResponse" )
WebSocket message types of the device protocol. The client protocol keeps its own in package protocol; these are only ever spoken to a device.
const DeviceTransceiveTimeout = 5 * time.Second
DeviceTransceiveTimeout bounds a single raw exchange. It is far shorter than a write because a transceive is one round trip, and a sequence of them has to fit inside the time a user holds a tag against the device.
const DeviceWriteTimeout = 20 * time.Second
DeviceWriteTimeout bounds how long the agent waits for a write outcome. A tag is only in the field while the user holds it there, so waiting much longer reports a stale result.
const MaxDeviceMessageSize = 256 << 10
MaxDeviceMessageSize caps an inbound frame on the device endpoint. A device that exceeds it loses its session.
The largest legitimate frame is a write carrying an NDEF message twice over: the parsed records plus the base64 ndefBytes of the same message. A Type 4 tag's NDEF file runs to tens of kilobytes, so the limit is set well above that. It bounds the allocation a peer can provoke; the reader enforces the tag's own capacity.
const SubprotocolDeviceV1 = "davi-nfc-device.v1"
SubprotocolDeviceV1 is offered by a v1 device in Sec-WebSocket-Protocol.
Variables ¶
var DeviceSubprotocols = []string{SubprotocolDeviceV1}
DeviceSubprotocols lists what the device endpoint accepts, in server preference order. A device offering none is still served, as version 0.
Functions ¶
func ConvertNDEFInput ¶ added in v1.2.0
func ConvertNDEFInput(data *protocol.NDEFMessageInput) (*nfc.NDEFMessage, error)
ConvertNDEFInput converts protocol NDEF format to internal nfc.NDEFMessage.
func ConvertNDEFRecordInput ¶ added in v1.2.0
func ConvertNDEFRecordInput(data protocol.NDEFRecordInput) (*nfc.NDEFRecord, error)
ConvertNDEFRecordInput converts protocol NDEF record to internal nfc.NDEFRecord.
func ConvertTagData ¶
ConvertTagData converts a device's tag report to an nfc.Tag. The result is read-only, having no route back to the device that scanned it; a tag the manager builds gets one.
func IsDeviceConnection ¶ added in v1.2.0
IsDeviceConnection reports whether a request is a device asking to connect rather than a client.
func NegotiateDeviceVersion ¶ added in v1.2.0
NegotiateDeviceVersion resolves the version to speak with a device that declared `declared` in its hello payload. Sending hello at all implies v1, so a device declaring less is raised rather than rejected.
func ParseUID ¶ added in v1.2.0
ParseUID normalizes a UID from various formats to colon-separated uppercase hex. Supports: "04:AB:CD:EF", "04ABCDEF", "04 AB CD EF", "04-AB-CD-EF" Returns: normalized colon-separated uppercase hex (e.g., "04:AB:CD:EF")
func VersionFromSubprotocol ¶ added in v1.2.0
VersionFromSubprotocol maps a negotiated token to a version. Empty or unrecognized means version 0.
Types ¶
type ActiveTagInfo ¶ added in v1.2.0
type ActiveTagInfo struct {
DeviceID string
UID string
// Tag answers for the tag's capabilities. Nil if the scan carried none.
Tag nfc.Tag
}
ActiveTagInfo describes a tag a device is holding.
type Device ¶
type Device struct {
// contains filtered or unexported fields
}
Device implements the nfc.Device interface for smartphone NFC scanning.
func NewDevice ¶
func NewDevice(deviceID string, req DeviceRegistrationRequest) *Device
NewDevice creates a new smartphone device instance.
func (*Device) AppVersion ¶
AppVersion returns the mobile app version.
func (*Device) Connection ¶
Connection returns the device connection string.
func (*Device) DeclaredCapabilities ¶ added in v1.2.0
func (d *Device) DeclaredCapabilities() (DeviceCapabilities, bool)
DeclaredCapabilities returns what the device said it can do, and whether it said anything at all.
The second result is the whole point: an omitted block and a block of falses are different claims, and only one of them is a refusal.
func (*Device) DeviceType ¶
DeviceType returns the device type identifier (implements nfc.DeviceInfoProvider).
func (*Device) GetTags ¶
GetTags satisfies nfc.Device and never returns a tag.
A phone is not polled the way a reader is: it pushes its scans, and those reach the agent through Manager.Data. What a device is holding right now is a separate question, answered by the registry of held tags on the manager.
func (*Device) IsHealthy ¶
IsHealthy checks if the device connection is healthy (implements nfc.DeviceHealthChecker).
func (*Device) PhoneCapabilities ¶
func (d *Device) PhoneCapabilities() DeviceCapabilities
PhoneCapabilities returns what the device declared, or the zero value if it declared nothing. Callers that must tell those apart use DeclaredCapabilities.
func (*Device) ProtocolVersion ¶ added in v1.1.0
ProtocolVersion returns the bridge protocol version negotiated at registration.
func (*Device) SupportedTagTypes ¶
SupportedTagTypes returns the NFC types this device supports (implements nfc.DeviceInfoProvider). A v0 device declares only its radio technology, which is all we can report.
func (*Device) SupportsEvents ¶
SupportsEvents returns true as smartphones emit tag events (implements nfc.DeviceEventEmitter).
func (*Device) SupportsTransceive ¶ added in v1.1.0
SupportsTransceive reports device-level transceive, which remains unsupported (implements nfc.DeviceTransceiver).
A device may well declare CanTransceive, but that capability is exercised against a specific tag and is reported by remotenfc.Tag. Device-level transceive has no tag to address, so declaring it here would promise something Transceive below cannot do.
func (*Device) Transceive ¶
Transceive is not directly applicable for smartphones. Raw exchange with a scanned tag goes through remotenfc.Tag.Transceive.
func (*Device) UpdateLastSeen ¶
func (d *Device) UpdateLastSeen()
UpdateLastSeen updates the device's last activity timestamp.
type DeviceCapabilities ¶
type DeviceCapabilities struct {
CanRead bool `json:"canRead"`
CanWrite bool `json:"canWrite"`
NFCType string `json:"nfcType"` // "nfca", "nfcb", "nfcf", "nfcv", "isodep", etc.
// CanTransceive is APDU-level exchange (Android IsoDep.transceive, iOS
// sendCommand, PN532 InDataExchange). CanTransceiveRaw is framing-level
// exchange (Android NfcA.transceive, PN532 InCommunicateThru), a strictly
// rarer capability, which is why it is a separate bit.
CanTransceive bool `json:"canTransceive,omitempty"`
CanTransceiveRaw bool `json:"canTransceiveRaw,omitempty"`
CanLock bool `json:"canLock,omitempty"`
SupportedTagTypes []string `json:"supportedTagTypes,omitempty"` // e.g. ["MIFARE Classic", "NTAG"]
DeviceType string `json:"deviceType,omitempty"` // e.g. "smartphone", "pn532-serial"
MaxBaudRate int `json:"maxBaudRate,omitempty"`
// MaxHoldMs is how long this device can keep a tag available for work after
// reporting it, zero meaning open-ended. A reader holding a tag in its field
// has no bound; an iOS device does, because CoreNFC connects a tag for about
// twenty seconds and cannot renew that.
//
// The deadline for a given tag is the arrival of its tagScanned plus this,
// and that sum is optimistic, since the tag was connected before the message
// was sent, so leave margin rather than treating it as exact.
//
// Advisory. A device declaring nothing is open-ended, which is how every
// device behaved before this field existed, so it must not become a gate:
// use it to decide what is worth attempting, never to refuse a device that
// stayed silent.
MaxHoldMs int `json:"maxHoldMs,omitempty"`
}
DeviceCapabilities defines the capabilities of a connected NFC device.
The first three fields are the original v0 declaration. Everything below is additive: a v0 device omits them and reads as all-false, which is what it could actually do anyway.
type DeviceHeartbeat ¶
type DeviceHeartbeat struct {
DeviceID string `json:"deviceID"`
Timestamp time.Time `json:"timestamp"`
}
DeviceHeartbeat is sent by a device periodically.
type DeviceRegistrationRequest ¶
type DeviceRegistrationRequest struct {
DeviceName string `json:"deviceName"` // e.g., "John's iPhone 12"
Platform string `json:"platform"` // "ios" or "android"
AppVersion string `json:"appVersion"` // e.g., "1.0.0"
ProtocolVersion int `json:"protocolVersion"` // Negotiated bridge protocol version
// Capabilities is what the device says it can do. A pointer so that saying
// nothing is distinguishable from declaring every field false: the two
// arrived alike while this was a value, and silence was read as refusal.
// Mirrors DeviceTagData.Capabilities, which has answered this way since a
// tag that declared nothing stopped meaning a tag that can do nothing.
Capabilities *DeviceCapabilities `json:"capabilities,omitempty"`
Metadata map[string]string `json:"metadata"` // Optional metadata
}
DeviceRegistrationRequest is sent by mobile app to register as an NFC device.
type DeviceRegistrationResponse ¶
type DeviceRegistrationResponse struct {
DeviceID string `json:"deviceID"` // Unique device identifier (UUID)
SessionToken string `json:"sessionToken"` // Authentication token (optional future use)
ServerInfo ServerInfo `json:"serverInfo"`
}
DeviceRegistrationResponse is sent by server after successful registration.
type DeviceTagData ¶ added in v1.2.0
type DeviceTagData struct {
DeviceID string `json:"deviceID"` // Device that scanned the tag
UID string `json:"uid"` // Tag UID (hex format)
Technology string `json:"technology"` // "ISO14443A", "ISO14443B", etc.
Type string `json:"type"` // "MIFARE Classic 1K", "Type4", etc.
ATR string `json:"atr"` // Answer to Reset (if applicable)
ScannedAt time.Time `json:"scannedAt"` // Timestamp of scan
NDEFMessage *protocol.NDEFMessageInput `json:"ndefMessage"` // Parsed NDEF data (if available)
RawData []byte `json:"rawData"` // Raw tag data (base64 encoded)
// Capabilities is what the device determined about this specific tag. When
// omitted the agent infers them from Type, which is all a v0 device allows.
Capabilities *protocol.TagCapabilities `json:"capabilities,omitempty"`
}
DeviceTagData is sent by a device when a tag is scanned.
type DeviceTagRemovedData ¶ added in v1.2.0
type DeviceTagRemovedData struct {
DeviceID string `json:"deviceID"`
UID string `json:"uid"` // UID of the removed tag
RemovedAt time.Time `json:"removedAt"` // Timestamp of removal
}
DeviceTagRemovedData is sent by a device when a tag leaves the NFC field.
type DeviceTransceiveRequest ¶ added in v1.2.0
type DeviceTransceiveRequest struct {
RequestID string `json:"requestID"`
DeviceID string `json:"deviceID"`
TagUID string `json:"tagUID,omitempty"` // Report TAG_REMOVED if a different tag is present
Data []byte `json:"data"` // Command bytes, base64 in transit
// Raw selects framing-level exchange (Android NfcA.transceive, PN532
// InCommunicateThru) over APDU-level (IsoDep.transceive, InDataExchange).
Raw bool `json:"raw,omitempty"`
// TimeoutMS bounds this single exchange on the device.
TimeoutMS int `json:"timeoutMs,omitempty"`
}
DeviceTransceiveRequest asks a device to exchange raw data with the tag it is holding.
There is deliberately no connect/disconnect pair around this: a tag session is already delimited by the tagScanned and tagRemoved events, and on phones the OS owns the session anyway.
type DeviceTransceiveResponse ¶ added in v1.2.0
type DeviceTransceiveResponse struct {
RequestID string `json:"requestID"`
Success bool `json:"success"`
Data []byte `json:"data,omitempty"` // Response bytes, base64 in transit
Error string `json:"error,omitempty"`
ErrorCode protocol.ErrorCode `json:"errorCode,omitempty"`
}
DeviceTransceiveResponse carries the tag's reply.
type DeviceWriteRequest ¶
type DeviceWriteRequest struct {
RequestID string `json:"requestID"` // Unique request ID for correlation
DeviceID string `json:"deviceID"` // Target device
NDEFMessage *protocol.NDEFMessageInput `json:"ndefMessage"` // Data to write, as records
TagUID string `json:"tagUID,omitempty"`
Lock bool `json:"lock,omitempty"` // Make read-only after writing
// NDEFBytes is the same message already encoded, and is authoritative where
// the two disagree. A device that can write raw NDEF should prefer it; the
// record form exists for APIs like Web NFC that only accept records and
// cannot express every record type faithfully.
NDEFBytes []byte `json:"ndefBytes,omitempty"`
// IdempotencyKey identifies the logical write. A device that has already
// applied this key must report the previous outcome rather than write
// again, since the same request can arrive twice if a response is lost to a
// dropped connection.
IdempotencyKey string `json:"idempotencyKey,omitempty"`
}
DeviceWriteRequest is sent by the agent to a device to write a tag.
type DeviceWriteResponse ¶
type DeviceWriteResponse struct {
RequestID string `json:"requestID"`
Success bool `json:"success"`
Error string `json:"error,omitempty"`
ErrorCode protocol.ErrorCode `json:"errorCode,omitempty"` // Preferred over parsing Error
}
DeviceWriteResponse is sent by a device after a write operation.
type DisconnectReason ¶ added in v1.2.0
type DisconnectReason string
DisconnectReason explains why a device's session ended.
const ( // DisconnectGoodbye means the device announced its departure. DisconnectGoodbye DisconnectReason = "goodbye" // DisconnectClosed means the connection closed cleanly without a goodbye. DisconnectClosed DisconnectReason = "closed" // DisconnectDropped means the connection went away without a close // handshake: the device crashed, lost its radio, or was killed. DisconnectDropped DisconnectReason = "dropped" )
func (DisconnectReason) Expected ¶ added in v1.2.0
func (r DisconnectReason) Expected() bool
Expected reports whether the device left on purpose. A dropped device may well come back; one that said goodbye should not be waited on.
type GoodbyeRequest ¶ added in v1.2.0
type GoodbyeRequest struct {
DeviceID string `json:"deviceID,omitempty"`
Reason string `json:"reason,omitempty"`
}
GoodbyeRequest is sent by a v1 device that is leaving deliberately, so the agent can tell an intentional departure from a dropped connection.
type HelloRequest ¶ added in v1.2.0
type HelloRequest struct {
ProtocolVersion int `json:"protocolVersion"`
DeviceRegistrationRequest
}
HelloRequest is the first frame from a v1 device. It folds registration into the version handshake so setup costs one round trip rather than two.
type HelloResponse ¶ added in v1.2.0
type HelloResponse struct {
ProtocolVersion int `json:"protocolVersion"`
DeviceRegistrationResponse
}
HelloResponse reports the version both sides will speak, never higher than the device asked for.
type Manager ¶
type Manager struct {
// contains filtered or unexported fields
}
Manager implements nfc.Manager for phones and other networked devices, and serves the WebSocket endpoint they connect to. See Handler.
It owns both the device registry and the sessions behind it, so a registration and its connection cannot outlive one another.
func NewManager ¶
NewManager creates a new smartphone manager.
func (*Manager) ActiveTag ¶ added in v1.2.0
func (m *Manager) ActiveTag(deviceID string) (ActiveTagInfo, bool)
ActiveTag returns the tag on the named device. An empty deviceID asks for the most recent scan across all devices.
Tags are tracked per device rather than in a single slot: two phones can each be holding one, and a request that names its target must reach that target rather than whichever scanned last.
func (*Manager) ActiveTagDevices ¶ added in v1.2.0
ActiveTagDevices lists the devices currently holding a tag, most recent first.
func (*Manager) DeviceChanges ¶
func (m *Manager) DeviceChanges() <-chan struct{}
DeviceChanges returns a channel that signals when devices are registered or unregistered.
func (*Manager) DeviceMaxHoldMs ¶ added in v1.2.0
DeviceMaxHoldMs reports how long a device can keep a tag available for work, zero meaning open-ended. That is also the answer for a device that declared nothing and for one no longer connected, so treat it as advice about what is worth attempting rather than as permission.
func (*Manager) Devices ¶ added in v1.2.0
func (m *Manager) Devices() ([]nfc.DeviceListing, error)
Devices lists the devices connected right now, by the identity each holds. An aggregate adds the prefix naming this manager.
func (*Manager) DevicesHoldingTags ¶ added in v1.2.0
DevicesHoldingTags lists the devices currently holding one, most recent first.
func (*Manager) DisconnectDevice ¶ added in v1.2.0
DisconnectDevice ends a device's live session, reporting whether there was one to end. The reason goes out as the WebSocket close reason, so the device can tell being turned away from losing its radio.
Closing the socket is the whole teardown: the session goroutine's read fails and its deferred endSession unregisters the device, fails its pending requests and clears its active tag. Repeating that here would race it.
Credentials are checked once, at the upgrade, so this is what makes a credential change reach a device that is already connected.
func (*Manager) Dropped ¶ added in v1.2.0
Dropped counts the scans and removals that could not be published within ScanPublishTimeout since the manager started. A climbing number means subscribers are not keeping up and taps are being lost.
func (*Manager) GetActiveDeviceCount ¶
GetActiveDeviceCount returns the number of active devices.
func (*Manager) GetDeviceCount ¶
GetDeviceCount returns the number of registered devices.
func (*Manager) Handler ¶ added in v1.2.0
func (m *Manager) Handler(opts ServerOptions) http.Handler
Handler returns the HTTP handler serving device connections.
It also stores opts on the manager, since the capabilities a tag reports depend on them. Call it once, before serving.
The handler admits every device that reaches it. Which devices may connect is not this driver's decision: mount it behind something that checks a credential and names the device it admitted (pairednfc.Manager.Admit), or mount it bare for a build reached only over a trusted transport.
func (*Manager) LockTag ¶ added in v1.2.0
func (m *Manager) LockTag(ctx context.Context, deviceID, tagUID, idempotencyKey string) (*nfc.LockResult, error)
LockTag makes the tag the device is holding permanently read-only. It travels as a write with no message: the device protocol has one tag-modifying frame, not two.
func (*Manager) OpenDevice ¶
OpenDevice opens connection to a registered smartphone device by ID. Format: "smartphone:{deviceID}" or just "{deviceID}"
func (*Manager) RegisterDevice ¶
func (m *Manager) RegisterDevice(req DeviceRegistrationRequest) (*Device, error)
RegisterDevice registers a device under an identity of this manager's own.
func (*Manager) Scans ¶ added in v1.2.0
func (m *Manager) Scans() *event.Signal[nfc.ScannedTag]
Scans carries every tag the registered devices report, as reported. What is read off the tag is the supervisor's, not this driver's.
func (*Manager) SendTagData ¶
SendTagData converts tag data and broadcasts it via the data channel.
func (*Manager) SendTagRemoved ¶
func (m *Manager) SendTagRemoved(deviceID string, data TagRemovedData) error
SendTagRemoved broadcasts a tag removal event via the data channel.
func (*Manager) SendToDevice ¶ added in v1.2.0
SendToDevice sends a message over a device's session.
func (*Manager) TagCapabilities ¶ added in v1.2.0
func (m *Manager) TagCapabilities(_ context.Context, deviceID, tagUID string) (*nfc.TagCapabilities, error)
TagCapabilities answers from what the device declared at the scan, with no round trip, so it costs nothing to ask.
func (*Manager) TagOn ¶ added in v1.2.0
TagOn reports the tag a device is holding, by UID. An empty deviceID asks for the most recent scan across all devices.
func (*Manager) TransceiveTag ¶ added in v1.2.0
func (m *Manager) TransceiveTag(ctx context.Context, deviceID, tagUID string, data []byte, raw bool) ([]byte, error)
TransceiveTag asks the device to exchange raw bytes with the tag.
func (*Manager) TransceiveWithDevice ¶ added in v1.2.0
func (m *Manager) TransceiveWithDevice(ctx context.Context, deviceID string, req DeviceTransceiveRequest) (DeviceTransceiveResponse, error)
TransceiveWithDevice sends a raw exchange to the tag a device is holding.
func (*Manager) UnregisterDevice ¶
UnregisterDevice removes a smartphone device.
func (*Manager) UpdateHeartbeat ¶
UpdateHeartbeat updates device last-seen timestamp.
func (*Manager) WriteTag ¶ added in v1.2.0
func (m *Manager) WriteTag(ctx context.Context, deviceID, tagUID string, msg *nfc.NDEFMessage, lock bool, idempotencyKey string) (*nfc.WriteResult, error)
WriteTag asks the device to encode msg onto the tag it is holding.
What comes back reports what the agent knows rather than what it checked. Whether the write could be confirmed is the tag's answer, not this driver's: a tag whose reads are a snapshot cannot confirm one, which is the same fact a reader's pipeline consults.
func (*Manager) WriteToDevice ¶ added in v1.2.0
func (m *Manager) WriteToDevice(ctx context.Context, deviceID string, req DeviceWriteRequest) (DeviceWriteResponse, error)
WriteToDevice asks a device to write the tag it holds and waits for the outcome. A request with Lock set and no message is a lock.
type ServerInfo ¶
type ServerInfo struct {
Version string `json:"version"`
SupportedNFC []string `json:"supportedNFC"` // ["mifare", "desfire", etc.]
// PublicKeyPin identifies this agent across certificate reissues, as
// "sha256/<base64>" over the SubjectPublicKeyInfo. A device records it when
// pairing and compares it on later connections, which is how it recognizes
// the agent without a certificate authority.
//
// Empty when the agent runs without its own generated certificate.
PublicKeyPin string `json:"publicKeyPin,omitempty"`
}
ServerInfo contains information about the server.
type ServerOptions ¶ added in v1.2.0
type ServerOptions struct {
// CheckOrigin admits or rejects an upgrade by Origin. Nil admits any.
CheckOrigin func(r *http.Request) bool
// AllowTagModification reports whether writes, locks and raw exchanges are
// currently permitted. Nil permits them.
AllowTagModification func() bool
// PublicKeyPin is reported at registration so a device can recognise this
// agent on later connections. Nil, or one returning empty, omits it.
//
// A function because it is read per registration: the pin follows the
// certificate, which can be reissued while the endpoint stays up.
PublicKeyPin func() string
}
ServerOptions configures the device endpoint. Everything here is agent policy that the driver honours but does not decide. Authentication is not among them: the caller wraps the handler.
type Tag ¶
Tag wraps device NFC data in the nfc.Tag interface.
Writes, locks and raw exchanges route back to the device holding the tag when it declared support for them. The remaining connection methods are inherited from nfc.BaseTag as no-ops.
func (*Tag) CanMakeReadOnly ¶
CanMakeReadOnly reports whether the tag can be locked through its device.
func (*Tag) Capabilities ¶
func (t *Tag) Capabilities() nfc.TagCapabilities
Capabilities combines what the device declared for this tag with what the manager can actually route.
An operation is reported only when the tag supports it, the device declared it, and the device is still connected. A capability that outlives its session is a promise the Tag cannot keep.
func (*Tag) GetNDEFMessage ¶
func (t *Tag) GetNDEFMessage() (*nfc.NDEFMessage, error)
GetNDEFMessage returns the parsed NDEF message if available.
func (*Tag) IsWritable ¶
IsWritable reports whether the tag can currently be written.
func (*Tag) MakeReadOnly ¶
MakeReadOnly permanently locks the tag through the device holding it.
func (*Tag) NumericType ¶
NumericType returns a numeric representation of the tag type. For smartphone tags, we return 0 as they don't have freefare numeric types.
func (*Tag) SourceDevice ¶
SourceDevice returns the device ID that scanned this tag.
func (*Tag) Transceive ¶
Transceive exchanges raw data with the tag through the device holding it.
This is one network round trip per command. A chatty sequence spends the whole time the user is holding the tag against the device, so prefer the NDEF path where it will do.
func (*Tag) WriteData ¶
WriteData writes an encoded NDEF message to the tag through the device holding it.
func (*Tag) WriteDataAndLock ¶ added in v1.2.0
WriteDataAndLock writes and locks in the one exchange the device protocol offers, so a failure cannot leave the data written and the lock not applied.
This is what nfc.AtomicLockWriter exists for: a local tag has to write and then lock as two operations, and a tag reached over a connection that carries both should not be made to imitate that.
type TagData ¶
type TagData = DeviceTagData
TagData is what a device reports when it scans a tag.
This and the two below are aliases rather than copies. They duplicated the wire shapes field for field and JSON tag for JSON tag, which bought a hand-written translation step in the device server and nothing else: two names for one thing drift, and these had already begun to.
type TagRemovedData ¶
type TagRemovedData = DeviceTagRemovedData
TagRemovedData is what a device reports when a tag leaves its field.