Discussion:
[Linuxptp-devel] [PATCH 00/15] TLVs and Management messages
Geoff Salmon
2012-12-28 21:51:13 UTC
Permalink
Hi

I delayed replying until I could send you some patches, which took
longer than I expected. I was unable to easily break up the second
patch into smaller patches, so I'm sending it as is for now. It's a
bit of a monster. By sending patches I'm not trying to ignore the
discussion from the previous thread, just to give us something more
concrete to discuss. Now that the patches are available, what do you
think of this approach for handling TLVs?

My current setup makes it hard to test much of PTP in action. I should
be getting some hardware soon to improve my testing abilities, but
until then I've mostly only tested the management interface. However
because of the TLV changes I modified the code responsible for the
path trace, follow up info, and forwarding of management TLVs. Please
double check that I haven't broken anything there.

The patches also add unit tests. Run "make check" to run them.
Currently the tests either link with object files directly or fork a
ptp4l process and communicate with it over the UDS. In future I think
most of ptp4l should be built as a static library that the tests can
link with. If we could mock the transports, PHC and timerfds, then I
think tests could cover a lot of the functionality and various
configurations available in PTP.

Geoff Salmon (15):
pmc: prefer exact matches for command names
Many changes to TLV handling and PTP management mechanism
Set the message header messageLength inside msg_pre_send
Support receiving/sending unknown TLV/OrgTLV/MananagementTLVs
Avoid calling msg_pre_send/post_recv unless actually forwarding a
message
Adds clock description
Config clock description
Send clock description in management messages
Adds fault log to clock and implements FAULT_LOG management msg
Adds interface for getting a transport's physical and protocol
addresses
Implement getting physical address of udp, udp6, and raw ports
Sets port protocol and physical address in CLOCK_DESCRIPTION
Adds tests
Add test util for testing write/read TLV
Test writing and reading unknown TLVs

.gitignore | 12 +-
clock.c | 517 ++++++++++++++-------
clock.h | 14 +-
config.c | 43 +-
config.h | 4 +-
ddt.h | 54 ++-
ddt_pack.h | 177 ++++++++
ds.h | 44 +-
makefile | 5 +
msg.c | 151 ++++---
msg.h | 60 ++-
pdt.h | 13 +
pdt_pack.h | 161 +++++++
pmc.c | 502 +++++++++++++++------
port.c | 621 ++++++++++++++-----------
port.h | 22 +-
ptp4l.c | 35 +-
raw.c | 14 +
tests/TESTS | 7 +
tests/childproc-t.c | 41 ++
tests/childproc.c | 477 ++++++++++++++++++++
tests/childproc.h | 55 +++
tests/description-t.c | 87 ++++
tests/errors-t.c | 121 +++++
tests/example-t.c | 30 ++
tests/faultlog-t.c | 189 ++++++++
tests/makefile | 72 +++
tests/mgmt.c | 240 ++++++++++
tests/mgmt.h | 68 +++
tests/priority-t.c | 39 ++
tests/runtests.c | 1204 +++++++++++++++++++++++++++++++++++++++++++++++++
tests/tap/basic.c | 629 ++++++++++++++++++++++++++
tests/tap/basic.h | 134 ++++++
tests/tap/float.c | 67 +++
tests/tap/float.h | 42 ++
tests/tap/libtap.sh | 246 ++++++++++
tests/tap/macros.h | 88 ++++
tests/testutil.c | 136 ++++++
tests/testutil.h | 64 +++
tests/unknown_tlv-t.c | 118 +++++
tlv.c | 852 ++++++++++++++++++++++++++--------
tlv.h | 503 +++++++++++++++------
tlv_pack.h | 544 ++++++++++++++++++++++
transport.c | 39 +-
transport.h | 30 +-
transport_private.h | 7 +
udp.c | 48 +-
udp6.c | 21 +
uds.c | 11 +
util.c | 42 ++
util.h | 30 ++
51 files changed, 7675 insertions(+), 1055 deletions(-)
create mode 100644 ddt_pack.h
create mode 100644 pdt_pack.h
create mode 100644 tests/TESTS
create mode 100644 tests/childproc-t.c
create mode 100644 tests/childproc.c
create mode 100644 tests/childproc.h
create mode 100644 tests/description-t.c
create mode 100644 tests/errors-t.c
create mode 100644 tests/example-t.c
create mode 100644 tests/faultlog-t.c
create mode 100644 tests/makefile
create mode 100644 tests/mgmt.c
create mode 100644 tests/mgmt.h
create mode 100644 tests/priority-t.c
create mode 100644 tests/runtests.c
create mode 100644 tests/tap/basic.c
create mode 100644 tests/tap/basic.h
create mode 100644 tests/tap/float.c
create mode 100644 tests/tap/float.h
create mode 100644 tests/tap/libtap.sh
create mode 100644 tests/tap/macros.h
create mode 100644 tests/testutil.c
create mode 100644 tests/testutil.h
create mode 100644 tests/unknown_tlv-t.c
create mode 100644 tlv_pack.h
--
1.8.0.2
Geoff Salmon
2012-12-28 21:51:14 UTC
Permalink
Previously if a command's full name was a prefix of another command
then parse_id would return AMBIGUOUS_ID. This was a problem for the
TIME and various TIME_* messages.
---
pmc.c | 7 +++++++
1 file changed, 7 insertions(+)

diff --git a/pmc.c b/pmc.c
index 5efcbe5..3fe0be7 100644
--- a/pmc.c
+++ b/pmc.c
@@ -388,6 +388,13 @@ static int parse_action(char *s)
static int parse_id(char *s)
{
int i, index = BAD_ID, len = strlen(s);
+ /* check for exact match */
+ for (i = 0; i < ARRAY_SIZE(idtab); i++) {
+ if (strcasecmp(s, idtab[i].name) == 0) {
+ return i;
+ }
+ }
+ /* look for a unique prefix match */
for (i = 0; i < ARRAY_SIZE(idtab); i++) {
if (0 == strncasecmp(s, idtab[i].name, len)) {
if (index == BAD_ID)
--
1.8.0.2
Richard Cochran
2012-12-31 19:41:34 UTC
Permalink
Post by Geoff Salmon
Previously if a command's full name was a prefix of another command
then parse_id would return AMBIGUOUS_ID. This was a problem for the
TIME and various TIME_* messages.
---
pmc.c | 7 +++++++
1 file changed, 7 insertions(+)
Applied.

Thanks,
Richard
Geoff Salmon
2012-12-28 21:51:16 UTC
Permalink
---
msg.c | 68 +++++++++++++++++++++++++++++-------------------------------------
pmc.c | 1 -
port.c | 34 +++++++++------------------------
3 files changed, 39 insertions(+), 64 deletions(-)

diff --git a/msg.c b/msg.c
index 7d071d6..51ddc6e 100644
--- a/msg.c
+++ b/msg.c
@@ -253,58 +253,50 @@ void msg_get(struct ptp_message *m)
m->refcnt++;
}

-int msg_post_recv(struct ptp_message *m, int cnt)
-{
- int pdulen, type;
- uint8_t *suffix = NULL;
-
- if (cnt < sizeof(struct ptp_header))
- return -1;
-
- if (hdr_post_recv(&m->header))
- return -1;
-
- if (m->header.messageLength != cnt)
- return -1;
-
- type = msg_type(m);
-
+static int msg_sizeof(int type) {
switch (type) {
case SYNC:
- pdulen = sizeof(struct sync_msg);
- break;
+ return sizeof(struct sync_msg);
case DELAY_REQ:
- pdulen = sizeof(struct delay_req_msg);
- break;
+ return sizeof(struct delay_req_msg);
case PDELAY_REQ:
- pdulen = sizeof(struct pdelay_req_msg);
- break;
+ return sizeof(struct pdelay_req_msg);
case PDELAY_RESP:
- pdulen = sizeof(struct pdelay_resp_msg);
- break;
+ return sizeof(struct pdelay_resp_msg);
case FOLLOW_UP:
- pdulen = sizeof(struct follow_up_msg);
- break;
+ return sizeof(struct follow_up_msg);
case DELAY_RESP:
- pdulen = sizeof(struct delay_resp_msg);
- break;
+ return sizeof(struct delay_resp_msg);
case PDELAY_RESP_FOLLOW_UP:
- pdulen = sizeof(struct pdelay_resp_fup_msg);
- break;
+ return sizeof(struct pdelay_resp_fup_msg);
case ANNOUNCE:
- pdulen = sizeof(struct announce_msg);
- break;
+ return sizeof(struct announce_msg);
case SIGNALING:
- pdulen = sizeof(struct signaling_msg);
- break;
+ return sizeof(struct signaling_msg);
case MANAGEMENT:
- pdulen = sizeof(struct management_msg);
- break;
+ return sizeof(struct management_msg);
default:
return -1;
}
+}

- if (cnt < pdulen)
+int msg_post_recv(struct ptp_message *m, int cnt)
+{
+ int pdulen, type;
+ uint8_t *suffix = NULL;
+
+ if (cnt < sizeof(struct ptp_header))
+ return -1;
+
+ if (hdr_post_recv(&m->header))
+ return -1;
+
+ if (m->header.messageLength != cnt)
+ return -1;
+
+ type = msg_type(m);
+ pdulen = msg_sizeof(type);
+ if (pdulen == -1 || cnt < pdulen)
return -1;

switch (type) {
@@ -407,7 +399,7 @@ int msg_pre_send(struct ptp_message *m)
default:
return -1;
}
- m->total_msg_len = m->header.messageLength;
+ m->total_msg_len = msg_sizeof(type);
suffix_pre_send(m, suffix, MAX_MSG_LEN - (suffix - m->data.buffer));
/* suffix_pre_send updates the total_msg_len. Copy it back to
header. */
diff --git a/pmc.c b/pmc.c
index ac998aa..35a6e34 100644
--- a/pmc.c
+++ b/pmc.c
@@ -125,7 +125,6 @@ static struct ptp_message *pmc_message(uint8_t action)

msg->header.tsmt = MANAGEMENT | transport_specific;
msg->header.ver = PTP_VERSION;
- msg->header.messageLength = sizeof(struct management_msg);
msg->header.domainNumber = domain_number;
msg->header.sourcePortIdentity = port_identity;
msg->header.sequenceId = sequence_id++;
diff --git a/port.c b/port.c
index 43b1c55..5157b86 100644
--- a/port.c
+++ b/port.c
@@ -652,18 +652,16 @@ static void port_synchronize(struct port *p,
static int port_pdelay_request(struct port *p)
{
struct ptp_message *msg;
- int cnt, pdulen;
+ int cnt;

msg = msg_allocate();
if (!msg)
return -1;

- pdulen = sizeof(struct pdelay_req_msg);
msg->hwts.type = p->timestamping;

msg->header.tsmt = PDELAY_REQ | p->transportSpecific;
msg->header.ver = PTP_VERSION;
- msg->header.messageLength = pdulen;
msg->header.domainNumber = clock_domain_number(p->clock);
msg->header.correction = -p->pod.asymmetry;
msg->header.sourcePortIdentity = p->ds.portIdentity;
@@ -675,7 +673,7 @@ static int port_pdelay_request(struct port *p)
if (msg_pre_send(msg))
goto out;

- cnt = transport_peer(p->trp, &p->fda, 1, msg, pdulen, &msg->hwts);
+ cnt = transport_peer(p->trp, &p->fda, 1, msg, msg->total_msg_len, &msg->hwts);
if (cnt <= 0) {
pr_err("port %hu: send peer delay request failed", portnum(p));
goto out;
@@ -698,7 +696,7 @@ out:
static int port_delay_request(struct port *p)
{
struct ptp_message *msg;
- int cnt, pdulen;
+ int cnt;

if (p->ds.delayMechanism == DM_P2P)
return port_pdelay_request(p);
@@ -707,12 +705,10 @@ static int port_delay_request(struct port *p)
if (!msg)
return -1;

- pdulen = sizeof(struct delay_req_msg);
msg->hwts.type = p->timestamping;

msg->header.tsmt = DELAY_REQ | p->transportSpecific;
msg->header.ver = PTP_VERSION;
- msg->header.messageLength = pdulen;
msg->header.domainNumber = clock_domain_number(p->clock);
msg->header.correction = -p->pod.asymmetry;
msg->header.sourcePortIdentity = p->ds.portIdentity;
@@ -723,7 +719,7 @@ static int port_delay_request(struct port *p)
if (msg_pre_send(msg))
goto out;

- cnt = transport_send(p->trp, &p->fda, 1, msg, pdulen, &msg->hwts);
+ cnt = transport_send(p->trp, &p->fda, 1, msg, msg->total_msg_len, &msg->hwts);
if (cnt <= 0) {
pr_err("port %hu: send delay request failed", portnum(p));
goto out;
@@ -762,7 +758,6 @@ static int port_tx_announce(struct port *p)

msg->header.tsmt = ANNOUNCE | p->transportSpecific;
msg->header.ver = PTP_VERSION;
- msg->header.messageLength = sizeof(struct announce_msg);
msg->header.domainNumber = clock_domain_number(p->clock);
msg->header.sourcePortIdentity = p->ds.portIdentity;
msg->header.sequenceId = p->seqnum.announce++;
@@ -824,7 +819,6 @@ static int port_tx_sync(struct port *p)

msg->header.tsmt = SYNC | p->transportSpecific;
msg->header.ver = PTP_VERSION;
- msg->header.messageLength = sizeof(struct sync_msg);
msg->header.domainNumber = clock_domain_number(p->clock);
msg->header.sourcePortIdentity = p->ds.portIdentity;
msg->header.sequenceId = p->seqnum.sync++;
@@ -862,7 +856,6 @@ static int port_tx_sync(struct port *p)

fup->header.tsmt = FOLLOW_UP | p->transportSpecific;
fup->header.ver = PTP_VERSION;
- fup->header.messageLength = sizeof(struct follow_up_msg);
fup->header.domainNumber = clock_domain_number(p->clock);
fup->header.sourcePortIdentity = p->ds.portIdentity;
fup->header.sequenceId = p->seqnum.sync - 1;
@@ -1082,7 +1075,7 @@ static int process_announce(struct port *p, struct ptp_message *m)
static int process_delay_req(struct port *p, struct ptp_message *m)
{
struct ptp_message *msg;
- int cnt, err = 0, pdulen;
+ int cnt, err = 0;

if (p->ds.portState != PS_MASTER && p->ds.portState != PS_GRAND_MASTER)
return 0;
@@ -1096,12 +1089,10 @@ static int process_delay_req(struct port *p, struct ptp_message *m)
if (!msg)
return -1;

- pdulen = sizeof(struct delay_resp_msg);
msg->hwts.type = p->timestamping;

msg->header.tsmt = DELAY_RESP | p->transportSpecific;
msg->header.ver = PTP_VERSION;
- msg->header.messageLength = pdulen;
msg->header.domainNumber = m->header.domainNumber;
msg->header.correction = m->header.correction;
msg->header.sourcePortIdentity = p->ds.portIdentity;
@@ -1117,7 +1108,7 @@ static int process_delay_req(struct port *p, struct ptp_message *m)
err = -1;
goto out;
}
- cnt = transport_send(p->trp, &p->fda, 0, msg, pdulen, NULL);
+ cnt = transport_send(p->trp, &p->fda, 0, msg, msg->total_msg_len, NULL);
if (cnt <= 0) {
pr_err("port %hu: send delay response failed", portnum(p));
err = -1;
@@ -1211,7 +1202,7 @@ static void process_follow_up(struct port *p, struct ptp_message *m)
static int process_pdelay_req(struct port *p, struct ptp_message *m)
{
struct ptp_message *rsp, *fup;
- int cnt, err = -1, rsp_len, fup_len;
+ int cnt, err = -1;

if (p->ds.delayMechanism == DM_E2E) {
pr_warning("port %hu: pdelay_req on E2E port", portnum(p));
@@ -1231,12 +1222,10 @@ static int process_pdelay_req(struct port *p, struct ptp_message *m)
return -1;
}

- rsp_len = sizeof(struct pdelay_resp_msg);
rsp->hwts.type = p->timestamping;

rsp->header.tsmt = PDELAY_RESP | p->transportSpecific;
rsp->header.ver = PTP_VERSION;
- rsp->header.messageLength = rsp_len;
rsp->header.domainNumber = m->header.domainNumber;
rsp->header.sourcePortIdentity = p->ds.portIdentity;
rsp->header.sequenceId = m->header.sequenceId;
@@ -1255,12 +1244,10 @@ static int process_pdelay_req(struct port *p, struct ptp_message *m)
ts_to_timestamp(&m->hwts.ts, &rsp->pdelay_resp.requestReceiptTimestamp);
rsp->pdelay_resp.requestingPortIdentity = m->header.sourcePortIdentity;

- fup_len = sizeof(struct pdelay_resp_fup_msg);
fup->hwts.type = p->timestamping;

fup->header.tsmt = PDELAY_RESP_FOLLOW_UP | p->transportSpecific;
fup->header.ver = PTP_VERSION;
- fup->header.messageLength = fup_len;
fup->header.domainNumber = m->header.domainNumber;
fup->header.correction = m->header.correction;
fup->header.sourcePortIdentity = p->ds.portIdentity;
@@ -1273,7 +1260,7 @@ static int process_pdelay_req(struct port *p, struct ptp_message *m)
if (msg_pre_send(rsp))
goto out;

- cnt = transport_peer(p->trp, &p->fda, 1, rsp, rsp_len, &rsp->hwts);
+ cnt = transport_peer(p->trp, &p->fda, 1, rsp, rsp->total_msg_len, &rsp->hwts);
if (cnt <= 0) {
pr_err("port %hu: send peer delay response failed", portnum(p));
goto out;
@@ -1289,7 +1276,7 @@ static int process_pdelay_req(struct port *p, struct ptp_message *m)
if (msg_pre_send(fup))
goto out;

- cnt = transport_peer(p->trp, &p->fda, 0, fup, fup_len, &rsp->hwts);
+ cnt = transport_peer(p->trp, &p->fda, 0, fup, fup->total_msg_len, &rsp->hwts);
if (cnt <= 0) {
pr_err("port %hu: send pdelay_resp_fup failed", portnum(p));
goto out;
@@ -1757,18 +1744,15 @@ struct ptp_message *port_management_reply(struct PortIdentity pid,
struct ptp_message *req)
{
struct ptp_message *msg;
- int pdulen;

msg = msg_allocate();
if (!msg)
return NULL;

- pdulen = sizeof(struct management_msg);
msg->hwts.type = ingress->timestamping;

msg->header.tsmt = MANAGEMENT | ingress->transportSpecific;
msg->header.ver = PTP_VERSION;
- msg->header.messageLength = pdulen;
msg->header.domainNumber = clock_domain_number(ingress->clock);
msg->header.sourcePortIdentity = pid;
msg->header.sequenceId = req->header.sequenceId;
--
1.8.0.2
Richard Cochran
2012-12-31 19:49:54 UTC
Permalink
Post by Geoff Salmon
---
msg.c | 68 +++++++++++++++++++++++++++++-------------------------------------
pmc.c | 1 -
port.c | 34 +++++++++------------------------
3 files changed, 39 insertions(+), 64 deletions(-)
What does this patch accomplish or fix?

Why is this patch needed at all?

Thanks,
Richard
Richard Cochran
2012-12-31 19:52:07 UTC
Permalink
Since only the port layer knows what TLV options are in effect, the
port layer should device the message length, not the msg.c layer.

Thanks,
Richard
Geoff Salmon
2012-12-31 20:31:23 UTC
Permalink
Hi
Comments below.

On 12/31/12 14:52, Richard Cochran wrote:>
Post by Richard Cochran
Since only the port layer knows what TLV options are in effect, the
port layer should device the message length, not the msg.c layer.
After patch #2, to send a message the port and clock layers pass the
message and optionally a TLV struct. The length of the TLV is determined
from the TLV type (+ org id/subtype or management id) while serializing
the TLV, inside tlv.c. The code in clock or port doesn't need to provide
either the TLV length or the total message length anymore.

Patch #2 made setting the msg->header.messageLength when sending a
message redundant, because msg.c already knows the size of the message,
so this patch removes the potential for sending a message with
mismatching msg->header.tsmt and msg->header.messageLength fields.

thanks for looking at these patches so quickly!
- Geoff
Geoff Salmon
2012-12-28 21:51:17 UTC
Permalink
The data for these unknown TLV types is a uint8_t* and length. No
network byte-order conversion is performed.
---
tlv.c | 61 +++++++++++++++++++++++++++++++++++++++++++++++++------------
tlv.h | 8 ++++++++
2 files changed, 57 insertions(+), 12 deletions(-)

diff --git a/tlv.c b/tlv.c
index 363c160..ad7b044 100644
--- a/tlv.c
+++ b/tlv.c
@@ -281,6 +281,14 @@ static int tlv_write_mgmt_data(enum management_types type,
if (len < 8 + 8 + 4 + 4 + 2 + 12 + 4 + CLOCK_IDENTITY_LENGTH)
return -1;
return packTimeStatusNP(&data->tsn, buf);
+ default:
+ if (data->unknown.len) {
+ if (len < data->unknown.len || data->unknown.data == 0) {
+ return -1;
+ }
+ memmove(buf, data->unknown.data, data->unknown.len);
+ }
+ return data->unknown.len;
}
return -1;
}
@@ -392,8 +400,12 @@ int tlv_read_mgmt_data(enum management_types type,
return 1;
case MGMT_TIME_STATUS_NP:
return unpackTimeStatusNP(buf, &data->tsn);
+ default:
+ data->unknown.data = buf;
+ data->unknown.len = len;
+ return len;
}
- return -MGMT_ERROR_NO_SUCH_ID;
+ return -MGMT_ERROR_NOT_SUPPORTED;
}

static int tlv_write_org_data(struct OrgTLV *org, uint8_t *buf, int len) {
@@ -406,12 +418,18 @@ static int tlv_write_org_data(struct OrgTLV *org, uint8_t *buf, int len) {
return -1;
return packAVBFollowUp(&data->fup, buf);
}
- return -1;
+ break;
}
- return -1;
+ if (data->unknown.len) {
+ if (len < data->unknown.len || data->unknown.data == 0) {
+ return -1;
+ }
+ memmove(buf, data->unknown.data, data->unknown.len);
+ }
+ return data->unknown.len;
}

-static int tlv_read_org_data(uint8_t *buf, struct OrgTLV *org) {
+static int tlv_read_org_data(uint8_t *buf, int len, struct OrgTLV *org) {
union OrgTLVData *data = &org->data;
switch (org->id) {
case OUI_CISCO_SYSTEMS:
@@ -419,9 +437,11 @@ static int tlv_read_org_data(uint8_t *buf, struct OrgTLV *org) {
case SUBTYPE_AVB_FOLLOW_UP:
return unpackAVBFollowUp(buf, &data->fup);
}
- return -1;
+ break;
}
- return -1;
+ data->unknown.data = buf;
+ data->unknown.len = len;
+ return len;
}

#define TLV_HDR_LEN 4
@@ -491,7 +511,14 @@ int tlv_write(struct tlv *tlv, uint8_t *buf, int len) {
}
break;
default:
- return -1;
+ if (tlv->unknown.len) {
+ if (len - offset < tlv->unknown.len || tlv->unknown.data == 0) {
+ return -1;
+ }
+ memmove(buf + offset, tlv->unknown.data, tlv->unknown.len);
+ offset += tlv->unknown.len;
+ }
+ break;
}

/* write padding if necessary */
@@ -520,6 +547,11 @@ int tlv_read(uint8_t *buf, int len, struct tlv *tlv) {
tlv->type = hdr.tlvType;
tlv->length = hdr.lengthField;

+ if (len < tlv->length + TLV_HDR_LEN) {
+ /* last TLV is incomplete */
+ return 0;
+ }
+
switch (tlv->type) {
case TLV_MANAGEMENT:
unpackEnumeration16(buf + offset, &mgmt_id);
@@ -534,9 +566,9 @@ int tlv_read(uint8_t *buf, int len, struct tlv *tlv) {
} else {
tlv->mgmt.empty_body = 0;
result = tlv_read_mgmt_data(tlv->mgmt.id,
- buf + offset,
- tlv->length - 2,
- &tlv->mgmt.data);
+ buf + offset,
+ tlv->length - offset + TLV_HDR_LEN,
+ &tlv->mgmt.data);
if (result < 0) {
tlv->mgmt.error = -result;
offset += tlv->length - 2;
@@ -551,7 +583,9 @@ int tlv_read(uint8_t *buf, int len, struct tlv *tlv) {
case TLV_ORGANIZATION_EXTENSION:
offset += unpackOrgTLV(buf + offset, &tlv->org);
if (offset > len) goto skip;
- result = tlv_read_org_data(buf + offset, &tlv->org);
+ result = tlv_read_org_data(buf + offset,
+ tlv->length - offset + TLV_HDR_LEN,
+ &tlv->org);
if (result == -1)
goto skip;
offset += result;
@@ -563,7 +597,10 @@ int tlv_read(uint8_t *buf, int len, struct tlv *tlv) {
offset += tlv->path_trace.num * sizeof(struct ClockIdentity);
break;
default:
- goto skip;
+ tlv->unknown.data = buf + offset;
+ tlv->unknown.len = tlv->length;
+ offset += tlv->length;
+ break;
}
/* skip over padding */
if (offset % 2 == 1) {
diff --git a/tlv.h b/tlv.h
index 434dc0a..6a2b5d8 100644
--- a/tlv.h
+++ b/tlv.h
@@ -185,6 +185,11 @@ struct TLVHeader {
UInteger16 lengthField;
};

+struct unknown_data {
+ const Octet *data;
+ int len;
+};
+
/**** Structs for Manangement TLV Data ****/

struct PathTraceList {
@@ -278,6 +283,7 @@ union ManagementTLVData {

/* implementation specific */
struct time_status_np tsn;
+ struct unknown_data unknown;
};

/**** Structs for Organization Exception TLVs ****/
@@ -292,6 +298,7 @@ struct follow_up_info_tlv {

union OrgTLVData {
struct follow_up_info_tlv fup;
+ struct unknown_data unknown;
};

/**** Structs for TLV Types ****/
@@ -343,6 +350,7 @@ struct tlv {
struct OrgTLV org;
/* TLV_PATH_TRACE */
struct PathTraceTLV path_trace;
+ struct unknown_data unknown;
};
};
--
1.8.0.2
Richard Cochran
2012-12-31 19:54:41 UTC
Permalink
Post by Geoff Salmon
The data for these unknown TLV types is a uint8_t* and length. No
network byte-order conversion is performed.
Since we will not process unknown TLVs, there is no point in copying
their data.

Thanks,
Richard
Geoff Salmon
2012-12-31 20:49:50 UTC
Permalink
Post by Richard Cochran
Post by Geoff Salmon
The data for these unknown TLV types is a uint8_t* and length. No
network byte-order conversion is performed.
Since we will not process unknown TLVs, there is no point in copying
their data.
Thanks,
Richard
That's true. I wondered whether to just ignore the data or not, but note
that when receiving an unknown TLV, no memcpy/memmove is done. Only a
pointer to the data in the message buffer is written to the TLV struct.
It's only when sending an unknown TLV will the data be copied to the
message.

As things stand now, an unknown TLV is never sent, so the data is never
copied. This patch implements writing unknown TLVs mostly for symmetry.

If that's too hand wavy a reason for supporting writing unknown TLVs,
allow me to hand wave a bit more. I have additional patches I haven't
sent for supporting multiple TLVs per message and the beginnings of an
abstraction for PTP profiles.

With multiple TLV support, it's conceivable for a forwarded management
message to contain additional TLVs unknown to ptp4l which should still
be forwarded on to another node which might understand them. With
profile support, I was planning to add hooks into clock_manage and
port_manage where the profile can be given a chance to handle the
unknown TLVs. However, I was also going to have hooks into tlv_read and
tlv_write to let profiles pack and unpack their profile-specific TLVs,
so I'm not sure if unknown TLVs will end up being sent by profiles either.

Shall I remove support for writing unknown TLVs from this patch? I'll
keep the writing for later if it's ever needed.

- Geoff
Richard Cochran
2012-12-31 21:34:08 UTC
Permalink
Post by Geoff Salmon
If that's too hand wavy a reason for supporting writing unknown
TLVs, allow me to hand wave a bit more. I have additional patches I
haven't sent for supporting multiple TLVs per message and the
beginnings of an abstraction for PTP profiles.
This idea sounds intriguing. Maybe you could post that first (at least
as a sketch) before all of the TLV changes?
Post by Geoff Salmon
With multiple TLV support, it's conceivable for a forwarded
management message to contain additional TLVs unknown to ptp4l which
should still be forwarded on to another node which might understand
them.
No messages are ever forwarded. Except management, which are sent
(almost) unchanged.
Post by Geoff Salmon
With profile support, I was planning to add hooks into
clock_manage and port_manage where the profile can be given a chance
to handle the unknown TLVs.
If we have profile code that handles a TLV, then the TLV is no longer
unknown.

Thanks,
Richard
Geoff Salmon
2012-12-31 21:29:35 UTC
Permalink
Actually, only reading unknown manangement TLVs is useful right now, and
that's only because an error message may be sent back. Maybe a better
patch would only support reading unknown management TLVs and completely
ignore unknown TLV and Org TLVs?

- Geoff
Post by Richard Cochran
Post by Geoff Salmon
The data for these unknown TLV types is a uint8_t* and length. No
network byte-order conversion is performed.
Since we will not process unknown TLVs, there is no point in copying
their data.
Thanks,
Richard
Geoff Salmon
2012-12-28 21:51:18 UTC
Permalink
---
clock.c | 40 +++++++++++++++++++++++++++-------------
1 file changed, 27 insertions(+), 13 deletions(-)

diff --git a/clock.c b/clock.c
index 1aafd5f..04ef605 100644
--- a/clock.c
+++ b/clock.c
@@ -764,10 +764,35 @@ static int clock_management_validate(struct ptp_message *msg) {
return 1;
}

+static void clock_forward_mgmt_msg(struct clock *c, struct port *p, struct ptp_message *msg)
+{
+ int i, msg_ready = 0;
+ struct port *fwd;
+ if (forwarding(c, p) && msg->management.boundaryHops) {
+ for (i = 0; i < c->nports + 1; i++) {
+ fwd = c->port[i];
+ if (fwd != p && forwarding(c, fwd)) {
+ /* delay calling msg_pre_send until
+ * actually forwarding */
+ if (!msg_ready) {
+ msg_ready = 1;
+ msg->management.boundaryHops--;
+ msg_pre_send(msg);
+ }
+ if (port_forward(fwd, msg, msg->total_msg_len))
+ pr_err("port %d: management forward failed", i);
+ }
+ }
+ if (msg_ready) {
+ msg_post_recv(msg, msg->total_msg_len);
+ msg->management.boundaryHops++;
+ }
+ }
+}
+
void clock_manage(struct clock *c, struct port *p, struct ptp_message *msg)
{
int i, result;
- struct port *fwd;
struct tlv *tlv;
struct PortIdentity pid;
struct ClockIdentity *tcid, wildcard = {
@@ -781,18 +806,7 @@ void clock_manage(struct clock *c, struct port *p, struct ptp_message *msg)
}

/* Forward this message out all eligible ports. */
- if (forwarding(c, p) && msg->management.boundaryHops) {
- msg->management.boundaryHops--;
- msg_pre_send(msg);
- for (i = 0; i < c->nports + 1; i++) {
- fwd = c->port[i];
- if (fwd != p && forwarding(c, fwd) &&
- port_forward(fwd, msg, msg->total_msg_len))
- pr_err("port %d: management forward failed", i);
- }
- msg_post_recv(msg, msg->total_msg_len);
- msg->management.boundaryHops++;
- }
+ clock_forward_mgmt_msg(c, p, msg);

/* Apply this message to the local clock and ports. */
tcid = &msg->management.targetPortIdentity.clockIdentity;
--
1.8.0.2
Richard Cochran
2012-12-31 19:46:51 UTC
Permalink
Post by Geoff Salmon
---
clock.c | 40 +++++++++++++++++++++++++++-------------
1 file changed, 27 insertions(+), 13 deletions(-)
This patch is a nice little optimization. I wanted to apply it right
away, but cherry picking fails because of the large changes in the
patch before.

Can you please rebase and post this one all by itself?

Thanks,
Richard
Geoff Salmon
2012-12-31 20:12:54 UTC
Permalink
---
clock.c | 44 +++++++++++++++++++++++++++++---------------
1 file changed, 29 insertions(+), 15 deletions(-)

diff --git a/clock.c b/clock.c
index 39f378e..5a475f2 100644
--- a/clock.c
+++ b/clock.c
@@ -577,10 +577,36 @@ void clock_install_fda(struct clock *c, struct port *p, struct fdarray fda)
}
}

-void clock_manage(struct clock *c, struct port *p, struct ptp_message *msg)
+static void clock_forward_mgmt_msg(struct clock *c, struct port *p, struct ptp_message *msg)
{
- int i, pdulen;
+ int i, pdulen, msg_ready = 0;
struct port *fwd;
+ if (forwarding(c, p) && msg->management.boundaryHops) {
+ for (i = 0; i < c->nports + 1; i++) {
+ fwd = c->port[i];
+ if (fwd != p && forwarding(c, fwd)) {
+ /* delay calling msg_pre_send until
+ * actually forwarding */
+ if (!msg_ready) {
+ msg_ready = 1;
+ pdulen = msg->header.messageLength;
+ msg->management.boundaryHops--;
+ msg_pre_send(msg);
+ }
+ if (port_forward(fwd, msg, pdulen))
+ pr_err("port %d: management forward failed", i);
+ }
+ }
+ if (msg_ready) {
+ msg_post_recv(msg, pdulen);
+ msg->management.boundaryHops++;
+ }
+ }
+}
+
+void clock_manage(struct clock *c, struct port *p, struct ptp_message *msg)
+{
+ int i;
struct management_tlv *mgt;
struct PortIdentity pid;
struct ClockIdentity *tcid, wildcard = {
@@ -588,19 +614,7 @@ void clock_manage(struct clock *c, struct port *p, struct ptp_message *msg)
};

/* Forward this message out all eligible ports. */
- if (forwarding(c, p) && msg->management.boundaryHops) {
- pdulen = msg->header.messageLength;
- msg->management.boundaryHops--;
- msg_pre_send(msg);
- for (i = 0; i < c->nports + 1; i++) {
- fwd = c->port[i];
- if (fwd != p && forwarding(c, fwd) &&
- port_forward(fwd, msg, pdulen))
- pr_err("port %d: management forward failed", i);
- }
- msg_post_recv(msg, pdulen);
- msg->management.boundaryHops++;
- }
+ clock_forward_mgmt_msg(c, p, msg);

/* Apply this message to the local clock and ports. */
tcid = &msg->management.targetPortIdentity.clockIdentity;
--
1.8.0.2
Richard Cochran
2012-12-31 21:57:17 UTC
Permalink
Post by Geoff Salmon
---
clock.c | 44 +++++++++++++++++++++++++++++---------------
1 file changed, 29 insertions(+), 15 deletions(-)
Applied.

Thanks,
Richard
Geoff Salmon
2012-12-28 21:51:20 UTC
Permalink
---
config.c | 23 +++++++++++++++++++++++
1 file changed, 23 insertions(+)

diff --git a/config.c b/config.c
index 41cc041..0592dcd 100644
--- a/config.c
+++ b/config.c
@@ -158,6 +158,7 @@ static enum parser_result parse_global_setting(const char *option,
UInteger16 u16;
UInteger8 u8;
unsigned char mac[MAC_LEN];
+ unsigned char oui[OUI_LEN];

struct defaultDS *dds = &cfg->dds;
struct port_defaults *pod = &cfg->pod;
@@ -327,6 +328,28 @@ static enum parser_result parse_global_setting(const char *option,
else
return BAD_VALUE;

+ } else if (!strcmp(option, "product_description")) {
+ if (static_ptp_text_set(&cfg->clock_desc.productDescription, value) != 0) {
+ return BAD_VALUE;
+ }
+ /* TODO: enforce
+ manufacturerName;modelNumber;instanceIdentifier
+ format. See 15.5.3.1.2.7 */
+ } else if (!strcmp(option, "revision_data")) {
+ if (static_ptp_text_set(&cfg->clock_desc.revisionData, value) != 0) {
+ return BAD_VALUE;
+ }
+ /* TODO: enforce HW;FW;SW format. See 15.5.3.1.2.8 */
+ } else if (!strcmp(option, "user_description")) {
+ if (static_ptp_text_set(&cfg->clock_desc.userDescription, value) != 0) {
+ return BAD_VALUE;
+ }
+ } else if (!strcmp(option, "manufacturer_id")) {
+ if (OUI_LEN != sscanf(value, "%hhx:%hhx:%hhx",
+ &oui[0], &oui[1], &oui[2]))
+ return BAD_VALUE;
+ for (i = 0; i < OUI_LEN; i++)
+ cfg->clock_desc.manufacturer_id[i] = oui[i];
} else
return NOT_PARSED;
--
1.8.0.2
Geoff Salmon
2012-12-28 21:51:22 UTC
Permalink
Fault log is a TAILQ of FaultRecords stored in the clock. They are
read using the FAULT_LOG management message. Currently nothing adds to
the list.
---
clock.c | 9 ++++++++-
pmc.c | 27 +++++++++++++++++++++++++--
2 files changed, 33 insertions(+), 3 deletions(-)

diff --git a/clock.c b/clock.c
index d647690..9d4e085 100644
--- a/clock.c
+++ b/clock.c
@@ -79,6 +79,7 @@ struct clock {
tmv_t t1;
tmv_t t2;
struct clock_description desc;
+ struct FaultRecordList faults;
};

struct clock the_clock;
@@ -106,6 +107,7 @@ void clock_destroy(struct clock *c)
mave_destroy(c->avg_delay);
memset(c, 0, sizeof(*c));
msg_cleanup();
+ /* TODO: Cleanup faults in c->faults */
}

static int clock_management_error(struct PortIdentity pid, struct port *ingress,
@@ -163,7 +165,10 @@ static int clock_management_response(struct clock *c, struct port *p,
ptp_text_copy(&data->cd.userDescription, &c->desc.userDescription);
break;
case MGMT_FAULT_LOG:
- /* TODO: should be gettable */
+ data->fl.type = FAULT_RECORD_LIST;
+ data->fl.list = c->faults;
+ break;
+ case MGMT_FAULT_LOG_RESET:
return -MGMT_ERROR_NOT_SUPPORTED;
case MGMT_DEFAULT_DATA_SET:
data->dds = c->dds;
@@ -619,6 +624,8 @@ struct clock *clock_create(int phc_index, struct interface *iface, int count,
c->fault_timeout = FAULT_RESET_SECONDS;
c->fest.max_count = 2;

+ TAILQ_INIT(&c->faults);
+
for (i = 0; i < count; i++) {
c->port[i] = port_open(phc_index, timestamping, 1+i, &iface[i], c);
if (!c->port[i]) {
diff --git a/pmc.c b/pmc.c
index 35a6e34..a7554bf 100644
--- a/pmc.c
+++ b/pmc.c
@@ -66,7 +66,7 @@ struct management_id idtab[] = {
{ "SAVE_IN_NON_VOLATILE_STORAGE", MGMT_SAVE_IN_NON_VOLATILE_STORAGE, not_supported },
{ "RESET_NON_VOLATILE_STORAGE", MGMT_RESET_NON_VOLATILE_STORAGE, not_supported },
{ "INITIALIZE", MGMT_INITIALIZE, not_supported },
- { "FAULT_LOG", MGMT_FAULT_LOG, not_supported },
+ { "FAULT_LOG", MGMT_FAULT_LOG, do_get_action },
{ "FAULT_LOG_RESET", MGMT_FAULT_LOG_RESET, not_supported },
{ "DEFAULT_DATA_SET", MGMT_DEFAULT_DATA_SET, do_get_action },
{ "CURRENT_DATA_SET", MGMT_CURRENT_DATA_SET, do_get_action },
@@ -250,7 +250,7 @@ static const char *bool2str(int b) {

static void pmc_show(struct ptp_message *msg, FILE *fp)
{
- int i, action;
+ int i, offset, action;
struct tlv *tlv;
struct defaultDS *dds;
struct currentDS *cds;
@@ -259,6 +259,7 @@ static void pmc_show(struct ptp_message *msg, FILE *fp)
struct time_status_np *tsn;
struct portDS *p;
struct ClockDescription *cd;
+ struct FaultRecord fault;
if (msg_type(msg) != MANAGEMENT) {
return;
}
@@ -319,6 +320,28 @@ static void pmc_show(struct ptp_message *msg, FILE *fp)
IFMT "userDescription %s",
text2str(&data->cd.userDescription));
break;
+ case MGMT_FAULT_LOG:
+ fprintf(fp, "FAULT_LOG "
+ IFMT "numberRecords %hu",
+ data->fl.numberRecords);
+ offset = 0;
+ for (i = 0; i < data->fl.numberRecords; i++) {
+ offset += unpackFaultRecord(data->fl.buffer.buf + offset, &fault);
+ fprintf(fp,
+ IFMT "FaultRecord %d"
+ IFMT "time %s"
+ IFMT "severity %hhu"
+ IFMT "name %s"
+ IFMT "value %s"
+ IFMT "description %s",
+ i,
+ time2str(&fault.faultTime),
+ fault.severityCode,
+ text2str(&fault.faultName),
+ text2str1(&fault.faultValue),
+ text2str2(&fault.faultDescription));
+ }
+ break;
case MGMT_DEFAULT_DATA_SET:
dds = &data->dds;
fprintf(fp, "DEFAULT_DATA_SET "
--
1.8.0.2
Richard Cochran
2012-12-31 22:17:42 UTC
Permalink
Post by Geoff Salmon
Fault log is a TAILQ of FaultRecords stored in the clock. They are
read using the FAULT_LOG management message. Currently nothing adds to
the list.
Without anything generating fault log messages, this change seems not
too compelling to me. What would cause a fault message anyhow?

I was thinking that the pr_ messages might generate the fault log.

Thoughts?

Richard
Geoff Salmon
2012-12-28 21:51:19 UTC
Permalink
Adds struct containing clock description info that will be needed for
USER_DESCRIPTION and CLOCK_DESCRIPTION management messages.
---
clock.c | 8 ++++++++
clock.h | 3 +++
config.h | 2 ++
ds.h | 6 ++++++
ptp4l.c | 8 +++++++-
5 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/clock.c b/clock.c
index 04ef605..23f2beb 100644
--- a/clock.c
+++ b/clock.c
@@ -78,6 +78,7 @@ struct clock {
tmv_t c2;
tmv_t t1;
tmv_t t2;
+ struct clock_description desc;
};

struct clock the_clock;
@@ -540,6 +541,7 @@ UInteger8 clock_class(struct clock *c)

struct clock *clock_create(int phc_index, struct interface *iface, int count,
enum timestamp_type timestamping, struct defaultDS *ds,
+ struct clock_description *desc,
enum servo_type servo)
{
int i, fadj = 0, max_adj = 0.0, sw_ts = timestamping == TS_SOFTWARE ? 1 : 0;
@@ -556,6 +558,8 @@ struct clock *clock_create(int phc_index, struct interface *iface, int count,
if (c->nports)
clock_destroy(c);

+ c->desc = *desc;
+
if (c->dds.free_running) {
c->clkid = CLOCK_INVALID;
} else if (phc_index >= 0) {
@@ -1124,3 +1128,7 @@ static void handle_state_decision_event(struct clock *c)
int clock_num_ports(struct clock *c) {
return c->nports;
}
+
+struct clock_description *clock_description(struct clock *c) {
+ return &c->desc;
+}
diff --git a/clock.h b/clock.h
index b88daf1..fa50a43 100644
--- a/clock.h
+++ b/clock.h
@@ -71,6 +71,7 @@ UInteger8 clock_class(struct clock *c);
*/
struct clock *clock_create(int phc_index, struct interface *iface, int count,
enum timestamp_type timestamping, struct defaultDS *ds,
+ struct clock_description *desc,
enum servo_type servo);

/**
@@ -220,4 +221,6 @@ struct timePropertiesDS *clock_time_properties(struct clock *c);
*/
int clock_num_ports(struct clock *c);

+struct clock_description *clock_description(struct clock *c);
+
#endif
diff --git a/config.h b/config.h
index 5ceba9d..2555395 100644
--- a/config.h
+++ b/config.h
@@ -76,6 +76,8 @@ struct config {
int print_level;
int use_syslog;
int verbose;
+
+ struct clock_description clock_desc;
};

int config_read(char *name, struct config *cfg);
diff --git a/ds.h b/ds.h
index 5227682..ebfe9a0 100644
--- a/ds.h
+++ b/ds.h
@@ -107,5 +107,11 @@ struct port_defaults {
};

#define OUI_LEN 3
+struct clock_description {
+ struct StaticPTPText productDescription;
+ struct StaticPTPText revisionData;
+ struct StaticPTPText userDescription;
+ Octet manufacturer_id[OUI_LEN];
+};

#endif
diff --git a/ptp4l.c b/ptp4l.c
index 7506ba0..afb4642 100644
--- a/ptp4l.c
+++ b/ptp4l.c
@@ -87,6 +87,12 @@ static struct config cfg_settings = {
.verbose = 0,

.cfg_ignore = 0,
+ .clock_desc = {
+ .productDescription = { .length = 0, .max_symbols = 64 },
+ .revisionData = { .length = 0, .max_symbols = 32 },
+ .userDescription = { .length = 0, .max_symbols = 128 },
+ .manufacturer_id = { 0, 0, 0 }
+ }
};

static void handle_int_quit_term(int s)
@@ -335,7 +341,7 @@ int main(int argc, char *argv[])
}

clock = clock_create(phc_index, iface, cfg_settings.nports,
- *timestamping, ds,
+ *timestamping, ds, &cfg_settings.clock_desc,
cfg_settings.clock_servo);
if (!clock) {
fprintf(stderr, "failed to create a clock\n");
--
1.8.0.2
Richard Cochran
2012-12-31 22:14:07 UTC
Permalink
Post by Geoff Salmon
Adds struct containing clock description info that will be needed for
USER_DESCRIPTION and CLOCK_DESCRIPTION management messages.
---
clock.c | 8 ++++++++
clock.h | 3 +++
config.h | 2 ++
ds.h | 6 ++++++
ptp4l.c | 8 +++++++-
5 files changed, 26 insertions(+), 1 deletion(-)
This patch, and the two following, are about the right size for me to
digest. If these three were based on the existing code, then I would
probably merge them right away without any reservation.

Thanks,
Richard
Geoff Salmon
2012-12-28 21:51:24 UTC
Permalink
---
raw.c | 8 ++++++--
udp.c | 51 ++++++++++++++++++++++++++++++++-------------------
udp6.c | 13 ++++++++++++-
3 files changed, 50 insertions(+), 22 deletions(-)

diff --git a/raw.c b/raw.c
index e7ba2f3..5afba66 100644
--- a/raw.c
+++ b/raw.c
@@ -296,11 +296,15 @@ static void raw_release(struct transport *t)
}

static int raw_physical_addr(struct transport *t, struct fdarray *fda, uint8_t *addr) {
- return 0;
+ struct raw *raw = container_of(t, struct raw, t);
+ memcpy(addr, raw->ptp_addr.src, MAC_LEN);
+ return MAC_LEN;
}

static int raw_protocol_addr(struct transport *t, struct fdarray *fda, uint8_t *addr) {
- return 0;
+ struct raw *raw = container_of(t, struct raw, t);
+ memcpy(addr, raw->ptp_addr.src, MAC_LEN);
+ return MAC_LEN;
}

struct transport *raw_transport_create(void)
diff --git a/udp.c b/udp.c
index 7cfd26b..29b2f0f 100644
--- a/udp.c
+++ b/udp.c
@@ -23,13 +23,16 @@
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
+#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <unistd.h>

+#include "contain.h"
#include "print.h"
#include "sk.h"
+#include "ether.h"
#include "transport_private.h"
#include "udp.h"

@@ -38,6 +41,12 @@
#define PTP_PRIMARY_MCAST_IPADDR "224.0.1.129"
#define PTP_PDELAY_MCAST_IPADDR "224.0.0.107"

+struct udp {
+ struct transport t;
+ uint8_t mac[MAC_LEN];
+ int mac_len;
+};
+
static int mcast_bind(int fd, int index)
{
int err;
@@ -140,8 +149,14 @@ static struct in_addr mcast_addr[2];
static int udp_open(struct transport *t, char *name, struct fdarray *fda,
enum timestamp_type ts_type)
{
+ struct udp *udp = container_of(t, struct udp, t);
int efd, gfd;

+ udp->mac_len = 0;
+ if (sk_interface_macaddr(name, udp->mac, MAC_LEN) == 0) {
+ udp->mac_len = MAC_LEN;
+ }
+
if (!inet_aton(PTP_PRIMARY_MCAST_IPADDR, &mcast_addr[MC_PRIMARY]))
return -1;

@@ -211,34 +226,32 @@ static int udp_send(struct transport *t, struct fdarray *fda, int event, int pee

static void udp_release(struct transport *t)
{
- /* No need for any per-instance deallocation. */
+ struct udp *udp = container_of(t, struct udp, t);
+ free(udp);
}

int udp_physical_addr(struct transport *t, struct fdarray *fda, uint8_t *addr) {
- /* TODO: Appear to need interface name to get MAC address.
- * Need a per-instance transport to save the name in. */
- /*
- sk_interface_macaddr
- */
- return 0;
+ struct udp *udp = container_of(t, struct udp, t);
+ if (udp->mac_len)
+ memcpy(addr, udp->mac, udp->mac_len);
+ return udp->mac_len;
}

int udp_protocol_addr(struct transport *t, struct fdarray *fda, uint8_t *addr) {
return 0;
}

-static struct transport the_udp_transport = {
- .close = udp_close,
- .open = udp_open,
- .recv = udp_recv,
- .send = udp_send,
- .release = udp_release,
- .physical_addr = udp_physical_addr,
- .protocol_addr = udp_protocol_addr,
-};
-
struct transport *udp_transport_create(void)
{
- /* No need for any per-instance allocation. */
- return &the_udp_transport;
+ struct udp *udp = calloc(1, sizeof(*udp));
+ if (!udp)
+ return NULL;
+ udp->t.close = udp_close;
+ udp->t.open = udp_open;
+ udp->t.recv = udp_recv;
+ udp->t.send = udp_send;
+ udp->t.release = udp_release;
+ udp->t.physical_addr = udp_physical_addr;
+ udp->t.protocol_addr = udp_protocol_addr;
+ return &udp->t;
}
diff --git a/udp6.c b/udp6.c
index 130ba60..f52426f 100644
--- a/udp6.c
+++ b/udp6.c
@@ -32,6 +32,7 @@
#include "contain.h"
#include "print.h"
#include "sk.h"
+#include "ether.h"
#include "transport_private.h"
#include "udp6.h"

@@ -45,6 +46,8 @@ unsigned char udp6_scope = 0x0E;
struct udp6 {
struct transport t;
int index;
+ uint8_t mac[MAC_LEN];
+ int mac_len;
};

static int is_link_local(struct in6_addr *addr)
@@ -159,6 +162,11 @@ static int udp6_open(struct transport *t, char *name, struct fdarray *fda,
struct udp6 *udp6 = container_of(t, struct udp6, t);
int efd, gfd;

+ udp6->mac_len = 0;
+ if (sk_interface_macaddr(name, udp6->mac, MAC_LEN) == 0) {
+ udp6->mac_len = MAC_LEN;
+ }
+
if (1 != inet_pton(AF_INET6, PTP_PRIMARY_MCAST_IP6ADDR, &mc6_addr[MC_PRIMARY]))
return -1;

@@ -234,7 +242,10 @@ static void udp6_release(struct transport *t)
}

static int udp6_physical_addr(struct transport *t, struct fdarray *fda, uint8_t *addr) {
- return 0;
+ struct udp6 *udp6 = container_of(t, struct udp6, t);
+ if (udp6->mac_len)
+ memcpy(addr, udp6->mac, udp6->mac_len);
+ return udp6->mac_len;
}

static int udp6_protocol_addr(struct transport *t, struct fdarray *fda, uint8_t *addr) {
--
1.8.0.2
Geoff Salmon
2012-12-28 21:51:28 UTC
Permalink
---
.gitignore | 1 +
tests/TESTS | 1 +
tests/makefile | 3 +-
tests/unknown_tlv-t.c | 118 ++++++++++++++++++++++++++++++++++++++++++++++++++
4 files changed, 122 insertions(+), 1 deletion(-)
create mode 100644 tests/unknown_tlv-t.c

diff --git a/.gitignore b/.gitignore
index 0ed84ca..6f76ba3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -11,3 +11,4 @@
/tests/priority-t
/tests/description-t
/tests/faultlog-t
+/tests/unknown_tlv-t
diff --git a/tests/TESTS b/tests/TESTS
index 361cf3b..a5e8486 100644
--- a/tests/TESTS
+++ b/tests/TESTS
@@ -4,3 +4,4 @@ errors
priority
description
faultlog
+unknown_tlv
diff --git a/tests/makefile b/tests/makefile
index 318c011..208a267 100644
--- a/tests/makefile
+++ b/tests/makefile
@@ -21,7 +21,7 @@ INC = -I..
CFLAGS = -Wall -Werror $(INC) $(DEBUG)$(EXTRA_CFLAGS)
LDLIBS = $(EXTRA_LDFLAGS)

-PRG = runtests example-t childproc-t errors-t priority-t description-t faultlog-t
+PRG = runtests example-t childproc-t errors-t priority-t description-t faultlog-t unknown_tlv-t

OBJECTS = $(PRG:=.o) tap/basic.o testutil.o childproc.o mgmt.o
SRC = $(OBJECTS:.o=.c)
@@ -45,6 +45,7 @@ errors-t: errors-t.o $(TEST_OBJS)
priority-t: priority-t.o $(TEST_OBJS)
description-t: description-t.o $(TEST_OBJS)
faultlog-t: faultlog-t.o $(TEST_OBJS)
+unknown_tlv-t: unknown_tlv-t.o $(TEST_OBJS)

#%-t.o: %-t.c
# $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
diff --git a/tests/unknown_tlv-t.c b/tests/unknown_tlv-t.c
new file mode 100644
index 0000000..5c75421
--- /dev/null
+++ b/tests/unknown_tlv-t.c
@@ -0,0 +1,118 @@
+/**
+ * @file faultlog-t.c
+ * @brief Tests FaultLog packing and unpacking
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "tap/basic.h"
+#include "testutil.h"
+
+#include <string.h>
+
+#include "tlv.h"
+
+int test_write_read(struct tlv *tlv, struct tlv *recv_tlv,
+ uint8_t *buf, int len, const char *name)
+{
+ int result;
+ ok(tlv_write(tlv, buf, len-1) == -1,
+ "%s: write too small buf", name);
+
+ result = (test_tlv_roundtrip_buf(tlv, buf, len, recv_tlv) == len);
+ ok(result, "%s: read/write exact fit", name);
+ if (result)
+ ok(tlv_read(buf, len-1, recv_tlv) == 0,
+ "%s: read too small", name);
+ else
+ skip("%s, read too small", name);
+ result = (test_tlv_roundtrip(tlv, recv_tlv) == len);
+ ok(result, "%s: read/write", name);
+ return result;
+}
+
+void test_empty_unknown(struct tlv *tlv, struct tlv *recv_tlv,
+ uint8_t *buf, int len,
+ struct unknown_data *send_data,
+ struct unknown_data *recv_data,
+ const char *name)
+{
+ send_data->len = 0;
+ if (test_write_read(tlv, recv_tlv, buf, len, name))
+ ok(recv_data->len == 0, "%s: unknown lengths match", name);
+ else
+ skip("%s: write empty tlv failed", name);
+}
+
+void test_unknown(struct tlv *tlv, struct tlv *recv_tlv,
+ uint8_t *buf, int len,
+ struct unknown_data *send_data,
+ struct unknown_data *recv_data,
+ const char *name)
+{
+ uint32_t data = 0xBADBEEF;
+ send_data->len = sizeof(data);
+ send_data->data = NULL;
+ ok(tlv_write(tlv, buf, len) == -1, "%s: write with NULL data", name);
+ send_data->data = (Octet*)&data;
+ if (test_write_read(tlv, recv_tlv, buf, len, name)) {
+ ok(recv_data->len = sizeof(data), "%s: unknown lengths match", name);
+
+ ok(memcmp(send_data->data, recv_data->data, sizeof(data)) == 0,
+ "%s: unknown datas match", name);
+ } else
+ skip_block(2, "%s: write/read failed", name);
+}
+
+int main() {
+ plan(36);
+
+ struct tlv tlv, recv_tlv;
+ uint8_t buf[1500];
+ //int len;
+
+ /* empty unknown TLV */
+ memset(&tlv, 0, sizeof(tlv));
+ tlv.type = 0x3FFF;
+ test_empty_unknown(&tlv, &recv_tlv, buf, 4,
+ &tlv.unknown, &recv_tlv.unknown, "empty tlv");
+ test_unknown(&tlv, &recv_tlv, buf, 8,
+ &tlv.unknown, &recv_tlv.unknown, "tlv");
+
+ /* empty unknown Org TLV */
+ memset(&tlv, 0, sizeof(tlv));
+ tlv.type = TLV_ORGANIZATION_EXTENSION;
+ tlv.org.id = 0;
+ tlv.org.subtype = 0;
+ test_empty_unknown(&tlv, &recv_tlv, buf, 10,
+ &tlv.org.data.unknown,
+ &recv_tlv.org.data.unknown, "empty org tlv");
+ test_unknown(&tlv, &recv_tlv, buf, 14,
+ &tlv.org.data.unknown,
+ &recv_tlv.org.data.unknown, "org tlv");
+
+ /* empty unknown Management TLV */
+ memset(&tlv, 0, sizeof(tlv));
+ tlv.type = TLV_MANAGEMENT;
+ tlv.mgmt.id = 0xDFFF;
+ test_empty_unknown(&tlv, &recv_tlv, buf, 6,
+ &tlv.mgmt.data.unknown,
+ &recv_tlv.mgmt.data.unknown, "empty mgmt tlv");
+ test_unknown(&tlv, &recv_tlv, buf, 10,
+ &tlv.mgmt.data.unknown,
+ &recv_tlv.mgmt.data.unknown, "mgmt tlv");
+
+ return 0;
+}
--
1.8.0.2
Geoff Salmon
2012-12-28 21:51:25 UTC
Permalink
Also makes the enum transport_type reflect the specs networkProtocol
enumeration.
---
port.c | 15 ++++++++++-----
transport.c | 6 +++---
transport.h | 6 ++++--
3 files changed, 17 insertions(+), 10 deletions(-)

diff --git a/port.c b/port.c
index a5f52be..fcc9fe5 100644
--- a/port.c
+++ b/port.c
@@ -409,6 +409,9 @@ static int port_management_response(struct port *target, struct port *ingress,
struct ClockDescription *cdt;
struct clock_description *desc;

+ uint8_t physical_addr[TRANSPORT_ADDR_LEN];
+ uint8_t protocol_addr[TRANSPORT_ADDR_LEN];
+
rsp_tlv.type = TLV_MANAGEMENT;
rsp_tlv.mgmt.id = tlv->mgmt.id;
rsp_tlv.mgmt.empty_body = 0;
@@ -434,12 +437,14 @@ static int port_management_response(struct port *target, struct port *ingress,
* symbols. */
cdt->physicalLayerProtocol.length = 0;

- cdt->physicalAddress.addressField = NULL;
- cdt->physicalAddress.addressLength = 0;
+ cdt->physicalAddress.addressField = physical_addr;
+ cdt->physicalAddress.addressLength =
+ transport_physical_addr(target->trp, &target->fda, physical_addr);

- cdt->protocolAddress.networkProtocol = 0;
- cdt->protocolAddress.addressField = NULL;
- cdt->protocolAddress.addressLength = 0;
+ cdt->protocolAddress.networkProtocol = transport_type(target->trp);
+ cdt->protocolAddress.addressField = protocol_addr;
+ cdt->protocolAddress.addressLength =
+ transport_protocol_addr(target->trp, &target->fda, protocol_addr);

memcpy(cdt->manufacturerIdentity, desc->manufacturer_id, OUI_LEN);

diff --git a/transport.c b/transport.c
index c05fdb8..7d89d45 100644
--- a/transport.c
+++ b/transport.c
@@ -77,6 +77,9 @@ struct transport *transport_create(enum transport_type type)
{
struct transport *t = NULL;
switch (type) {
+ case TRANS_UDS:
+ t = uds_transport_create();
+ break;
case TRANS_UDP_IPV4:
t = udp_transport_create();
break;
@@ -90,9 +93,6 @@ struct transport *transport_create(enum transport_type type)
case TRANS_CONTROLNET:
case TRANS_PROFINET:
break;
- case TRANS_UDS:
- t = uds_transport_create();
- break;
}
if (t)
t->type = type;
diff --git a/transport.h b/transport.h
index c360734..2ab337d 100644
--- a/transport.h
+++ b/transport.h
@@ -25,14 +25,16 @@

#include "fd.h"

+/* Values from networkProtocol enumeration 7.4.1 Table 3 */
enum transport_type {
- TRANS_UDP_IPV4,
+ /* 0 is Reserved in spec. Use it for UDS */
+ TRANS_UDS = 0,
+ TRANS_UDP_IPV4 = 1,
TRANS_UDP_IPV6,
TRANS_IEEE_802_3,
TRANS_DEVICENET,
TRANS_CONTROLNET,
TRANS_PROFINET,
- TRANS_UDS,
};

/**
--
1.8.0.2
Geoff Salmon
2012-12-28 21:51:21 UTC
Permalink
Sends info in CLOCK_DESCRIPTION and USER_DESCRIPTION messages. Also
supports setting the USER_DESCRIPTION.
---
clock.c | 10 +++++++++-
port.c | 10 ++++++----
2 files changed, 15 insertions(+), 5 deletions(-)

diff --git a/clock.c b/clock.c
index 23f2beb..d647690 100644
--- a/clock.c
+++ b/clock.c
@@ -160,7 +160,7 @@ static int clock_management_response(struct clock *c, struct port *p,

switch ((int)tlv->mgmt.id) {
case MGMT_USER_DESCRIPTION:
- ptp_text_set(&data->cd.userDescription, "");
+ ptp_text_copy(&data->cd.userDescription, &c->desc.userDescription);
break;
case MGMT_FAULT_LOG:
/* TODO: should be gettable */
@@ -267,7 +267,15 @@ static int clock_management_set(struct clock *c, struct port *p,
struct tlv *tlv, struct ptp_message *req)
{
int result;
+ union ManagementTLVData *data = &tlv->mgmt.data;
switch ((int)tlv->mgmt.id) {
+ case MGMT_USER_DESCRIPTION:
+ if (static_ptp_text_copy(&c->desc.userDescription,
+ &data->cd.userDescription) != 0)
+ {
+ return -MGMT_ERROR_WRONG_VALUE;
+ }
+ break;
case MGMT_PRIORITY1:
/* TODO: should be settable */
return -MGMT_ERROR_NOT_SETABLE;
diff --git a/port.c b/port.c
index 5157b86..a5f52be 100644
--- a/port.c
+++ b/port.c
@@ -407,6 +407,7 @@ static int port_management_response(struct port *target, struct port *ingress,
struct tlv rsp_tlv;
union ManagementTLVData *data = &rsp_tlv.mgmt.data;
struct ClockDescription *cdt;
+ struct clock_description *desc;

rsp_tlv.type = TLV_MANAGEMENT;
rsp_tlv.mgmt.id = tlv->mgmt.id;
@@ -418,6 +419,7 @@ static int port_management_response(struct port *target, struct port *ingress,
break;
case MGMT_CLOCK_DESCRIPTION:
cdt = &data->cd;
+ desc = clock_description(c);
if (clock_num_ports(c) == 1) {
cdt->clockType = CLOCK_TYPE_ORDINARY;
} else {
@@ -439,11 +441,11 @@ static int port_management_response(struct port *target, struct port *ingress,
cdt->protocolAddress.addressField = NULL;
cdt->protocolAddress.addressLength = 0;

- memset(cdt->manufacturerIdentity, 0, OUI_LEN);
+ memcpy(cdt->manufacturerIdentity, desc->manufacturer_id, OUI_LEN);

- ptp_text_set(&cdt->productDescription, "");
- ptp_text_set(&cdt->revisionData, "");
- ptp_text_set(&cdt->userDescription, "");
+ ptp_text_copy(&cdt->productDescription, &desc->productDescription);
+ ptp_text_copy(&cdt->revisionData, &desc->revisionData);
+ ptp_text_copy(&cdt->userDescription, &desc->userDescription);

memset(cdt->profileIdentity, 0, sizeof(cdt->profileIdentity));
break;
--
1.8.0.2
Geoff Salmon
2012-12-28 21:51:27 UTC
Permalink
---
tests/mgmt.c | 3 ++-
tests/testutil.c | 21 +++++++++++++++++++++
tests/testutil.h | 22 ++++++++++++++++++++++
3 files changed, 45 insertions(+), 1 deletion(-)

diff --git a/tests/mgmt.c b/tests/mgmt.c
index f3d57f0..9556163 100644
--- a/tests/mgmt.c
+++ b/tests/mgmt.c
@@ -137,7 +137,8 @@ static void msg_init(struct mgmt_msg *m, uint8_t hops, enum management_actions a
memset(m, 0, sizeof(*m));
msg_hdr_init(&m->hdr);
/* use the wildcard clock identity by default */
- memset(&m->msg.targetPortIdentity.clockIdentity, 0xFF, CLOCK_IDENTITY_LENGTH);
+ memset(&m->msg.targetPortIdentity.clockIdentity, 0xFF,
+ sizeof(struct ClockIdentity));
/* use the wildcard port identity by default */
m->msg.targetPortIdentity.portNumber = 0xFFFF;
m->msg.startingBoundaryHops = hops;
diff --git a/tests/testutil.c b/tests/testutil.c
index 3bb3aa1..7484759 100644
--- a/tests/testutil.c
+++ b/tests/testutil.c
@@ -113,3 +113,24 @@ struct childproc *test_restart_childproc(const char *config) {
killchild();
return test_childproc(config);
}
+
+int test_tlv_roundtrip_buf(struct tlv *in, uint8_t *buf, int len, struct tlv *out) {
+ int write_len, read_len;
+ /* fill buffer with garbage to ensure values are overwritten */
+ memset(buf, 0xFE, len);
+ write_len = tlv_write(in, buf, len);
+ if (write_len < 0)
+ return TLV_WRITE_FAIL;
+
+ read_len = tlv_read(buf, write_len, out);
+ if (read_len < 0)
+ return TLV_READ_FAIL;
+ if (write_len != read_len)
+ return TLV_READ_LEN_FAIL;
+ return write_len;
+}
+
+int test_tlv_roundtrip(struct tlv *in, struct tlv *out) {
+ static uint8_t buf[1500];
+ return test_tlv_roundtrip_buf(in, buf, sizeof(buf), out);
+}
diff --git a/tests/testutil.h b/tests/testutil.h
index 797b602..bda9b25 100644
--- a/tests/testutil.h
+++ b/tests/testutil.h
@@ -27,6 +27,7 @@

#include "mgmt.h"
#include "childproc.h"
+#include "tlv.h"

struct sequence_ids {
UInteger16 mgmt;
@@ -39,4 +40,25 @@ void ptp_text_set(struct PTPText *t, const char *txt);

struct childproc *test_childproc(const char *config);
struct childproc *test_restart_childproc(const char *config);
+
+#define TLV_WRITE_FAIL -1
+#define TLV_READ_FAIL -2
+#define TLV_READ_LEN_FAIL -3
+
+/**
+ * Writes and then reads a tlv.
+ * @param in tlv to write
+ * @param buf buffer to use
+ * @param len length of the buffer
+ * @param out tlv will be read from buffer into this pointer
+ * @return On success returns the number of bytes that actually
+ * written to the buffer. On failure, returns -1 if the write
+ * failed, returns -2 if the read failed, returns -3 if the
+ * amount read did not match the amount written.
+ */
+int test_tlv_roundtrip_buf(struct tlv *in, uint8_t *buf, int len, struct tlv *out);
+
+/* Like test_tlv_roundtrip_buf but uses a static buffer of size 1500. */
+int test_tlv_roundtrip(struct tlv *in, struct tlv *out);
+
#endif
--
1.8.0.2
Geoff Salmon
2012-12-28 21:51:23 UTC
Permalink
These addresses are needed to fill the CLOCK_DESCRIPTION management
TLV. In this commit only the interface is added. The implementation
needs to be filled in for each of the transport types.
---
raw.c | 10 ++++++++++
transport.c | 37 ++++++++++++++++++++++++++++++++-----
transport.h | 24 ++++++++++++++++++++++++
transport_private.h | 7 +++++++
udp.c | 15 +++++++++++++++
udp6.c | 10 ++++++++++
uds.c | 11 +++++++++++
7 files changed, 109 insertions(+), 5 deletions(-)

diff --git a/raw.c b/raw.c
index 8e22be4..e7ba2f3 100644
--- a/raw.c
+++ b/raw.c
@@ -295,6 +295,14 @@ static void raw_release(struct transport *t)
free(raw);
}

+static int raw_physical_addr(struct transport *t, struct fdarray *fda, uint8_t *addr) {
+ return 0;
+}
+
+static int raw_protocol_addr(struct transport *t, struct fdarray *fda, uint8_t *addr) {
+ return 0;
+}
+
struct transport *raw_transport_create(void)
{
struct raw *raw;
@@ -306,5 +314,7 @@ struct transport *raw_transport_create(void)
raw->t.recv = raw_recv;
raw->t.send = raw_send;
raw->t.release = raw_release;
+ raw->t.physical_addr = raw_physical_addr;
+ raw->t.protocol_addr = raw_protocol_addr;
return &raw->t;
}
diff --git a/transport.c b/transport.c
index 713f018..c05fdb8 100644
--- a/transport.c
+++ b/transport.c
@@ -53,23 +53,50 @@ int transport_peer(struct transport *t, struct fdarray *fda, int event,
return t->send(t, fda, event, 1, buf, buflen, hwts);
}

+int transport_physical_addr(struct transport *t, struct fdarray *fda, uint8_t *addr)
+{
+ /* physical_addr is optional. Some transports may not have
+ one. */
+ if (t->physical_addr) {
+ return t->physical_addr(t, fda, addr);
+ }
+ return 0;
+}
+
+int transport_protocol_addr(struct transport *t, struct fdarray *fda, uint8_t *addr)
+{
+ return t->protocol_addr(t, fda, addr);
+}
+
+enum transport_type transport_type(struct transport *t)
+{
+ return t->type;
+}
+
struct transport *transport_create(enum transport_type type)
{
+ struct transport *t = NULL;
switch (type) {
case TRANS_UDP_IPV4:
- return udp_transport_create();
+ t = udp_transport_create();
+ break;
case TRANS_UDP_IPV6:
- return udp6_transport_create();
+ t = udp6_transport_create();
+ break;
case TRANS_IEEE_802_3:
- return raw_transport_create();
+ t = raw_transport_create();
+ break;
case TRANS_DEVICENET:
case TRANS_CONTROLNET:
case TRANS_PROFINET:
break;
case TRANS_UDS:
- return uds_transport_create();
+ t = uds_transport_create();
+ break;
}
- return NULL;
+ if (t)
+ t->type = type;
+ return t;
}

void transport_destroy(struct transport *t)
diff --git a/transport.h b/transport.h
index 03fcb79..c360734 100644
--- a/transport.h
+++ b/transport.h
@@ -21,6 +21,7 @@
#define HAVE_TRANSPORT_H

#include <time.h>
+#include <inttypes.h>

#include "fd.h"

@@ -73,6 +74,29 @@ int transport_peer(struct transport *t, struct fdarray *fda, int event,
void *buf, int buflen, struct hw_timestamp *hwts);

/**
+ * Returns the transport's type.
+ */
+enum transport_type transport_type(struct transport *t);
+
+#define TRANSPORT_ADDR_LEN 16
+
+/**
+ * Gets the transport's physical address. The address is written to
+ * the addr buffer and the length of the address is returned. Valid
+ * lengths are 0-16 and the caller must at ensure that the buffer
+ * pointed to by addr can hold 16 bytes.
+ */
+int transport_physical_addr(struct transport *t, struct fdarray *fda, uint8_t *addr);
+
+/**
+ * Gets the transport's protocol address. The address is written to
+ * the addr buffer and the length of the address is returned. Valid
+ * lengths are 0-16 and the caller must at ensure that the buffer
+ * pointed to by addr can hold 16 bytes.
+ */
+int transport_protocol_addr(struct transport *t, struct fdarray *fda, uint8_t *addr);
+
+/**
* Allocate an instance of the specified transport.
* @param type Which transport to obtain.
* @return Pointer to a transport instance on success, NULL otherwise.
diff --git a/transport_private.h b/transport_private.h
index 255035d..079de3d 100644
--- a/transport_private.h
+++ b/transport_private.h
@@ -26,6 +26,8 @@
#include "transport.h"

struct transport {
+ enum transport_type type;
+
int (*close)(struct transport *t, struct fdarray *fda);

int (*open)(struct transport *t, char *name, struct fdarray *fda,
@@ -38,6 +40,11 @@ struct transport {
void *buf, int buflen, struct hw_timestamp *hwts);

void (*release)(struct transport *t);
+
+ /* physical_addr is optional. May be NULL for some transports. */
+ int (*physical_addr)(struct transport *t, struct fdarray *fda, uint8_t *addr);
+
+ int (*protocol_addr)(struct transport *t, struct fdarray *fda, uint8_t *addr);
};

#endif
diff --git a/udp.c b/udp.c
index cf73411..7cfd26b 100644
--- a/udp.c
+++ b/udp.c
@@ -214,12 +214,27 @@ static void udp_release(struct transport *t)
/* No need for any per-instance deallocation. */
}

+int udp_physical_addr(struct transport *t, struct fdarray *fda, uint8_t *addr) {
+ /* TODO: Appear to need interface name to get MAC address.
+ * Need a per-instance transport to save the name in. */
+ /*
+ sk_interface_macaddr
+ */
+ return 0;
+}
+
+int udp_protocol_addr(struct transport *t, struct fdarray *fda, uint8_t *addr) {
+ return 0;
+}
+
static struct transport the_udp_transport = {
.close = udp_close,
.open = udp_open,
.recv = udp_recv,
.send = udp_send,
.release = udp_release,
+ .physical_addr = udp_physical_addr,
+ .protocol_addr = udp_protocol_addr,
};

struct transport *udp_transport_create(void)
diff --git a/udp6.c b/udp6.c
index 490a780..130ba60 100644
--- a/udp6.c
+++ b/udp6.c
@@ -233,6 +233,14 @@ static void udp6_release(struct transport *t)
free(udp6);
}

+static int udp6_physical_addr(struct transport *t, struct fdarray *fda, uint8_t *addr) {
+ return 0;
+}
+
+static int udp6_protocol_addr(struct transport *t, struct fdarray *fda, uint8_t *addr) {
+ return 0;
+}
+
struct transport *udp6_transport_create(void)
{
struct udp6 *udp6;
@@ -244,5 +252,7 @@ struct transport *udp6_transport_create(void)
udp6->t.recv = udp6_recv;
udp6->t.send = udp6_send;
udp6->t.release = udp6_release;
+ udp6->t.physical_addr = udp6_physical_addr;
+ udp6->t.protocol_addr = udp6_protocol_addr;
return &udp6->t;
}
diff --git a/uds.c b/uds.c
index 0d114c3..3b50ccd 100644
--- a/uds.c
+++ b/uds.c
@@ -114,6 +114,15 @@ static void uds_release(struct transport *t)
free(uds);
}

+static int uds_protocol_addr(struct transport *t, struct fdarray *fda, uint8_t *addr) {
+ int len = strlen(UDS_PATH);
+ if (len > TRANSPORT_ADDR_LEN) {
+ len = TRANSPORT_ADDR_LEN;
+ }
+ memcpy(addr, UDS_PATH, len);
+ return len;
+}
+
struct transport *uds_transport_create(void)
{
struct uds *uds;
@@ -125,6 +134,8 @@ struct transport *uds_transport_create(void)
uds->t.recv = uds_recv;
uds->t.send = uds_send;
uds->t.release = uds_release;
+ uds->t.physical_addr = NULL;
+ uds->t.protocol_addr = uds_protocol_addr;
return &uds->t;
}
--
1.8.0.2
Richard Cochran
2012-12-31 22:24:18 UTC
Permalink
Post by Geoff Salmon
These addresses are needed to fill the CLOCK_DESCRIPTION management
TLV. In this commit only the interface is added. The implementation
needs to be filled in for each of the transport types.
I understand the need for this patch (and the two following), and I
agree with the general idea of how you implemented it, but I would
need the patches should be reordered to be easier to review.

Something like:

1. add the address method to the transport code
2. implement the new method in udp
3. implement the new method in udp6
4. implement the new method in raw
5. implement the new method in uds
6. finally, call the transport method from the upper layer code.

Thanks,
Richard
Geoff Salmon
2012-12-28 21:51:15 UTC
Permalink
This post might be inappropriate. Click to display it.
Richard Cochran
2012-12-31 21:24:15 UTC
Permalink
Post by Geoff Salmon
Adds structs and pack/unpack functions for most non-optional TLVs and
management messages. The tlv structs no longer reflect the on-the-wire
representation of the messages. Instead code in tlv.c and the various
*_pack.h files pack and unpack the message fields into the appropriate
struct fields.
Most of the GET management messages are implemented. The SET and
COMMAND messages still need to be handled.
Supporting getting and printing the new management messages in pmc.
So how to break this (and the other patches) into reasonably small pieces?

- Each new header or .c/.h pair should go into its own patch, before
the code is actually used. Follow the patch or patches with brand
new code with the (hopefully minimal) additions to the existing code
that use the new functions.

- It would help to have a bit more description or introduction
justifying each change.

- Work from the bottom up, in small steps, so that it is really easy
to follow how your logic is built. Don't add the higher level code
with a TODO in the lower level, and then remove the TODO in a later
patch. That is backwards and really annoying to review.

Since this patch is obviously way too big, I can't really offer a
thorough review. Here are comments on a few spots that did catch my
eye.
Post by Geoff Salmon
---
clock.c | 462 +++++++++++++++++++++++-----------
clock.h | 11 +-
config.c | 20 +-
config.h | 2 +-
ddt.h | 54 +++-
ddt_pack.h | 177 +++++++++++++
ds.h | 38 ++-
msg.c | 91 ++++---
msg.h | 60 ++++-
pdt.h | 13 +
pdt_pack.h | 161 ++++++++++++
pmc.c | 473 +++++++++++++++++++++++++----------
port.c | 586 ++++++++++++++++++++++++-------------------
port.h | 22 +-
ptp4l.c | 29 ++-
tlv.c | 821 ++++++++++++++++++++++++++++++++++++++++++++++---------------
tlv.h | 495 ++++++++++++++++++++++++++-----------
tlv_pack.h | 544 ++++++++++++++++++++++++++++++++++++++++
util.c | 42 ++++
util.h | 30 +++
20 files changed, 3158 insertions(+), 973 deletions(-)
create mode 100644 ddt_pack.h
create mode 100644 pdt_pack.h
create mode 100644 tlv_pack.h
diff --git a/clock.c b/clock.c
index 39f378e..1aafd5f 100644
--- a/clock.c
+++ b/clock.c
@@ -57,7 +57,7 @@ struct clock {
struct defaultDS dds;
struct dataset default_dataset;
struct currentDS cur;
- struct parent_ds dad;
+ struct parentDS dad;
I really don't see the need to change these. You can add code for the
currently unimplemented TLVs without rewriting what is alreaady in
place.
Post by Geoff Salmon
struct timePropertiesDS tds;
struct ClockIdentity ptl[PATH_TRACE_MAX];
struct foreign_clock *best;
@@ -67,8 +67,6 @@ struct clock {
int fault_fd[CLK_N_PORTS];
time_t fault_timeout;
int nports; /* does not include the UDS port */
- int free_running;
- int freq_est_interval;
Can't see any improvement here. In any case, this sort of change
deserves its own patch series.
Post by Geoff Salmon
int utc_timescale;
tmv_t master_offset;
tmv_t path_delay;
@@ -98,6 +96,7 @@ void clock_destroy(struct clock *c)
port_close(c->port[i]);
close(c->fault_fd[i]);
}
+
port_close(c->port[i]); /*uds*/
if (c->clkid != CLOCK_REALTIME) {
phc_close(c->clkid);
@@ -108,6 +107,16 @@ void clock_destroy(struct clock *c)
msg_cleanup();
}
+static int clock_management_error(struct PortIdentity pid, struct port *ingress,
+ struct ptp_message *req, struct tlv *req_tlv,
+ enum management_error_types error_id)
+{
+ int result = port_management_error(pid, ingress, req, req_tlv, error_id);
+ if (result)
+ pr_err("clock: management error failed: %d", error_id);
+ return result;
+}
+
This new helper function, and the treatment of port_management_error
and port_management_error_port really need their own patch series.
Post by Geoff Salmon
static int clock_fault_timeout(struct clock *c, int index, int set)
{
struct itimerspec tmo = {
@@ -130,46 +139,103 @@ static void clock_freq_est_reset(struct clock *c)
c->fest.count = 0;
};
-static int clock_management_get_response(struct clock *c, struct port *p,
- int id, struct ptp_message *req)
Please don't change the basic functional structure. Try to work within
it first. If it really needs to change, then offer a patch just for
that, and tell us why its needed in the patch's description.

[ The idea is to have one function to handle GET, and one for SET. ]
Post by Geoff Salmon
+/**
+ * negative management error code < 0 for an invalid message
+ */
+static int clock_management_response(struct clock *c, struct port *p,
+ struct tlv *tlv, struct ptp_message *req)
{
- int datalen = 0, err, pdulen, respond = 0;
- struct management_tlv *tlv;
- struct ptp_message *rsp;
struct time_status_np *tsn;
- struct PortIdentity pid = port_identity(p);
-
- rsp = port_management_reply(pid, p, req);
- if (!rsp) {
- return 0;
- }
- tlv = (struct management_tlv *) rsp->management.suffix;
- tlv->type = TLV_MANAGEMENT;
- tlv->id = id;
-
- switch (id) {
- memcpy(tlv->data, &c->dds, sizeof(c->dds));
- datalen = sizeof(c->dds);
- respond = 1;
+ struct tlv rsp_tlv;
+ struct timespec timespec;
+ union ManagementTLVData *data = &rsp_tlv.mgmt.data;
+ struct Timestamp *time;
+
+ rsp_tlv.type = TLV_MANAGEMENT;
+ rsp_tlv.mgmt.id = tlv->mgmt.id;
+ rsp_tlv.mgmt.empty_body = 0;
+ rsp_tlv.mgmt.error = 0;
+
+ switch ((int)tlv->mgmt.id) {
Why the cast?
Post by Geoff Salmon
+ ptp_text_set(&data->cd.userDescription, "");
+ break;
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
+ data->dds = c->dds;
+ break;
+ data->cds = c->cur;
break;
- memcpy(tlv->data, &c->cur, sizeof(c->cur));
- datalen = sizeof(c->cur);
- respond = 1;
+ data->parent_ds = c->dad;
break;
- memcpy(tlv->data, &c->dad.pds, sizeof(c->dad.pds));
- datalen = sizeof(c->dad.pds);
- respond = 1;
+ data->tpds = c->tds;
break;
- memcpy(tlv->data, &c->tds, sizeof(c->tds));
- datalen = sizeof(c->tds);
- respond = 1;
+ data->dds = c->dds;
+ break;
+ /* TODO: Fix me. This probably isn't the correct time to return. */
You can sure say that again. When in doubt, leave it out.
Post by Geoff Salmon
+ if (clock_gettime(CLOCK_REALTIME, &timespec) != 0) {
+ return -MGMT_ERROR_GENERAL_ERROR;
+ }
+ time = &data->time.currentTime;
+ time->seconds_msb = (uint32_t)((timespec.tv_sec >> 32) & 0xFFFFFFFF);
+ time->seconds_lsb = (uint32_t)(timespec.tv_sec & 0xFFFFFFFF);
+ time->nanoseconds = timespec.tv_nsec;
break;
- tsn = (struct time_status_np *) tlv->data;
+ data->ca.clockAccuracy = c->dds.clockQuality.clockAccuracy;
+ break;
+ data->tpds = c->tds;
+ break;
+ data->path.num = c->dad.path_length;
+ data->path.list = c->dad.ptl;
+ break;
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
This string of identical cases is gross. Better would be to leave the
IDs that don't apply for a particular action as a default.
Post by Geoff Salmon
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
+ tsn = &data->tsn;
tsn->master_offset = c->master_offset;
tsn->ingress_time = tmv_to_nanoseconds(c->t2);
tsn->cumulativeScaledRateOffset =
@@ -178,29 +244,87 @@ static int clock_management_get_response(struct clock *c, struct port *p,
tsn->scaledLastGmPhaseChange = c->status.scaledLastGmPhaseChange;
tsn->gmTimeBaseIndicator = c->status.gmTimeBaseIndicator;
tsn->lastGmPhaseChange = c->status.lastGmPhaseChange;
- if (cid_eq(&c->dad.pds.grandmasterIdentity, &c->dds.clockIdentity))
+ if (cid_eq(&c->dad.grandmasterIdentity, &c->dds.clockIdentity))
tsn->gmPresent = 0;
else
tsn->gmPresent = 1;
- tsn->gmIdentity = c->dad.pds.grandmasterIdentity;
- datalen = sizeof(*tsn);
- respond = 1;
+ tsn->gmIdentity = c->dad.grandmasterIdentity;
break;
+ return 0;
}
- if (respond) {
- tlv->length = sizeof(tlv->id) + datalen;
- pdulen = rsp->header.messageLength + sizeof(*tlv) + datalen;
- rsp->header.messageLength = pdulen;
- rsp->tlv_count = 1;
- err = msg_pre_send(rsp);
- if (err) {
- goto out;
- }
- err = port_forward(p, rsp, pdulen);
+ if (port_management_reply_send(p, port_identity(p), req, &rsp_tlv))
+ pr_err("failed to send management response");
+ return 1;
+}
+
+/**
+ * negative management error code < 0 for an invalid message
+ */
+static int clock_management_set(struct clock *c, struct port *p,
+ struct tlv *tlv, struct ptp_message *req)
+{
+ int result;
+ switch ((int)tlv->mgmt.id) {
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ return 0;
+ }
+ /* Set handled. Send response. */
+ if ((result = clock_management_response(c, p, tlv, req)) != 1) {
+ pr_err("clock: Management SET %d handled but RESPONSE failed", tlv->mgmt.id);
+ if (result == 0)
+ result = 1;
}
- msg_put(rsp);
- return respond ? 1 : 0;
+ return result;
}
static int clock_master_lost(struct clock *c)
@@ -325,39 +449,43 @@ static void clock_step(clockid_t clkid, int64_t ns)
static void clock_update_grandmaster(struct clock *c)
{
- struct parentDS *pds = &c->dad.pds;
memset(&c->cur, 0, sizeof(c->cur));
memset(c->ptl, 0, sizeof(c->ptl));
- pds->parentPortIdentity.clockIdentity = c->dds.clockIdentity;
- pds->parentPortIdentity.portNumber = 0;
- pds->grandmasterIdentity = c->dds.clockIdentity;
- pds->grandmasterClockQuality = c->dds.clockQuality;
- pds->grandmasterPriority1 = c->dds.priority1;
- pds->grandmasterPriority2 = c->dds.priority2;
+ c->dad.parentPortIdentity.clockIdentity = c->dds.clockIdentity;
+ c->dad.parentPortIdentity.portNumber = 0;
+ c->dad.grandmasterIdentity = c->dds.clockIdentity;
+ c->dad.grandmasterClockQuality = c->dds.clockQuality;
+ c->dad.grandmasterPriority1 = c->dds.priority1;
+ c->dad.grandmasterPriority2 = c->dds.priority2;
c->dad.path_length = 0;
c->tds.currentUtcOffset = CURRENT_UTC_OFFSET;
- if (c->utc_timescale) {
- c->tds.flags = 0;
- } else {
- c->tds.flags = PTP_TIMESCALE;
- }
+ c->tds.currentUtcOffsetValid = FALSE;
+ c->tds.leap61 = FALSE;
+ c->tds.leap59 = FALSE;
+ c->tds.timeTraceable = FALSE;
+ c->tds.frequencyTraceable = FALSE;
+ c->tds.ptpTimescale = c->utc_timescale ? FALSE : TRUE;
All this mucking around with the data set definitions has nothing to
do with adding missing TLVS. Even if it were necessary to change them,
then you would need to do it in a series of patches, one by one, without
introducing any new code at the same time.
Post by Geoff Salmon
c->tds.timeSource = INTERNAL_OSCILLATOR;
}
static void clock_update_slave(struct clock *c)
{
- struct parentDS *pds = &c->dad.pds;
struct ptp_message *msg = TAILQ_FIRST(&c->best->messages);
c->cur.stepsRemoved = 1 + c->best->dataset.stepsRemoved;
- pds->parentPortIdentity = c->best->dataset.sender;
- pds->grandmasterIdentity = msg->announce.grandmasterIdentity;
- pds->grandmasterClockQuality = msg->announce.grandmasterClockQuality;
- pds->grandmasterPriority1 = msg->announce.grandmasterPriority1;
- pds->grandmasterPriority2 = msg->announce.grandmasterPriority2;
+ c->dad.parentPortIdentity = c->best->dataset.sender;
+ c->dad.grandmasterIdentity = msg->announce.grandmasterIdentity;
+ c->dad.grandmasterClockQuality = msg->announce.grandmasterClockQuality;
+ c->dad.grandmasterPriority1 = msg->announce.grandmasterPriority1;
+ c->dad.grandmasterPriority2 = msg->announce.grandmasterPriority2;
c->tds.currentUtcOffset = msg->announce.currentUtcOffset;
- c->tds.flags = msg->header.flagField[1];
+ c->tds.currentUtcOffsetValid = field_is_set(msg, 1, UTC_OFF_VALID);
+ c->tds.leap61 = field_is_set(msg, 1, LEAP_61);
+ c->tds.leap59 = field_is_set(msg, 1, LEAP_59);
+ c->tds.timeTraceable = field_is_set(msg, 1, TIME_TRACEABLE);
+ c->tds.frequencyTraceable = field_is_set(msg, 1, FREQ_TRACEABLE);
+ c->tds.ptpTimescale = field_is_set(msg, 1, PTP_TIMESCALE);
This is going backwards, to how I had the code before.
Post by Geoff Salmon
c->tds.timeSource = msg->announce.timeSource;
- if (!(c->tds.flags & PTP_TIMESCALE)) {
+ if (!c->tds.ptpTimescale) {
pr_warning("foreign master not using PTP timescale");
}
if (c->tds.currentUtcOffset < CURRENT_UTC_OFFSET) {
@@ -370,9 +498,9 @@ static void clock_utc_correct(struct clock *c)
struct timespec offset;
if (!c->utc_timescale)
return;
- if (!(c->tds.flags & PTP_TIMESCALE))
+ if (!c->tds.ptpTimescale)
return;
- if (c->tds.flags & UTC_OFF_VALID && c->tds.flags & TIME_TRACEABLE) {
+ if (c->tds.currentUtcOffsetValid && c->tds.timeTraceable) {
offset.tv_sec = c->tds.currentUtcOffset;
} else if (c->tds.currentUtcOffset > CURRENT_UTC_OFFSET) {
offset.tv_sec = c->tds.currentUtcOffset;
@@ -411,7 +539,7 @@ UInteger8 clock_class(struct clock *c)
}
struct clock *clock_create(int phc_index, struct interface *iface, int count,
- enum timestamp_type timestamping, struct default_ds *dds,
+ enum timestamp_type timestamping, struct defaultDS *ds,
enum servo_type servo)
{
int i, fadj = 0, max_adj = 0.0, sw_ts = timestamping == TS_SOFTWARE ? 1 : 0;
@@ -428,10 +556,7 @@ struct clock *clock_create(int phc_index, struct interface *iface, int count,
if (c->nports)
clock_destroy(c);
- c->free_running = dds->free_running;
- c->freq_est_interval = dds->freq_est_interval;
-
- if (c->free_running) {
+ if (c->dds.free_running) {
c->clkid = CLOCK_INVALID;
} else if (phc_index >= 0) {
snprintf(phc, 31, "/dev/ptp%d", phc_index);
@@ -465,13 +590,13 @@ struct clock *clock_create(int phc_index, struct interface *iface, int count,
return NULL;
}
- c->dds = dds->dds;
+ c->dds = *ds;
/* Initialize the parentDS. */
clock_update_grandmaster(c);
- c->dad.pds.parentStats = 0;
- c->dad.pds.observedParentOffsetScaledLogVariance = 0xffff;
- c->dad.pds.observedParentClockPhaseChangeRate = 0x7fffffff;
+ c->dad.parentStats = 0;
+ c->dad.observedParentOffsetScaledLogVariance = 0xffff;
+ c->dad.observedParentClockPhaseChangeRate = 0x7fffffff;
c->dad.ptl = c->ptl;
for (i = 0; i < ARRAY_SIZE(c->pollfd); i++) {
@@ -577,28 +702,95 @@ void clock_install_fda(struct clock *c, struct port *p, struct fdarray fda)
}
}
+/**
+ * management error code < 0 for an invalid message
+ */
+static int clock_management_validate(struct ptp_message *msg) {
+ int result;
+ uint8_t action = management_action(msg);
+ struct tlv *tlv = msg->tlv;
+ if (tlv == NULL || tlv->type != TLV_MANAGEMENT) {
+ return 0;
+ }
+
+ if ((result = tlv_check_mgmt_action(action, tlv->mgmt.id)) != 1) {
+ return result;
+ }
+
+ /* Received TLVs may have errors or empty data fields. Check
+ * flags and return errors if appropriate for the action.
+ * Errors messages are only sent for GET, SET, and COMMAND
+ * messges.
+ */
+ switch (action) {
+ if (tlv->mgmt.error) return -tlv->mgmt.error;
+ /* All GETs should be empty */
+ if (!tlv->mgmt.empty_body) {
+ return -MGMT_ERROR_WRONG_LENGTH;
+ }
+ break;
+ if (tlv->mgmt.error) return -tlv->mgmt.error;
+ /* All SETs except NULL_MANAGEMENT should have a
+ body */
+ if (tlv->mgmt.empty_body &&
+ tlv->mgmt.id != MGMT_NULL_MANAGEMENT)
+ {
+ return -MGMT_ERROR_WRONG_LENGTH;
+ }
Poor coding style here. (We are using kernel style).
Post by Geoff Salmon
+ break;
+ if (tlv->mgmt.error) return 0;
+ /* All RESPONSEs except NULL_MANAGEMENT should have a
+ body */
+ if (tlv->mgmt.empty_body &&
+ tlv->mgmt.id != MGMT_NULL_MANAGEMENT)
+ {
+ return 0;
+ }
And here.
Post by Geoff Salmon
+ break;
+ if (tlv->mgmt.error) return -tlv->mgmt.error;
+ break;
+ if (tlv->mgmt.error) return 0;
+ break;
+ /* unknown action */
+ return 0;
+ }
+ return 1;
+}
+
void clock_manage(struct clock *c, struct port *p, struct ptp_message *msg)
{
- int i, pdulen;
+ int i, result;
struct port *fwd;
- struct management_tlv *mgt;
+ struct tlv *tlv;
struct PortIdentity pid;
struct ClockIdentity *tcid, wildcard = {
{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
};
+ uint8_t action = management_action(msg);
+ if ((result = clock_management_validate(msg)) != 1) {
+ if (result < 0)
+ clock_management_error(pid, p, msg, msg->tlv, -result);
+ return;
+ }
/* Forward this message out all eligible ports. */
if (forwarding(c, p) && msg->management.boundaryHops) {
- pdulen = msg->header.messageLength;
msg->management.boundaryHops--;
msg_pre_send(msg);
for (i = 0; i < c->nports + 1; i++) {
fwd = c->port[i];
if (fwd != p && forwarding(c, fwd) &&
- port_forward(fwd, msg, pdulen))
+ port_forward(fwd, msg, msg->total_msg_len))
pr_err("port %d: management forward failed", i);
}
- msg_post_recv(msg, pdulen);
+ msg_post_recv(msg, msg->total_msg_len);
msg->management.boundaryHops++;
}
@@ -607,73 +799,51 @@ void clock_manage(struct clock *c, struct port *p, struct ptp_message *msg)
if (!cid_eq(tcid, &wildcard) && !cid_eq(tcid, &c->dds.clockIdentity)) {
return;
}
- if (msg->tlv_count != 1) {
- return;
- }
- mgt = (struct management_tlv *) msg->management.suffix;
- switch (management_action(msg)) {
- if (clock_management_get_response(c, p, mgt->id, msg))
- return;
Here I had "get_response" ...
Post by Geoff Salmon
+ tlv = msg->tlv;
+
+ result = 1; /* ignore RESPONSE and ACKNOWLEDGE msgs */
+ switch (action) {
+ result = clock_management_response(c, p, tlv, msg);
+ break;
+ result = clock_management_set(c, p, tlv, msg);
... and so this function should be called "set_response".
Post by Geoff Salmon
break;
+ result = -MGMT_ERROR_NOT_SUPPORTED;
break;
- /* Ignore responses from other nodes. */
+ }
+
+ if (result) {
+ if (result < 0)
+ clock_management_error(pid, p, msg, tlv, -result);
return;
}
- switch (mgt->id) {
- pid = port_identity(p);
- if (port_managment_error(pid, p, msg, NOT_SUPPORTED))
- pr_err("failed to send management error status");
- break;
Don't see why this should be removed.
Post by Geoff Salmon
- for (i = 0; i < c->nports; i++) {
- if (port_manage(c->port[i], p, msg))
- break;
+ /* TLV was not handled by clock. Some manangement messages are
+ applicable to a specific port or all ports. Give each
+ non-UDS port a chance to process the message. */
+ int handled = 0;
+ for (i = 0; i < c->nports; i++) {
+ if ((result = port_manage(c->port[i], p, msg))) {
+ if (result < 0)
+ return;
+ handled = 1;
}
- break;
}
+ if (!handled)
+ clock_management_error(pid, p, msg, tlv, MGMT_ERROR_NO_SUCH_ID);
}
-struct parent_ds *clock_parent_ds(struct clock *c)
+struct parentDS *clock_parent_ds(struct clock *c)
{
return &c->dad;
}
struct PortIdentity clock_parent_identity(struct clock *c)
{
- return c->dad.pds.parentPortIdentity;
+ return c->dad.parentPortIdentity;
}
int clock_poll(struct clock *c)
@@ -801,7 +971,7 @@ void clock_remove_fda(struct clock *c, struct port *p, struct fdarray fda)
int clock_slave_only(struct clock *c)
{
- return c->dds.flags & DDS_SLAVE_ONLY;
+ return c->dds.slaveOnly;
}
UInteger16 clock_steps_removed(struct clock *c)
@@ -841,7 +1011,7 @@ enum servo_state clock_synchronize(struct clock *c,
if (!c->path_delay)
return state;
- if (c->free_running)
+ if (c->dds.free_running)
return clock_no_adjust(c);
adj = servo_sample(c->servo, c->master_offset, ingress, &state);
@@ -866,7 +1036,7 @@ enum servo_state clock_synchronize(struct clock *c,
void clock_sync_interval(struct clock *c, int n)
{
- int shift = c->freq_est_interval - n;
+ int shift = c->dds.freq_est_interval - n;
if (shift < 0)
shift = 0;
@@ -936,3 +1106,7 @@ static void handle_state_decision_event(struct clock *c)
port_dispatch(c->port[i], event, fresh_best);
}
}
+
+int clock_num_ports(struct clock *c) {
+ return c->nports;
+}
diff --git a/clock.h b/clock.h
index e815a49..b88daf1 100644
--- a/clock.h
+++ b/clock.h
@@ -65,12 +65,12 @@ UInteger8 clock_class(struct clock *c);
*/
struct clock *clock_create(int phc_index, struct interface *iface, int count,
- enum timestamp_type timestamping, struct default_ds *dds,
+ enum timestamp_type timestamping, struct defaultDS *ds,
enum servo_type servo);
/**
@@ -128,7 +128,7 @@ void clock_manage(struct clock *c, struct port *p, struct ptp_message *msg);
*/
-struct parent_ds *clock_parent_ds(struct clock *c);
+struct parentDS *clock_parent_ds(struct clock *c);
/**
* Obtain the parent port identity from a clock's parent data set.
@@ -215,4 +215,9 @@ void clock_sync_interval(struct clock *c, int n);
*/
struct timePropertiesDS *clock_time_properties(struct clock *c);
+/**
+ * Returns the number of ports, not including UDS port, in the clock.
+ */
+int clock_num_ports(struct clock *c);
+
#endif
diff --git a/config.c b/config.c
index a4a6261..41cc041 100644
--- a/config.c
+++ b/config.c
@@ -22,6 +22,7 @@
#include "config.h"
#include "ether.h"
#include "print.h"
+#include "util.h"
enum config_section {
GLOBAL_SECTION,
@@ -158,7 +159,7 @@ static enum parser_result parse_global_setting(const char *option,
UInteger8 u8;
unsigned char mac[MAC_LEN];
- struct defaultDS *dds = &cfg->dds.dds;
+ struct defaultDS *dds = &cfg->dds;
struct port_defaults *pod = &cfg->pod;
enum parser_result r;
@@ -170,20 +171,13 @@ static enum parser_result parse_global_setting(const char *option,
if (!strcmp(option, "twoStepFlag")) {
if (1 != sscanf(value, "%d", &val))
return BAD_VALUE;
- if (val)
- dds->flags |= DDS_TWO_STEP_FLAG;
- else
- dds->flags &= ~DDS_TWO_STEP_FLAG;
+ dds->twoStepFlag = val ? 1 : 0;
} else if (!strcmp(option, "slaveOnly")) {
if (1 != sscanf(value, "%d", &val))
return BAD_VALUE;
- if (!(cfg_ignore & CFG_IGNORE_SLAVEONLY)) {
- if (val)
- dds->flags |= DDS_SLAVE_ONLY;
- else
- dds->flags &= ~DDS_SLAVE_ONLY;
- }
+ if (!(cfg_ignore & CFG_IGNORE_SLAVEONLY))
+ dds->slaveOnly = val ? 1 : 0;
} else if (!strcmp(option, "priority1")) {
if (1 != sscanf(value, "%hhu", &u8))
@@ -219,12 +213,12 @@ static enum parser_result parse_global_setting(const char *option,
} else if (!strcmp(option, "free_running")) {
if (1 != sscanf(value, "%d", &val))
return BAD_VALUE;
- cfg->dds.free_running = val ? 1 : 0;
+ dds->free_running = val ? 1 : 0;
} else if (!strcmp(option, "freq_est_interval")) {
if (1 != sscanf(value, "%d", &val) || !(val >= 0))
return BAD_VALUE;
- cfg->dds.freq_est_interval = val;
+ dds->freq_est_interval = val;
pod->freq_est_interval = val;
} else if (!strcmp(option, "assume_two_step")) {
diff --git a/config.h b/config.h
index 497d683..5ceba9d 100644
--- a/config.h
+++ b/config.h
@@ -58,7 +58,7 @@ struct config {
enum transport_type transport;
enum delay_mechanism dm;
- struct default_ds dds;
+ struct defaultDS dds;
struct port_defaults pod;
int *assume_two_step;
int *tx_timestamp_retries;
diff --git a/ddt.h b/ddt.h
index f142f8e..d8a0e1b 100644
--- a/ddt.h
+++ b/ddt.h
@@ -20,14 +20,19 @@
#ifndef HAVE_DDT_H
#define HAVE_DDT_H
+#include <sys/queue.h>
+
#include "pdt.h"
#define PACKED __attribute__((packed))
+#define CLOCK_IDENTITY_LENGTH 8
+
typedef Integer64 TimeInterval; /* nanoseconds << 16 */
/** On the wire time stamp format. */
struct Timestamp {
+ /*UInteger48 secondsField;*/
This comment is rather useless.
Post by Geoff Salmon
uint16_t seconds_msb; /* 16 bits + */
uint32_t seconds_lsb; /* 32 bits = 48 bits*/
UInteger32 nanoseconds;
@@ -40,18 +45,21 @@ struct timestamp {
};
struct ClockIdentity {
- Octet id[8];
-};
+ Octet id[CLOCK_IDENTITY_LENGTH];
+} PACKED;
struct PortIdentity {
struct ClockIdentity clockIdentity;
UInteger16 portNumber;
} PACKED;
+struct PortIdentity;
+
+#define MAX_PORT_ADDR_LEN 16
struct PortAddress {
Enumeration16 networkProtocol;
UInteger16 addressLength;
- Octet *address;
+ OctetPtr addressField;
Please, no Ptr types, this isn't windows.
Post by Geoff Salmon
};
struct ClockQuality {
@@ -60,15 +68,37 @@ struct ClockQuality {
UInteger16 offsetScaledLogVariance;
} PACKED;
-struct TLV {
- Enumeration16 type;
- UInteger16 length; /* must be even */
- Octet value[0];
-} PACKED;
-
struct PTPText {
UInteger8 length;
- Octet *text;
+ Octet *text; /* May not be null-terminated */
+};
+
+/* A StaticPTPText is like a PTPText but includes space to store the
+ * text inside the struct. The text array must always be
+ * null-terminated. Also tracks a maximum number of symbols. Note in
+ * UTF-8, # symbols != # bytes.
+ */
+#define MAX_PTP_OCTETS 255
+struct StaticPTPText {
+ /* null-terminated array of UTF-8 symbols */
+ Octet text[MAX_PTP_OCTETS + 1];
+ /* number of used bytes in text, not including trailing
+ * null */
+ int length;
+ /* max number of UTF-8 symbols that can be in text */
+ int max_symbols;
+};
+
+enum fault_severity {
+ FAULT_EMERGENCY = 0,
+ FAULT_ALERT,
+ FAULT_CRITICAL,
+ FAULT_ERROR,
+ FAULT_WARNING,
+ FAULT_NOTICE,
+ FAULT_INFORMATIONAL,
+ FAULT_DEBUG,
+ /* recerved 0x08 - 0xFF */
};
struct FaultRecord {
@@ -78,6 +108,10 @@ struct FaultRecord {
struct PTPText faultName;
struct PTPText faultValue;
struct PTPText faultDescription;
+
+ TAILQ_ENTRY(FaultRecord) entry;
};
+TAILQ_HEAD(FaultRecordList, FaultRecord);
+
This really should be attached to the clock instance. I made an effort
to make it possible to have multiple clock instances, even though it
isn't the normal use case. For example, it could happen when making a
simulation in one addresses space.
Post by Geoff Salmon
#endif
diff --git a/ddt_pack.h b/ddt_pack.h
new file mode 100644
index 0000000..77f9731
--- /dev/null
+++ b/ddt_pack.h
@@ -0,0 +1,177 @@
+/**
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef HAVE_DDT_PACK_H
+#define HAVE_DDT_PACK_H
+
+#include "pdt_pack.h"
+
+static inline int packTimestamp(const struct Timestamp *data, Octet *buf) {
Regarding coding style: we are using the kernel style, but with the
exception of leaving the UpperCamelCase and lowerCamelCase when it
makes a direct connection to the text of the standards.

Sorry that this isn't really immediately clear in the code. I will add
a coding style doc to the project.
Post by Geoff Salmon
+ int offset = 0;
+
+ packUInteger16(&data->seconds_msb, buf);
+ packUInteger32(&data->seconds_lsb, buf + 2);
+ /*packUInteger48(&data->secondsField, buf + offset);*/
This comment makes it look like you forgot to implement packUInteger48.
Post by Geoff Salmon
+ offset += 6;
+ packUInteger32(&data->nanoseconds, buf + offset);
+ offset += 4;
+ return offset;
+}
+
+static inline int unpackTimestamp(const Octet *buf, struct Timestamp *data) {
+ int offset = 0;
+ unpackUInteger16(buf, &data->seconds_msb);
+ unpackUInteger32(buf + 2, &data->seconds_lsb);
+ /*unpackUInteger48(buf + offset, &data->secondsField);*/
+ offset += 6;
+ unpackUInteger32(buf + offset, &data->nanoseconds);
+ offset += 4;
+ return offset;
+}
+
+static inline int packClockIdentity(const struct ClockIdentity *data, Octet *buf) {
+ int offset = 0;
+ packOctetArray(data->id, buf + offset, 8);
+ offset += 8;
+ return offset;
+}
+
+static inline int unpackClockIdentity(const Octet *buf, struct ClockIdentity *data) {
+ int offset = 0;
+ unpackOctetArray(buf + offset, data->id, 8);
+ offset += 8;
+ return offset;
+}
+
+static inline int packPortIdentity(const struct PortIdentity *data, Octet *buf) {
+ int offset = 0;
+ packClockIdentity(&data->clockIdentity, buf + offset);
+ offset += 8;
+ packUInteger16(&data->portNumber, buf + offset);
+ offset += 2;
+ return offset;
+}
+
+static inline int unpackPortIdentity(const Octet *buf, struct PortIdentity *data) {
+ int offset = 0;
+ unpackClockIdentity(buf + offset, &data->clockIdentity);
+ offset += 8;
+ unpackUInteger16(buf + offset, &data->portNumber);
+ offset += 2;
+ return offset;
+}
+
+static inline int packPortAddress(const struct PortAddress *data, Octet *buf) {
+ int offset = 0;
+ packEnumeration16(&data->networkProtocol, buf + offset);
+ offset += 2;
+ packUInteger16(&data->addressLength, buf + offset);
+ offset += 2;
+ packOctetPtr(&data->addressField, buf + offset, data->addressLength);
+ offset += data->addressLength;
+ return offset;
+}
+
+static inline int unpackPortAddress(const Octet *buf, struct PortAddress *data) {
+ int offset = 0;
+ unpackEnumeration16(buf + offset, &data->networkProtocol);
+ offset += 2;
+ unpackUInteger16(buf + offset, &data->addressLength);
+ offset += 2;
+ unpackOctetPtr(buf + offset, &data->addressField, data->addressLength);
+ offset += data->addressLength;
+ return offset;
+}
+
+static inline int packClockQuality(const struct ClockQuality *data, Octet *buf) {
+ int offset = 0;
+ packUInteger8(&data->clockClass, buf + offset);
+ offset++;
+ packEnumeration8(&data->clockAccuracy, buf + offset);
+ offset++;
+ packUInteger16(&data->offsetScaledLogVariance, buf + offset);
+ offset += 2;
+ return offset;
+}
+
+static inline int unpackClockQuality(const Octet *buf, struct ClockQuality *data) {
+ int offset = 0;
+ unpackUInteger8(buf + offset, &data->clockClass);
+ offset += 1;
+ unpackEnumeration8(buf + offset, &data->clockAccuracy);
+ offset += 1;
+ unpackUInteger16(buf + offset, &data->offsetScaledLogVariance);
+ offset += 2;
+ return offset;
+}
+
+static inline int packPTPText(const struct PTPText *data, Octet *buf) {
+ int offset = 0;
+ packUInteger8(&data->length, buf + offset);
+ offset++;
+ packOctetPtr(&data->text, buf + offset, data->length);
+ offset += data->length;
+ return offset;
+}
+
+static inline int unpackPTPText(const Octet *buf, struct PTPText *data) {
+ int offset = 0;
+ unpackUInteger8(buf + offset, &data->length);
+ offset += 1;
+ unpackOctetPtr(buf + offset, &data->text, data->length);
+ offset += data->length;
+ return offset;
+}
+
+
+static inline int packFaultRecord(const struct FaultRecord *data, Octet *buf) {
+ int offset = 0;
+ packUInteger16(&data->faultRecordLength, buf + offset);
+ offset += 2;
+ packTimestamp(&data->faultTime, buf + offset);
+ offset += 10;
+ packEnumeration8(&data->severityCode, buf + offset);
+ offset += 1;
+ packPTPText(&data->faultName, buf + offset);
+ offset += 1 + data->faultName.length;
+ packPTPText(&data->faultValue, buf + offset);
+ offset += 1 + data->faultValue.length;
+ packPTPText(&data->faultDescription, buf + offset);
+ offset += 1 + data->faultDescription.length;
+ return offset;
+}
+
+static inline int unpackFaultRecord(const Octet *buf, struct FaultRecord *data) {
+ int offset = 0;
+ unpackUInteger16(buf + offset, &data->faultRecordLength);
+ offset += 2;
+ unpackTimestamp(buf + offset, &data->faultTime);
+ offset += 10;
+ unpackEnumeration8(buf + offset, &data->severityCode);
+ offset += 1;
+ unpackPTPText(buf + offset, &data->faultName);
+ offset += 1 + data->faultName.length;
+ unpackPTPText(buf + offset, &data->faultValue);
+ offset += 1 + data->faultValue.length;
+ unpackPTPText(buf + offset, &data->faultDescription);
+ offset += 1 + data->faultDescription.length;
+ return offset;
+}
+
+#endif
diff --git a/ds.h b/ds.h
index 514679b..5227682 100644
--- a/ds.h
+++ b/ds.h
@@ -24,25 +24,17 @@
/* clock data sets */
-#define DDS_TWO_STEP_FLAG (1<<0)
-#define DDS_SLAVE_ONLY (1<<1)
-
struct defaultDS {
- UInteger8 flags;
- UInteger8 reserved1;
+ Boolean twoStepFlag;
+ Boolean slaveOnly;
UInteger16 numberPorts;
UInteger8 priority1;
struct ClockQuality clockQuality;
UInteger8 priority2;
struct ClockIdentity clockIdentity;
UInteger8 domainNumber;
- UInteger8 reserved2;
-} PACKED;
-
-struct default_ds {
- struct defaultDS dds;
- int free_running;
- int freq_est_interval; /*log seconds*/
+ int free_running;
+ int freq_est_interval; /*log seconds*/
};
struct dataset {
@@ -63,18 +55,13 @@ struct currentDS {
struct parentDS {
struct PortIdentity parentPortIdentity;
- UInteger8 parentStats;
- UInteger8 reserved;
+ Boolean parentStats;
UInteger16 observedParentOffsetScaledLogVariance;
Integer32 observedParentClockPhaseChangeRate;
UInteger8 grandmasterPriority1;
struct ClockQuality grandmasterClockQuality;
UInteger8 grandmasterPriority2;
struct ClockIdentity grandmasterIdentity;
-} PACKED;
-
-struct parent_ds {
- struct parentDS pds;
struct ClockIdentity *ptl;
unsigned int path_length;
};
@@ -84,9 +71,14 @@ struct parent_ds {
struct timePropertiesDS {
Integer16 currentUtcOffset;
- UInteger8 flags;
+ Boolean leap61;
+ Boolean leap59;
+ Boolean currentUtcOffsetValid;
+ Boolean ptpTimescale;
+ Boolean timeTraceable;
+ Boolean frequencyTraceable;
Enumeration8 timeSource;
-} PACKED;
+};
struct portDS {
struct PortIdentity portIdentity;
@@ -98,8 +90,8 @@ struct portDS {
Integer8 logSyncInterval;
Enumeration8 delayMechanism;
Integer8 logMinPdelayReqInterval;
- UInteger8 versionNumber;
-} PACKED;
+ UInteger4 versionNumber;
+};
struct port_defaults {
Integer64 asymmetry;
@@ -114,4 +106,6 @@ struct port_defaults {
int freq_est_interval; /*log seconds*/
};
+#define OUI_LEN 3
+
#endif
diff --git a/msg.c b/msg.c
index 3ccff96..7d071d6 100644
--- a/msg.c
+++ b/msg.c
@@ -144,48 +144,54 @@ static void port_id_pre_send(struct PortIdentity *pid)
pid->portNumber = htons(pid->portNumber);
}
-static int suffix_post_recv(uint8_t *ptr, int len)
+static void suffix_post_recv(struct ptp_message *m, uint8_t *ptr, int len)
{
- int cnt;
- struct TLV *tlv;
+ struct tlv *tlv;
+ int result;
+ m->tlv = NULL;
if (!ptr)
- return 0;
+ return;
- for (cnt = 0; len > sizeof(struct TLV); cnt++) {
- tlv = (struct TLV *) ptr;
- tlv->type = ntohs(tlv->type);
- tlv->length = ntohs(tlv->length);
- if (tlv->length % 2) {
- break;
- }
- len -= sizeof(struct TLV);
- ptr += sizeof(struct TLV);
- if (tlv->length > len) {
- break;
- }
- len -= tlv->length;
- ptr += tlv->length;
- tlv_post_recv(tlv);
+ tlv = &m->recv_tlv;
+
+ result = tlv_read(ptr, len, tlv);
+ if (result > 0) {
+ /* got a TLV */
+ msg_add_tlv(m, tlv);
+ } else {
+ /* What should be done with invalid or
+ unrecognized TLVs? Ignore them for now,
+ although maybe boundary clocks should
+ forward them on?
I think the code was already doing the right thing WRT this point:

- Invalid packets are dropped.
- TLVs are not forwarded. (BCs generate messages, and they don't
forward them, except for management messages.)
- Valid managment messages are forwarded, complete with unrecognized TLVs.
Post by Geoff Salmon
+ */
}
- return cnt;
}
-static void suffix_pre_send(uint8_t *ptr, int cnt)
+static void suffix_pre_send(struct ptp_message *m, uint8_t *ptr, int len)
{
- int i;
- struct TLV *tlv;
+ struct tlv *tlv;
+ int result;
if (!ptr)
return;
- for (i = 0; i < cnt; i++) {
- tlv = (struct TLV *) ptr;
- tlv_pre_send(tlv);
- ptr += sizeof(struct TLV) + tlv->length;
- tlv->type = htons(tlv->type);
- tlv->length = htons(tlv->length);
+ tlv = m->tlv;
+ if (tlv == NULL)
+ return;
+
+ result = tlv_write(tlv, ptr, len);
+ if (result > 0) {
+ ptr += result;
+ len -= result;
+ m->total_msg_len += result;
}
+
+ /* After the TLV is serialized, remove pointers to TLV
+ * structs. The pointers are owned by other parts of the code
+ * and may be on the stack somewhere, so it's safest to clear
+ * them as soon as they aren't needed. */
+ m->tlv = NULL;
}
static void timestamp_post_recv(struct ptp_message *m, struct Timestamp *ts)
@@ -258,6 +264,9 @@ int msg_post_recv(struct ptp_message *m, int cnt)
if (hdr_post_recv(&m->header))
return -1;
+ if (m->header.messageLength != cnt)
+ return -1;
+
type = msg_type(m);
switch (type) {
@@ -342,7 +351,12 @@ int msg_post_recv(struct ptp_message *m, int cnt)
return -1;
}
- m->tlv_count = suffix_post_recv(suffix, cnt - pdulen);
+ m->total_msg_len = m->header.messageLength;
+ /* adjust header message len to not include suffix. Suffix
+ * will be added back by msg_pre_send */
+ m->header.messageLength = pdulen;
+
+ suffix_post_recv(m, suffix, cnt - pdulen);
return 0;
}
@@ -352,9 +366,6 @@ int msg_pre_send(struct ptp_message *m)
int type;
uint8_t *suffix = NULL;
- if (hdr_pre_send(&m->header))
- return -1;
-
type = msg_type(m);
switch (type) {
@@ -396,7 +407,14 @@ int msg_pre_send(struct ptp_message *m)
return -1;
}
- suffix_pre_send(suffix, m->tlv_count);
+ m->total_msg_len = m->header.messageLength;
+ suffix_pre_send(m, suffix, MAX_MSG_LEN - (suffix - m->data.buffer));
+ /* suffix_pre_send updates the total_msg_len. Copy it back to
+ header. */
+ m->header.messageLength = m->total_msg_len;
+ if (hdr_pre_send(&m->header))
+ return -1;
+
return 0;
}
@@ -464,3 +482,8 @@ int msg_sots_missing(struct ptp_message *m)
}
return (!m->hwts.ts.tv_sec && !m->hwts.ts.tv_nsec) ? 1 : 0;
}
+
+void msg_add_tlv(struct ptp_message *m, struct tlv *tlv) {
+ m->tlv = tlv;
+ m->tlv_count = 1;
+}
diff --git a/msg.h b/msg.h
index 940e790..311a953 100644
--- a/msg.h
+++ b/msg.h
@@ -24,11 +24,36 @@
#include <sys/queue.h>
#include <time.h>
-#include "ddt.h"
+#include "tlv.h"
#include "transport.h"
#define PTP_VERSION 2
+enum msg_types {
These are already defined.
Post by Geoff Salmon
+ MSG_SYNC = 0,
+ MSG_DELAY_REQ,
+ MSG_PDELAY_REQ,
+ MSG_PDELAY_RESP,
+ /*Reserved 4-7 */
+ MSG_FOLLOW_UP = 8,
+ MSG_DELAY_RESP,
+ MSG_PDELAY_RESP_FOLLOW_UP,
+ MSG_ANNOUNCE,
+ MSG_SIGNALING,
+ MSG_MANAGEMENT,
+ /* Reserved E-F*/
+};
+
+enum control_types {
And so are these.
Post by Geoff Salmon
+ CONTROL_SYNC = 0x00,
+ CONTROL_DELAY_REQ,
+ CONTROL_FOLLOW_UP,
+ CONTROL_DELAY_RESP,
+ CONTROL_MANAGEMENT,
+ CONTROL_OTHER
+/* Reserved 0x06-0xFF */
+};
+
/* Values for the messageType field */
#define SYNC 0x0
#define DELAY_REQ 0x1
@@ -150,10 +175,16 @@ struct management_msg {
uint8_t suffix[0];
} PACKED;
+#define MAX_MSG_LEN 1500
+
struct message_data {
- uint8_t buffer[1500];
+ uint8_t buffer[MAX_MSG_LEN];
} PACKED;
+#define PATH_TRACE_MAX \
+ ((sizeof(struct message_data) - sizeof(struct announce_msg) - TLV_HDR_LEN) / \
+ sizeof(struct ClockIdentity))
+
struct ptp_message {
union {
struct ptp_header header;
@@ -200,10 +231,27 @@ struct ptp_message {
* SO_TIMESTAMPING socket option.
*/
struct hw_timestamp hwts;
+
+ /**
+ * The message's TLV. Possibly NULL.
+ */
+ struct tlv *tlv;
+
+ /**
+ * Holds the received TLV
+ */
+ struct tlv recv_tlv;
+
/**
* Contains the number of TLVs in the suffix.
*/
int tlv_count;
+ /**
+ * Contains the size of the full message. Is set by the
+ * msg_pre_end and msg_post_recv. Unlike the
+ * header.messageLength, this is always in host byte order.
+ */
+ int total_msg_len;
};
/**
@@ -306,6 +354,14 @@ void msg_put(struct ptp_message *m);
int msg_sots_missing(struct ptp_message *m);
/**
+ * Adds a TLV to a message.
+ * so only the TLV that is added last will be used.
+ */
+void msg_add_tlv(struct ptp_message *m, struct tlv *tlv);
+
+/**
* Work around buggy 802.1AS switches.
*/
extern int assume_two_step;
diff --git a/pdt.h b/pdt.h
index 29ffbb5..b81f42a 100644
--- a/pdt.h
+++ b/pdt.h
@@ -25,15 +25,28 @@
enum {FALSE, TRUE};
typedef int Boolean;
+typedef uint8_t Enumeration4;
These typedefs don't make any sense. There is no C type smaller than 8
bits, and so these must be bit fields.
Post by Geoff Salmon
typedef uint8_t Enumeration8;
typedef uint16_t Enumeration16;
+typedef uint8_t UInteger4;
typedef int8_t Integer8;
typedef uint8_t UInteger8;
typedef int16_t Integer16;
typedef uint16_t UInteger16;
typedef int32_t Integer32;
typedef uint32_t UInteger32;
+typedef struct {
+ uint32_t lsb;
+ uint16_t msb;
+} UInteger48;
typedef int64_t Integer64;
+typedef uint64_t UInteger64;
+typedef int64_t Integer64;
+typedef uint64_t UInteger64;
+typedef uint8_t Nibble;
typedef uint8_t Octet;
+typedef uint8_t* OctetPtr;
+
+typedef Integer64 TimeInterval;
#endif
diff --git a/pdt_pack.h b/pdt_pack.h
new file mode 100644
index 0000000..c2ac253
--- /dev/null
+++ b/pdt_pack.h
@@ -0,0 +1,161 @@
+/**
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef HAVE_PDT_PACK_H
+#define HAVE_PDT_PACK_H
+
+#include <arpa/inet.h>
+#include <asm/byteorder.h>
+
+#include "ddt.h"
+#include "ds.h"
+
+#define flip16(x) htons(x)
+#define flip32(x) htonl(x)
+#define flip64(x) __cpu_to_be64(x)
+
+#define PACK_SIMPLE(type) \
+static inline void pack##type(const type *from, void *to) \
+{ \
+ *(type *)to = *(type *)from; \
The heavy handed casting here is asking for trouble, IMHO.

Why do it?
Why not just use a simple assigment?

Better to key the type information and let the complier warn on type
mismatches.
Post by Geoff Salmon
+} \
+static inline void unpack##type(const void *from, type *to) \
+{ \
+ *(type *)to = *(type *)from; \
+}
+
+#define PACK_ENDIAN(type, size) \
+static inline void pack##type(const type *from, void *to) \
+{ \
+ *(type *)to = flip##size( *(type *)from ); \
+} \
+static inline void unpack##type(const void *from, type *to) \
+{ \
+ *(type *)to = flip##size( *(type *)from ); \
+}
+
+#define PACK_LOWER_AND_UPPER(type) \
+static inline void pack##type##Lower(const type *from, void *to) \
+{ \
+ *(uint8_t*)to = *(uint8_t*)to & 0xF0; \
+ *(uint8_t*)to = *(uint8_t*)to | *(type *)from; \
+} \
+\
+static inline void pack##type##Upper(const void *from, type *to) \
+{ \
+ *(uint8_t*)to = *(uint8_t*)to & 0x0F; \
+ *(uint8_t*)to = *(uint8_t*)to | (*(type *)from << 4); \
+} \
+\
+static inline void unpack##type##Lower(const type *from, void *to) \
+{ \
+ *(type *)to = *(uint8_t*)from & 0x0F; \
+} \
+\
+static inline void unpack##type##Upper(const void *from, type *to) \
+{ \
+ *(type *)to = *(uint8_t*)from & 0xF0; \
+}
+
+PACK_SIMPLE(UInteger8)
+PACK_SIMPLE(Octet)
+PACK_SIMPLE(Enumeration8)
+PACK_SIMPLE(Integer8)
+
+PACK_ENDIAN(Enumeration16, 16)
+PACK_ENDIAN(Integer16, 16)
+PACK_ENDIAN(UInteger16, 16)
+PACK_ENDIAN(Integer32, 32)
+PACK_ENDIAN(UInteger32, 32)
+PACK_ENDIAN(Integer64, 64)
+PACK_ENDIAN(UInteger64, 64)
+
+PACK_LOWER_AND_UPPER(Enumeration4)
+PACK_LOWER_AND_UPPER(UInteger4)
+PACK_LOWER_AND_UPPER(Nibble)
+
+#undef PACK_SIMPLE
+#undef PACK_ENDIAN
+#undef PACK_LOWER_AND_UPPER
+
+/* TODO: Is it better for pack/unpack Boolean to use !! instead of & 1
+ to treat any non-zero values as true? Maybe only in packBoolean?
+*/
+static inline void unpackBoolean(const void *from, Boolean *to) {
Opening brace goes on its own line.
Post by Geoff Salmon
+ *(Boolean *)to = (*(Boolean *)from) & 1;
+}
+
+static inline void packBoolean(const Boolean *from, void *to) {
+ *(Boolean *)to = (*(Boolean *)from) & 1;
+}
+
+static inline void unpackUInteger48(const void *buf, UInteger48 *i) {
+ unpackUInteger16(buf, &i->msb);
+ unpackUInteger32(buf + 4, &i->lsb);
+}
+
+static inline void packUInteger48(const UInteger48 *i, void *buf) {
+ packUInteger16(&i->msb, buf);
+ packUInteger32(&i->lsb, buf + 4);
+}
+
+static inline void unpackOctetPtr(const void *buf, OctetPtr *bytes, int len) {
+ if (len)
+ *bytes = (Octet*)buf;
+ else
+ *bytes = 0;
+}
+
+static inline void packOctetPtr(const OctetPtr *bytes, void *buf, int len) {
+ /*
+ Use memmove instead of memcpy to support calling
+ msg_post_recv and then msg_pre_send on the same message,
+ which could cause parts of the message's TLVs (PTPTexts for
+ example) to be copied from and to the same message buffer.
+ The only place this happens currently, I think, is when
+ management messages are forwarded.
+
+ As long as the TLVs aren't inserted or TLVs aren't reordered
+ between the calls to msg_post_recv and msg_pre_send then
+ this memmove should avoid any corruption.
+ */
+ if (len)
+ memmove(buf, *bytes, len);
+}
+
+static inline void unpackOctetArray(const void *buf, Octet *bytes, int len) {
+ memcpy(bytes, buf, len);
+}
+
+static inline void packOctetArray(const Octet *bytes, void *buf, int len) {
+ memcpy(buf, bytes, len);
+}
+
+static inline void packReserved(Octet *buf, int len) {
+ if (len == 0) {
+ /* A zero length reserved field is used for upper or
+ * lower nibbles that should be zeroed out. Zero out
+ * the whole byte. */
+ *((int8_t*)buf) = 0;
+ } else {
+ memset(buf, 0, len);
+ }
+}
+
+#endif
diff --git a/pmc.c b/pmc.c
index 3fe0be7..ac998aa 100644
--- a/pmc.c
+++ b/pmc.c
@@ -32,6 +32,7 @@
#include "tlv.h"
#include "transport.h"
#include "util.h"
+#include "ddt_pack.h"
#include "version.h"
#define BAD_ACTION -1
@@ -61,72 +62,70 @@ struct management_id {
struct management_id idtab[] = {
/* Clock management ID values */
- { "USER_DESCRIPTION", USER_DESCRIPTION, not_supported },
- { "SAVE_IN_NON_VOLATILE_STORAGE", SAVE_IN_NON_VOLATILE_STORAGE, not_supported },
- { "RESET_NON_VOLATILE_STORAGE", RESET_NON_VOLATILE_STORAGE, not_supported },
- { "INITIALIZE", INITIALIZE, not_supported },
- { "FAULT_LOG", FAULT_LOG, not_supported },
- { "FAULT_LOG_RESET", FAULT_LOG_RESET, not_supported },
- { "DEFAULT_DATA_SET", DEFAULT_DATA_SET, do_get_action },
- { "CURRENT_DATA_SET", CURRENT_DATA_SET, do_get_action },
- { "PARENT_DATA_SET", PARENT_DATA_SET, do_get_action },
- { "TIME_PROPERTIES_DATA_SET", TIME_PROPERTIES_DATA_SET, do_get_action },
- { "PRIORITY1", PRIORITY1, not_supported },
- { "PRIORITY2", PRIORITY2, not_supported },
- { "DOMAIN", DOMAIN, not_supported },
- { "SLAVE_ONLY", SLAVE_ONLY, not_supported },
- { "TIME", TIME, not_supported },
- { "CLOCK_ACCURACY", CLOCK_ACCURACY, not_supported },
- { "UTC_PROPERTIES", UTC_PROPERTIES, not_supported },
- { "TRACEABILITY_PROPERTIES", TRACEABILITY_PROPERTIES, not_supported },
- { "TIMESCALE_PROPERTIES", TIMESCALE_PROPERTIES, not_supported },
- { "PATH_TRACE_LIST", PATH_TRACE_LIST, not_supported },
- { "PATH_TRACE_ENABLE", PATH_TRACE_ENABLE, not_supported },
- { "GRANDMASTER_CLUSTER_TABLE", GRANDMASTER_CLUSTER_TABLE, not_supported },
- { "ACCEPTABLE_MASTER_TABLE", ACCEPTABLE_MASTER_TABLE, not_supported },
- { "ACCEPTABLE_MASTER_MAX_TABLE_SIZE", ACCEPTABLE_MASTER_MAX_TABLE_SIZE, not_supported },
- { "ALTERNATE_TIME_OFFSET_ENABLE", ALTERNATE_TIME_OFFSET_ENABLE, not_supported },
- { "ALTERNATE_TIME_OFFSET_NAME", ALTERNATE_TIME_OFFSET_NAME, not_supported },
- { "ALTERNATE_TIME_OFFSET_MAX_KEY", ALTERNATE_TIME_OFFSET_MAX_KEY, not_supported },
- { "ALTERNATE_TIME_OFFSET_PROPERTIES", ALTERNATE_TIME_OFFSET_PROPERTIES, not_supported },
- { "TRANSPARENT_CLOCK_DEFAULT_DATA_SET", TRANSPARENT_CLOCK_DEFAULT_DATA_SET, not_supported },
- { "PRIMARY_DOMAIN", PRIMARY_DOMAIN, not_supported },
- { "TIME_STATUS_NP", TIME_STATUS_NP, do_get_action },
This kind of gratuitous renaming does not really add anything to the
code. Even if it were needed (to avoid a namespace clash), then it
would need it own patch series, without adding any new code.
Post by Geoff Salmon
+ { "USER_DESCRIPTION", MGMT_USER_DESCRIPTION, do_get_action },
+ { "SAVE_IN_NON_VOLATILE_STORAGE", MGMT_SAVE_IN_NON_VOLATILE_STORAGE, not_supported },
+ { "RESET_NON_VOLATILE_STORAGE", MGMT_RESET_NON_VOLATILE_STORAGE, not_supported },
+ { "INITIALIZE", MGMT_INITIALIZE, not_supported },
+ { "FAULT_LOG", MGMT_FAULT_LOG, not_supported },
+ { "FAULT_LOG_RESET", MGMT_FAULT_LOG_RESET, not_supported },
+ { "DEFAULT_DATA_SET", MGMT_DEFAULT_DATA_SET, do_get_action },
+ { "CURRENT_DATA_SET", MGMT_CURRENT_DATA_SET, do_get_action },
+ { "PARENT_DATA_SET", MGMT_PARENT_DATA_SET, do_get_action },
+ { "TIME_PROPERTIES_DATA_SET", MGMT_TIME_PROPERTIES_DATA_SET, do_get_action },
+ { "PRIORITY1", MGMT_PRIORITY1, do_get_action },
+ { "PRIORITY2", MGMT_PRIORITY2, do_get_action },
+ { "DOMAIN", MGMT_DOMAIN, do_get_action },
+ { "SLAVE_ONLY", MGMT_SLAVE_ONLY, do_get_action },
+ { "TIME", MGMT_TIME, do_get_action },
+ { "CLOCK_ACCURACY", MGMT_CLOCK_ACCURACY, do_get_action },
+ { "UTC_PROPERTIES", MGMT_UTC_PROPERTIES, do_get_action },
+ { "TRACEABILITY_PROPERTIES", MGMT_TRACEABILITY_PROPERTIES, do_get_action },
+ { "TIMESCALE_PROPERTIES", MGMT_TIMESCALE_PROPERTIES, do_get_action },
+ { "PATH_TRACE_LIST", MGMT_PATH_TRACE_LIST, do_get_action },
+ { "PATH_TRACE_ENABLE", MGMT_PATH_TRACE_ENABLE, not_supported },
+ { "GRANDMASTER_CLUSTER_TABLE", MGMT_GRANDMASTER_CLUSTER_TABLE, not_supported },
+ { "ACCEPTABLE_MASTER_TABLE", MGMT_ACCEPTABLE_MASTER_TABLE, not_supported },
+ { "ACCEPTABLE_MASTER_MAX_TABLE_SIZE", MGMT_ACCEPTABLE_MASTER_MAX_TABLE_SIZE, not_supported },
+ { "ALTERNATE_TIME_OFFSET_ENABLE", MGMT_ALTERNATE_TIME_OFFSET_ENABLE, not_supported },
+ { "ALTERNATE_TIME_OFFSET_NAME", MGMT_ALTERNATE_TIME_OFFSET_NAME, not_supported },
+ { "ALTERNATE_TIME_OFFSET_MAX_KEY", MGMT_ALTERNATE_TIME_OFFSET_MAX_KEY, not_supported },
+ { "ALTERNATE_TIME_OFFSET_PROPERTIES", MGMT_ALTERNATE_TIME_OFFSET_PROPERTIES, not_supported },
+ { "TRANSPARENT_CLOCK_DEFAULT_DATA_SET", MGMT_TRANSPARENT_CLOCK_DEFAULT_DATA_SET, not_supported },
+ { "PRIMARY_DOMAIN", MGMT_PRIMARY_DOMAIN, not_supported },
+ { "TIME_STATUS_NP", MGMT_TIME_STATUS_NP, do_get_action },
/* Port management ID values */
- { "NULL_MANAGEMENT", NULL_MANAGEMENT, null_management },
- { "CLOCK_DESCRIPTION", CLOCK_DESCRIPTION, not_supported },
- { "PORT_DATA_SET", PORT_DATA_SET, do_get_action },
- { "LOG_ANNOUNCE_INTERVAL", LOG_ANNOUNCE_INTERVAL, not_supported },
- { "ANNOUNCE_RECEIPT_TIMEOUT", ANNOUNCE_RECEIPT_TIMEOUT, not_supported },
- { "LOG_SYNC_INTERVAL", LOG_SYNC_INTERVAL, not_supported },
- { "VERSION_NUMBER", VERSION_NUMBER, not_supported },
- { "ENABLE_PORT", ENABLE_PORT, not_supported },
- { "DISABLE_PORT", DISABLE_PORT, not_supported },
- { "UNICAST_NEGOTIATION_ENABLE", UNICAST_NEGOTIATION_ENABLE, not_supported },
- { "UNICAST_MASTER_TABLE", UNICAST_MASTER_TABLE, not_supported },
- { "UNICAST_MASTER_MAX_TABLE_SIZE", UNICAST_MASTER_MAX_TABLE_SIZE, not_supported },
- { "ACCEPTABLE_MASTER_TABLE_ENABLED", ACCEPTABLE_MASTER_TABLE_ENABLED, not_supported },
- { "ALTERNATE_MASTER", ALTERNATE_MASTER, not_supported },
- { "TRANSPARENT_CLOCK_PORT_DATA_SET", TRANSPARENT_CLOCK_PORT_DATA_SET, not_supported },
- { "DELAY_MECHANISM", DELAY_MECHANISM, not_supported },
- { "LOG_MIN_PDELAY_REQ_INTERVAL", LOG_MIN_PDELAY_REQ_INTERVAL, not_supported },
+ { "NULL_MANAGEMENT", MGMT_NULL_MANAGEMENT, null_management },
+ { "CLOCK_DESCRIPTION", MGMT_CLOCK_DESCRIPTION, do_get_action },
+ { "PORT_DATA_SET", MGMT_PORT_DATA_SET, do_get_action },
+ { "LOG_ANNOUNCE_INTERVAL", MGMT_LOG_ANNOUNCE_INTERVAL, do_get_action },
+ { "ANNOUNCE_RECEIPT_TIMEOUT", MGMT_ANNOUNCE_RECEIPT_TIMEOUT, do_get_action },
+ { "LOG_SYNC_INTERVAL", MGMT_LOG_SYNC_INTERVAL, do_get_action },
+ { "VERSION_NUMBER", MGMT_VERSION_NUMBER, do_get_action },
+ { "ENABLE_PORT", MGMT_ENABLE_PORT, not_supported },
+ { "DISABLE_PORT", MGMT_DISABLE_PORT, not_supported },
+ { "UNICAST_NEGOTIATION_ENABLE", MGMT_UNICAST_NEGOTIATION_ENABLE, not_supported },
+ { "UNICAST_MASTER_TABLE", MGMT_UNICAST_MASTER_TABLE, not_supported },
+ { "UNICAST_MASTER_MAX_TABLE_SIZE", MGMT_UNICAST_MASTER_MAX_TABLE_SIZE, not_supported },
+ { "ACCEPTABLE_MASTER_TABLE_ENABLED", MGMT_ACCEPTABLE_MASTER_TABLE_ENABLED, not_supported },
+ { "ALTERNATE_MASTER", MGMT_ALTERNATE_MASTER, not_supported },
+ { "TRANSPARENT_CLOCK_PORT_DATA_SET", MGMT_TRANSPARENT_CLOCK_PORT_DATA_SET, not_supported },
+ { "DELAY_MECHANISM", MGMT_DELAY_MECHANISM, do_get_action },
+ { "LOG_MIN_PDELAY_REQ_INTERVAL", MGMT_LOG_MIN_PDELAY_REQ_INTERVAL, do_get_action },
};
static struct ptp_message *pmc_message(uint8_t action)
{
struct ptp_message *msg;
- int pdulen;
msg = msg_allocate();
if (!msg)
return NULL;
- pdulen = sizeof(struct management_msg);
msg->hwts.type = TS_SOFTWARE;
msg->header.tsmt = MANAGEMENT | transport_specific;
msg->header.ver = PTP_VERSION;
- msg->header.messageLength = pdulen;
+ msg->header.messageLength = sizeof(struct management_msg);
msg->header.domainNumber = domain_number;
msg->header.sourcePortIdentity = port_identity;
msg->header.sequenceId = sequence_id++;
@@ -142,7 +141,7 @@ static struct ptp_message *pmc_message(uint8_t action)
return msg;
}
-static int pmc_send(struct ptp_message *msg, int pdulen)
+static int pmc_send(struct ptp_message *msg)
{
int cnt, err;
err = msg_pre_send(msg);
@@ -150,7 +149,7 @@ static int pmc_send(struct ptp_message *msg, int pdulen)
fprintf(stderr, "msg_pre_send failed\n");
return -1;
}
- cnt = transport_send(transport, &fdarray, 0, msg, pdulen, &msg->hwts);
+ cnt = transport_send(transport, &fdarray, 0, msg, msg->total_msg_len, &msg->hwts);
if (cnt < 0) {
fprintf(stderr, "failed to send message\n");
return -1;
@@ -168,46 +167,164 @@ static char *action_string[] = {
#define IFMT "\n\t\t"
+static char *text2str_impl(struct PTPText *text, struct StaticPTPText *s) {
+ s->max_symbols = -1;
+ static_ptp_text_copy(s, text);
+ return (char*)(s->text);
+}
+
+static char *text2str(struct PTPText *text) {
+ static struct StaticPTPText s;
+ return text2str_impl(text, &s);
+}
+/* Extra copies of the text2str function so if can be used multiple
+ * times in the same fprintf call. Need to do this because of the
+ * static StaticPTPText inside function.
+ */
+static char *text2str1(struct PTPText *text) {
+ static struct StaticPTPText s;
+ return text2str_impl(text, &s);
+}
+static char *text2str2(struct PTPText *text) {
+ static struct StaticPTPText s;
+ return text2str_impl(text, &s);
+}
+static char *text2str3(struct PTPText *text) {
+ static struct StaticPTPText s;
+ return text2str_impl(text, &s);
+}
+
+static char *bin2str_impl(Octet *data, int len, char *buf, int buf_len) {
+ int i, offset = 0;
+ /* print at most 16 bytes */
+ if (len > 16)
+ len = 16;
+ buf[0] = '\0';
+ if (len)
+ offset += snprintf(buf, buf_len, "%02hhx", data[0]);
+ for (i = 1; i < len; i++) {
+ if (offset >= buf_len)
+ /* truncated output */
+ break;
+ offset += snprintf(buf + offset, buf_len - offset, ":%02hhx", data[i]);
+ }
+ return buf;
+}
+
+static char *bin2str(Octet *data, int len) {
+ static char buf[49];
+ return bin2str_impl(data, len, buf, sizeof(buf));
+}
+
+/* Extra copies of the bin2str function so if can be used multiple
+ * times in the same fprintf call. Need to do this because of the
+ * static StaticPTPText inside function.
+ */
+static char *bin2str1(Octet *data, int len) {
+ static char buf[49];
+ return bin2str_impl(data, len, buf, sizeof(buf));
+}
+
+static char *bin2str2(Octet *data, int len) {
+ static char buf[49];
+ return bin2str_impl(data, len, buf, sizeof(buf));
+}
+
+static char *bin2str3(Octet *data, int len) {
+ static char buf[49];
+ return bin2str_impl(data, len, buf, sizeof(buf));
+}
+
+static char *time2str(struct Timestamp *t) {
+ static char buf[42];
+ uint64_t secs = t->seconds_msb;
+ secs <<= 32;
+ secs += t->seconds_lsb;
+
+ snprintf(buf, sizeof(buf), "%" PRIu64 ".%u", secs, t->nanoseconds);
+ return buf;
+}
+
+static const char *bool2str(int b) {
+ return b ? "TRUE" : "FALSE";
+}
+
static void pmc_show(struct ptp_message *msg, FILE *fp)
{
- int action;
- struct TLV *tlv;
- struct management_tlv *mgt;
+ int i, action;
+ struct tlv *tlv;
struct defaultDS *dds;
struct currentDS *cds;
struct parentDS *pds;
struct timePropertiesDS *tp;
struct time_status_np *tsn;
struct portDS *p;
+ struct ClockDescription *cd;
if (msg_type(msg) != MANAGEMENT) {
return;
}
action = management_action(msg);
- if (action < GET || action > ACKNOWLEDGE) {
+ if (action < ACTION_GET || action > ACTION_ACKNOWLEDGE) {
return;
}
fprintf(fp, "\t%s seq %hu %s ",
pid2str(&msg->header.sourcePortIdentity),
msg->header.sequenceId, action_string[action]);
- if (msg->tlv_count != 1) {
+ tlv = msg->tlv;
+ if (tlv == NULL) {
goto out;
}
- tlv = (struct TLV *) msg->management.suffix;
- if (tlv->type == TLV_MANAGEMENT) {
+ switch (tlv->type) {
fprintf(fp, "MANAGMENT ");
- } else if (tlv->type == TLV_MANAGEMENT_ERROR_STATUS) {
+ break;
fprintf(fp, "MANAGMENT_ERROR_STATUS ");
goto out;
- } else {
fprintf(fp, "unknown-tlv ");
+ goto out;
}
- mgt = (struct management_tlv *) msg->management.suffix;
- switch (mgt->id) {
- dds = (struct defaultDS *) mgt->data;
+ union ManagementTLVData *data = &tlv->mgmt.data;
+ switch (tlv->mgmt.id) {
+ fprintf(fp, "NULL ");
+ break;
+ cd = &data->cd;
+ fprintf(fp, "CLOCK_DESCRIPTION "
+ IFMT "clockType %hu"
+ IFMT "physicalLayerProtocol %s"
+ IFMT "physicalAddress %s"
+ IFMT "protocolAddress %hu %s"
+ IFMT "manufacturerId %s"
+ IFMT "productDescription %s"
+ IFMT "revisionData %s"
+ IFMT "userDescription %s"
+ IFMT "profileId %s",
+ cd->clockType,
+ text2str(&cd->physicalLayerProtocol),
+ bin2str(cd->physicalAddress.addressField,
+ cd->physicalAddress.addressLength),
+ cd->protocolAddress.networkProtocol,
+ bin2str1(cd->protocolAddress.addressField,
+ cd->protocolAddress.addressLength),
+ bin2str2(cd->manufacturerIdentity, 3),
+ text2str1(&cd->productDescription),
+ text2str2(&cd->revisionData),
+ text2str3(&cd->userDescription),
+ bin2str3(cd->profileIdentity, 6));
+ break;
+ fprintf(fp, "USER_DESCRIPTION "
+ IFMT "userDescription %s",
+ text2str(&data->cd.userDescription));
+ break;
+ dds = &data->dds;
fprintf(fp, "DEFAULT_DATA_SET "
- IFMT "twoStepFlag %d"
- IFMT "slaveOnly %d"
+ IFMT "slaveOnly %s"
+ IFMT "twoStepFlag %s"
IFMT "numberPorts %hu"
IFMT "priority1 %hhu"
IFMT "clockClass %hhu"
@@ -216,8 +333,8 @@ static void pmc_show(struct ptp_message *msg, FILE *fp)
IFMT "priority2 %hhu"
IFMT "clockIdentity %s"
IFMT "domainNumber %hhu",
- dds->flags & DDS_TWO_STEP_FLAG ? 1 : 0,
- dds->flags & DDS_SLAVE_ONLY ? 1 : 0,
+ bool2str(dds->slaveOnly),
+ bool2str(dds->twoStepFlag),
dds->numberPorts,
dds->priority1,
dds->clockQuality.clockClass,
@@ -227,8 +344,8 @@ static void pmc_show(struct ptp_message *msg, FILE *fp)
cid2str(&dds->clockIdentity),
dds->domainNumber);
break;
- cds = (struct currentDS *) mgt->data;
+ cds = &data->cds;
fprintf(fp, "CURRENT_DATA_SET "
IFMT "stepsRemoved %hd"
IFMT "offsetFromMaster %.1f"
@@ -236,11 +353,11 @@ static void pmc_show(struct ptp_message *msg, FILE *fp)
cds->stepsRemoved, cds->offsetFromMaster / 65536.0,
cds->meanPathDelay / 65536.0);
break;
- pds = (struct parentDS *) mgt->data;
+ pds = &data->parent_ds;
fprintf(fp, "PARENT_DATA_SET "
IFMT "parentPortIdentity %s"
- IFMT "parentStats %hhu"
+ IFMT "parentStats %s"
IFMT "observedParentOffsetScaledLogVariance 0x%04hx"
IFMT "observedParentClockPhaseChangeRate 0x%08x"
IFMT "grandmasterPriority1 %hhu"
@@ -250,7 +367,7 @@ static void pmc_show(struct ptp_message *msg, FILE *fp)
IFMT "grandmasterPriority2 %hhu"
IFMT "grandmasterIdentity %s",
pid2str(&pds->parentPortIdentity),
- pds->parentStats,
+ bool2str(pds->parentStats),
pds->observedParentOffsetScaledLogVariance,
pds->observedParentClockPhaseChangeRate,
pds->grandmasterPriority1,
@@ -260,50 +377,28 @@ static void pmc_show(struct ptp_message *msg, FILE *fp)
pds->grandmasterPriority2,
cid2str(&pds->grandmasterIdentity));
break;
- tp = (struct timePropertiesDS *) mgt->data;
+ tp = &data->tpds;
fprintf(fp, "TIME_PROPERTIES_DATA_SET "
IFMT "currentUtcOffset %hd"
- IFMT "leap61 %d"
- IFMT "leap59 %d"
- IFMT "currentUtcOffsetValid %d"
- IFMT "ptpTimescale %d"
- IFMT "timeTraceable %d"
- IFMT "frequencyTraceable %d"
+ IFMT "leap61 %s"
+ IFMT "leap59 %s"
+ IFMT "currentUtcOffsetValid %s"
+ IFMT "ptpTimescale %s"
+ IFMT "timeTraceable %s"
+ IFMT "frequencyTraceable %s"
IFMT "timeSource 0x%02hhx",
tp->currentUtcOffset,
- tp->flags & LEAP_61 ? 1 : 0,
- tp->flags & LEAP_59 ? 1 : 0,
- tp->flags & UTC_OFF_VALID ? 1 : 0,
- tp->flags & PTP_TIMESCALE ? 1 : 0,
- tp->flags & TIME_TRACEABLE ? 1 : 0,
- tp->flags & FREQ_TRACEABLE ? 1 : 0,
+ bool2str(tp->leap61),
+ bool2str(tp->leap59),
+ bool2str(tp->currentUtcOffsetValid),
+ bool2str(tp->ptpTimescale),
+ bool2str(tp->timeTraceable),
+ bool2str(tp->frequencyTraceable),
tp->timeSource);
break;
- tsn = (struct time_status_np *) mgt->data;
- fprintf(fp, "TIME_STATUS_NP "
- IFMT "master_offset %" PRId64
- IFMT "ingress_time %" PRId64
- IFMT "cumulativeScaledRateOffset %+.9f"
- IFMT "scaledLastGmPhaseChange %d"
- IFMT "gmTimeBaseIndicator %hu"
- IFMT "lastGmPhaseChange 0x%04hx'%016" PRIx64 ".%04hx"
- IFMT "gmPresent %s"
- IFMT "gmIdentity %s",
- tsn->master_offset,
- tsn->ingress_time,
- 1.0 + (tsn->cumulativeScaledRateOffset + 0.0) / P41,
- tsn->scaledLastGmPhaseChange,
- tsn->gmTimeBaseIndicator,
- tsn->lastGmPhaseChange.nanoseconds_msb,
- tsn->lastGmPhaseChange.nanoseconds_lsb,
- tsn->lastGmPhaseChange.fractional_nanoseconds,
- tsn->gmPresent ? "true" : "false",
- cid2str(&tsn->gmIdentity));
- break;
- p = (struct portDS *) mgt->data;
+ p = &data->port_ds;
if (p->portState > PS_SLAVE) {
p->portState = 0;
}
@@ -324,6 +419,123 @@ static void pmc_show(struct ptp_message *msg, FILE *fp)
p->logSyncInterval, p->delayMechanism,
p->logMinPdelayReqInterval, p->versionNumber);
break;
+ fprintf(fp, "PRIORITY1 "
+ IFMT "priority1 %hhu",
+ data->dds.priority1);
Over zealous line breaking.
Post by Geoff Salmon
+ break;
+ fprintf(fp, "PRIORITY2 "
+ IFMT "priority2 %hhu",
+ data->dds.priority2);
+ break;
+ fprintf(fp, "DOMAIN "
+ IFMT "domainNumber %hhu",
+ data->dds.domainNumber);
+ break;
+ fprintf(fp, "SLAVE_ONLY "
+ IFMT "slaveOnly %s",
+ bool2str(data->dds.slaveOnly));
+ break;
+ fprintf(fp, "LOG_ANNOUNCE_INTERVAL "
+ IFMT "logAnnounceInterval %hhd",
+ data->port_ds.logAnnounceInterval);
+ break;
+ fprintf(fp, "ANNOUNCE_RECEIPT_TIMEOUT "
+ IFMT "announceReceiptTimeout %hhu",
+ data->port_ds.announceReceiptTimeout);
+ break;
+ fprintf(fp, "LOG_SYNC_INTERVAL "
+ IFMT "logSyncInterval %hhd",
+ data->port_ds.logSyncInterval);
+ break;
+ fprintf(fp, "VERSION_NUMBER "
+ IFMT "versionNumber %hhu",
+ data->port_ds.versionNumber);
+ break;
+ fprintf(fp, "TIME "
+ IFMT "time %s",
+ time2str(&data->time.currentTime));
+ break;
+ fprintf(fp, "CLOCK_ACCURACY "
+ IFMT "clockAccuracy %hhu",
+ data->ca.clockAccuracy);
+ break;
+ tp = &data->tpds;
+ fprintf(fp, "UTC_PROPERTIES "
+ IFMT "currentUtcOffset %d"
+ IFMT "leap61 %s"
+ IFMT "leap59 %s"
+ IFMT "currentUtcOffsetValid %s",
+ tp->currentUtcOffset,
+ bool2str(tp->leap61),
+ bool2str(tp->leap59),
+ bool2str(tp->currentUtcOffsetValid));
+ break;
+ tp = &data->tpds;
+ fprintf(fp, "TRACEABILITY_PROPERTIES_DATA_SET "
+ IFMT "timeTraceable %s"
+ IFMT "frequencyTraceable %s",
+ bool2str(tp->timeTraceable),
+ bool2str(tp->frequencyTraceable));
+ break;
+ tp = &data->tpds;
+ fprintf(fp, "TIMESCALE_PROPERTIES_DATA_SET "
+ IFMT "ptpTimescale %s",
+ bool2str(tp->ptpTimescale));
+ break;
+ fprintf(fp, "PATH_TRACE_LIST ");
+ for (i = 0; i < data->path.num; i++) {
+ fprintf(fp, IFMT "%4d: %s", i, cid2str(data->path.list + i));
+ }
+ break;
+ fprintf(fp, "DELAY_MECHANISM "
+ IFMT "delayMechanism %hhu",
+ data->port_ds.delayMechanism);
+ break;
+ fprintf(fp, "LOG_MIN_PDELAY_REQ_INTERVAL "
+ IFMT "logMinPdelayReqInterval %hhd",
+ data->port_ds.logMinPdelayReqInterval);
+ break;
+ tsn = &data->tsn;
+ fprintf(fp, "TIME_STATUS_NP "
+ IFMT "master_offset %" PRId64
+ IFMT "ingress_time %" PRId64
+ IFMT "cumulativeScaledRateOffset %+.9f"
+ IFMT "scaledLastGmPhaseChange %d"
+ IFMT "gmTimeBaseIndicator %hu"
+ IFMT "lastGmPhaseChange 0x%04hx'%016" PRIx64 ".%04hx"
+ IFMT "gmPresent %s"
+ IFMT "gmIdentity %s",
+ tsn->master_offset,
+ tsn->ingress_time,
+ 1.0 + (tsn->cumulativeScaledRateOffset + 0.0) / P41,
+ tsn->scaledLastGmPhaseChange,
+ tsn->gmTimeBaseIndicator,
+ tsn->lastGmPhaseChange.nanoseconds_msb,
+ tsn->lastGmPhaseChange.nanoseconds_lsb,
+ tsn->lastGmPhaseChange.fractional_nanoseconds,
+ bool2str(tsn->gmPresent),
+ cid2str(&tsn->gmIdentity));
+ break;
+ fprintf(fp, "UNKNOWN(%d) ", tlv->mgmt.id);
+ break;
}
fprintf(fp, "\n");
static void get_action(int id)
{
- int pdulen;
struct ptp_message *msg;
- struct management_tlv *mgt;
- msg = pmc_message(GET);
+ struct tlv tlv;
+ msg = pmc_message(ACTION_GET);
if (!msg) {
return;
}
- mgt = (struct management_tlv *) msg->management.suffix;
- mgt->type = TLV_MANAGEMENT;
- mgt->length = 2;
- mgt->id = id;
- pdulen = msg->header.messageLength + sizeof(*mgt);
- msg->header.messageLength = pdulen;
- msg->tlv_count = 1;
- pmc_send(msg, pdulen);
+
+ tlv.type = TLV_MANAGEMENT;
+ tlv.mgmt.id = id;
+ tlv.mgmt.empty_body = 1;
+ tlv.mgmt.error = 0;
+ msg_add_tlv(msg, &tlv);
+
+ pmc_send(msg);
msg_put(msg);
}
static void do_get_action(int action, int index)
{
- if (action == GET)
+ if (action == ACTION_GET)
get_action(idtab[index].code);
else
fprintf(stderr, "%s only allows GET\n", idtab[index].name);
@@ -365,7 +576,7 @@ static void not_supported(int action, int index)
static void null_management(int action, int index)
{
- if (action == GET)
+ if (action == ACTION_GET)
get_action(idtab[index].code);
else
puts("non-get actions still todo");
@@ -375,13 +586,13 @@ static int parse_action(char *s)
{
int len = strlen(s);
if (0 == strncasecmp(s, "GET", len))
- return GET;
+ return ACTION_GET;
else if (0 == strncasecmp(s, "SET", len))
- return SET;
+ return ACTION_SET;
else if (0 == strncasecmp(s, "CMD", len))
- return COMMAND;
+ return ACTION_COMMAND;
else if (0 == strncasecmp(s, "COMMAND", len))
- return COMMAND;
+ return ACTION_COMMAND;
return BAD_ACTION;
}
diff --git a/port.c b/port.c
index 8b03bd9..43b1c55 100644
--- a/port.c
+++ b/port.c
@@ -71,25 +71,21 @@ struct port {
struct nrate_estimator nrate;
/* portDS */
struct port_defaults pod;
- struct PortIdentity portIdentity;
- enum port_state state; /*portState*/
- Integer8 logMinDelayReqInterval;
- TimeInterval peerMeanPathDelay;
- Integer8 logAnnounceInterval;
- UInteger8 announceReceiptTimeout;
+ struct portDS ds;
UInteger8 transportSpecific;
- Integer8 logSyncInterval;
- Enumeration8 delayMechanism;
- Integer8 logMinPdelayReqInterval;
- unsigned int versionNumber; /*UInteger4*/
+
/* foreignMasterDS */
LIST_HEAD(fm, foreign_clock) foreign_masters;
};
-#define portnum(p) (p->portIdentity.portNumber)
+#define portnum(p) (p->ds.portIdentity.portNumber)
#define NSEC2SEC 1000000000LL
+static int port_management_error_port(struct port *p, struct port *ingress,
+ struct ptp_message *req, struct tlv *req_tlv,
+ enum management_error_types error_id);
+
static int announce_compare(struct ptp_message *m1, struct ptp_message *m2)
{
struct announce_msg *a = &m1->announce, *b = &m2->announce;
@@ -275,31 +271,35 @@ static int add_foreign_master(struct port *p, struct ptp_message *m)
return broke_threshold || diff;
}
-static int follow_up_info_append(struct port *p, struct ptp_message *m)
+static void follow_up_info_append(struct port *p, struct ptp_message *m, struct tlv *tlv)
{
- struct follow_up_info_tlv *fui;
- fui = (struct follow_up_info_tlv *) m->follow_up.suffix;
- fui->type = TLV_ORGANIZATION_EXTENSION;
- fui->length = sizeof(*fui) - sizeof(fui->type) - sizeof(fui->length);
- memcpy(fui->id, ieee8021_id, sizeof(ieee8021_id));
- fui->subtype[2] = 1;
- m->tlv_count = 1;
- return sizeof(*fui);
+ struct OrgTLV *org;
+ if (tlv == NULL) {
+ return;
+ }
+ tlv->type = TLV_ORGANIZATION_EXTENSION;
+ org = &tlv->org;
+ org->id = OUI_CISCO_SYSTEMS;
+ org->subtype = SUBTYPE_AVB_FOLLOW_UP;
+ memset(&org->data.fup, 0, sizeof(org->data.fup));
+
+ msg_add_tlv(m, tlv);
}
static struct follow_up_info_tlv *follow_up_info_extract(struct ptp_message *m)
{
- struct follow_up_info_tlv *f;
- f = (struct follow_up_info_tlv *) m->follow_up.suffix;
-
- if (m->tlv_count != 1 ||
- f->type != TLV_ORGANIZATION_EXTENSION ||
- f->length != sizeof(*f) - sizeof(f->type) - sizeof(f->length) ||
-// memcmp(f->id, ieee8021_id, sizeof(ieee8021_id)) ||
- f->subtype[0] || f->subtype[1] || f->subtype[2] != 1) {
- return NULL;
+ struct tlv *tlv;
+ struct OrgTLV *org;
+ tlv = m->tlv;
+ if (tlv) {
+ if (tlv->type == TLV_ORGANIZATION_EXTENSION) {
+ org = &tlv->org;
+ if (org->id == OUI_CISCO_SYSTEMS && org->subtype == SUBTYPE_AVB_FOLLOW_UP) {
+ return &org->data.fup;
+ }
+ }
}
- return f;
+ return NULL;
}
static void free_foreign_masters(struct port *p)
@@ -312,28 +312,31 @@ static void free_foreign_masters(struct port *p)
}
}
-static int path_trace_append(struct port *p, struct ptp_message *m,
- struct parent_ds *dad)
+static void path_trace_append(struct port *p, struct ptp_message *m,
+ struct parentDS *dad, struct tlv *tlv)
{
- struct path_trace_tlv *ptt;
+ struct PathTraceTLV *ptt;
int length = 1 + dad->path_length;
if (length > PATH_TRACE_MAX) {
- return 0;
+ return;
}
- ptt = (struct path_trace_tlv *) m->announce.suffix;
- ptt->type = TLV_PATH_TRACE;
- ptt->length = length * sizeof(struct ClockIdentity);
- memcpy(ptt->cid, dad->ptl, ptt->length);
- ptt->cid[length - 1] = clock_identity(p->clock);
- m->tlv_count = 1;
- return ptt->length + sizeof(ptt->type) + sizeof(ptt->length);
+
+ tlv->type = TLV_PATH_TRACE;
+ ptt = &tlv->path_trace;
+ ptt->num = length;
+ ptt->cid = dad->ptl;
+ ptt->op = OP_APPEND;
+ ptt->op_arg.append = clock_identity(p->clock);
+
+ msg_add_tlv(m, tlv);
}
static int path_trace_ignore(struct port *p, struct ptp_message *m)
{
struct ClockIdentity cid;
- struct path_trace_tlv *ptt;
+ struct ClockIdentity *cids;
+ struct tlv *tlv;
int i, cnt;
if (!p->pod.path_trace_enabled) {
@@ -345,14 +348,15 @@ static int path_trace_ignore(struct port *p, struct ptp_message *m)
if (m->tlv_count != 1) {
return 1;
}
- ptt = (struct path_trace_tlv *) m->announce.suffix;
- if (ptt->type != TLV_PATH_TRACE) {
+ tlv = m->tlv;
+ if (tlv == NULL || tlv->type != TLV_PATH_TRACE) {
return 1;
}
- cnt = path_length(ptt);
+ cnt = path_length(tlv);
+ cids = tlv->path_trace.cid;
cid = clock_identity(p->clock);
for (i = 0; i < cnt; i++) {
- if (0 == memcmp(&ptt->cid[i], &cid, sizeof(cid)))
+ if (0 == memcmp(cids + i, &cid, sizeof(cid)))
return 1;
}
return 0;
@@ -376,7 +380,7 @@ static int port_ignore(struct port *p, struct ptp_message *m)
if (msg_transport_specific(m) != p->transportSpecific) {
return 1;
}
- if (pid_eq(&m->header.sourcePortIdentity, &p->portIdentity)) {
+ if (pid_eq(&m->header.sourcePortIdentity, &p->ds.portIdentity)) {
return 1;
}
if (m->header.domainNumber != clock_domain_number(p->clock)) {
@@ -392,63 +396,136 @@ static int port_ignore(struct port *p, struct ptp_message *m)
return 0;
}
-static int port_management_get_response(struct port *target,
- struct port *ingress, int id,
- struct ptp_message *req)
+/**
+ * negative management error code < 0 for an invalid message
+ */
+static int port_management_response(struct port *target, struct port *ingress,
+ struct tlv *tlv, struct ptp_message *req)
{
- int datalen = 0, err, pdulen, respond = 0;
- struct management_tlv *tlv;
- struct ptp_message *rsp;
- struct portDS *pds;
- struct PortIdentity pid = port_identity(target);
-
- rsp = port_management_reply(pid, ingress, req);
- if (!rsp) {
- return 0;
- }
- tlv = (struct management_tlv *) rsp->management.suffix;
- tlv->type = TLV_MANAGEMENT;
- tlv->id = id;
-
- switch (id) {
- datalen = 0;
- respond = 1;
+ struct clock *c = target->clock;
+ struct tlv rsp_tlv;
+ union ManagementTLVData *data = &rsp_tlv.mgmt.data;
+ struct ClockDescription *cdt;
+
+ rsp_tlv.type = TLV_MANAGEMENT;
+ rsp_tlv.mgmt.id = tlv->mgmt.id;
+ rsp_tlv.mgmt.empty_body = 0;
+ rsp_tlv.mgmt.error = 0;
+
+ switch (tlv->mgmt.id) {
break;
- pds = (struct portDS *) tlv->data;
- pds->portIdentity = target->portIdentity;
- pds->portState = target->state;
- pds->logMinDelayReqInterval = target->logMinDelayReqInterval;
- pds->peerMeanPathDelay = target->peerMeanPathDelay;
- pds->logAnnounceInterval = target->logAnnounceInterval;
- pds->announceReceiptTimeout = target->announceReceiptTimeout;
- pds->logSyncInterval = target->logSyncInterval;
- if (target->delayMechanism) {
- pds->delayMechanism = target->delayMechanism;
+ cdt = &data->cd;
+ if (clock_num_ports(c) == 1) {
+ cdt->clockType = CLOCK_TYPE_ORDINARY;
} else {
- pds->delayMechanism = DM_E2E;
+ /* TODO: should both ordinary and boundary
+ flags be set in this case?
+ */
+ cdt->clockType = CLOCK_TYPE_BOUNDARY;
+ }
+ /* TODO: How should the physicalLayerProtocol be set?
+ * 15.5.3.1.2.2: "indicate the physical layer protocol
+ * defining the physicalAddress member" Max 32
+ * symbols. */
+ cdt->physicalLayerProtocol.length = 0;
+
+ cdt->physicalAddress.addressField = NULL;
+ cdt->physicalAddress.addressLength = 0;
+
+ cdt->protocolAddress.networkProtocol = 0;
+ cdt->protocolAddress.addressField = NULL;
+ cdt->protocolAddress.addressLength = 0;
+
+ memset(cdt->manufacturerIdentity, 0, OUI_LEN);
+
+ ptp_text_set(&cdt->productDescription, "");
+ ptp_text_set(&cdt->revisionData, "");
+ ptp_text_set(&cdt->userDescription, "");
+
+ memset(cdt->profileIdentity, 0, sizeof(cdt->profileIdentity));
+ break;
+ data->port_ds = target->ds;
+ if (target->ds.delayMechanism == DM_AUTO) {
+ data->port_ds.delayMechanism = DM_E2E;
}
- pds->logMinPdelayReqInterval = target->logMinPdelayReqInterval;
- pds->versionNumber = target->versionNumber;
- datalen = sizeof(*pds);
- respond = 1;
break;
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
+ /* TODO: should be gettable */
+ return -MGMT_ERROR_NOT_SUPPORTED;
+ return 0;
}
- if (respond) {
- tlv->length = sizeof(tlv->id) + datalen;
- pdulen = rsp->header.messageLength + sizeof(*tlv) + datalen;
- rsp->header.messageLength = pdulen;
- rsp->tlv_count = 1;
- err = msg_pre_send(rsp);
- if (err) {
- goto out;
- }
- err = port_forward(ingress, rsp, pdulen);
+ if (port_management_reply_send(ingress, port_identity(target), req, &rsp_tlv))
+ pr_err("port %hu: failed to send management response", portnum(target));
+ return 1;
+}
+
+/**
+ * negative management error code < 0 for an invalid message
+ */
+static int port_management_set(struct port *target, struct port *ingress,
+ struct tlv *tlv, struct ptp_message *req)
+{
+ int result;
+ switch (tlv->mgmt.id) {
+ break;
+ /* TODO: These should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ /* TODO: should be settable */
+ return -MGMT_ERROR_NOT_SETABLE;
+ return 0;
}
- msg_put(rsp);
- return respond ? 1 : 0;
+ /* Set handled. Send response. */
+ if ((result = port_management_response(target, ingress, tlv, req)) != 1) {
+ pr_err("port %hu: Management SET %d handled but RESPONSE failed", portnum(target), tlv->mgmt.id);
+ if (result == 0)
+ result = 1;
+ }
+ return result;
}
static void port_nrate_calculate(struct port *p, tmv_t t3, tmv_t t4, tmv_t c)
@@ -480,7 +557,7 @@ static void port_nrate_calculate(struct port *p, tmv_t t3, tmv_t t4, tmv_t c)
static void port_nrate_initialize(struct port *p)
{
- int shift = p->pod.freq_est_interval - p->logMinPdelayReqInterval;
+ int shift = p->pod.freq_est_interval - p->ds.logMinPdelayReqInterval;
if (shift < 0)
shift = 0;
@@ -494,7 +571,7 @@ static void port_nrate_initialize(struct port *p)
static int port_set_announce_tmo(struct port *p)
{
return set_tmo(p->fda.fd[FD_ANNOUNCE_TIMER],
- p->announceReceiptTimeout, p->logAnnounceInterval);
+ p->ds.announceReceiptTimeout, p->ds.logAnnounceInterval);
}
static int port_set_delay_tmo(struct port *p)
@@ -503,9 +580,9 @@ static int port_set_delay_tmo(struct port *p)
{0, 0}, {0, 0}
};
int index;
- if (p->delayMechanism == DM_P2P) {
+ if (p->ds.delayMechanism == DM_P2P) {
return set_tmo(p->fda.fd[FD_DELAY_TIMER], 1,
- p->logMinPdelayReqInterval);
+ p->ds.logMinPdelayReqInterval);
}
index = random() % TMTAB_MAX;
tmo.it_value = p->tmtab.ts[index];
@@ -514,25 +591,25 @@ static int port_set_delay_tmo(struct port *p)
static int port_set_manno_tmo(struct port *p)
{
- return set_tmo(p->fda.fd[FD_MANNO_TIMER], 1, p->logAnnounceInterval);
+ return set_tmo(p->fda.fd[FD_MANNO_TIMER], 1, p->ds.logAnnounceInterval);
}
static int port_set_qualification_tmo(struct port *p)
{
return set_tmo(p->fda.fd[FD_QUALIFICATION_TIMER],
- 1+clock_steps_removed(p->clock), p->logAnnounceInterval);
+ 1+clock_steps_removed(p->clock), p->ds.logAnnounceInterval);
}
static int port_set_sync_tmo(struct port *p)
{
- return set_tmo(p->fda.fd[FD_SYNC_TIMER], 1, p->logSyncInterval);
+ return set_tmo(p->fda.fd[FD_SYNC_TIMER], 1, p->ds.logSyncInterval);
}
static void port_show_transition(struct port *p,
enum port_state next, enum fsm_event event)
{
pr_notice("port %hu: %s to %s on %s", portnum(p),
- ps_str[p->state], ps_str[next], ev_str[event]);
+ ps_str[p->ds.portState], ps_str[next], ev_str[event]);
}
static void port_slave_priority_warning(struct port *p)
@@ -589,11 +666,11 @@ static int port_pdelay_request(struct port *p)
msg->header.messageLength = pdulen;
msg->header.domainNumber = clock_domain_number(p->clock);
msg->header.correction = -p->pod.asymmetry;
- msg->header.sourcePortIdentity = p->portIdentity;
+ msg->header.sourcePortIdentity = p->ds.portIdentity;
msg->header.sequenceId = p->seqnum.delayreq++;
msg->header.control = CTL_OTHER;
msg->header.logMessageInterval = p->pod.follow_up_info ?
- p->logMinPdelayReqInterval : 0x7f;
+ p->ds.logMinPdelayReqInterval : 0x7f;
if (msg_pre_send(msg))
goto out;
@@ -623,7 +700,7 @@ static int port_delay_request(struct port *p)
struct ptp_message *msg;
int cnt, pdulen;
- if (p->delayMechanism == DM_P2P)
+ if (p->ds.delayMechanism == DM_P2P)
return port_pdelay_request(p);
msg = msg_allocate();
@@ -638,7 +715,7 @@ static int port_delay_request(struct port *p)
msg->header.messageLength = pdulen;
msg->header.domainNumber = clock_domain_number(p->clock);
msg->header.correction = -p->pod.asymmetry;
- msg->header.sourcePortIdentity = p->portIdentity;
+ msg->header.sourcePortIdentity = p->ds.portIdentity;
msg->header.sequenceId = p->seqnum.delayreq++;
msg->header.control = CTL_DELAY_REQ;
msg->header.logMessageInterval = 0x7f;
static int port_tx_announce(struct port *p)
{
- struct parent_ds *dad = clock_parent_ds(p->clock);
+ struct parentDS *dad = clock_parent_ds(p->clock);
struct timePropertiesDS *tp = clock_time_properties(p->clock);
struct ptp_message *msg;
- int cnt, err = 0, pdulen;
+ int cnt, err = 0;
+ struct tlv pt_tlv;
msg = msg_allocate();
if (!msg)
return -1;
- pdulen = sizeof(struct announce_msg);
msg->hwts.type = p->timestamping;
if (p->pod.path_trace_enabled)
- pdulen += path_trace_append(p, msg, dad);
+ path_trace_append(p, msg, dad, &pt_tlv);
msg->header.tsmt = ANNOUNCE | p->transportSpecific;
msg->header.ver = PTP_VERSION;
- msg->header.messageLength = pdulen;
+ msg->header.messageLength = sizeof(struct announce_msg);
msg->header.domainNumber = clock_domain_number(p->clock);
- msg->header.sourcePortIdentity = p->portIdentity;
+ msg->header.sourcePortIdentity = p->ds.portIdentity;
msg->header.sequenceId = p->seqnum.announce++;
msg->header.control = CTL_OTHER;
- msg->header.logMessageInterval = p->logAnnounceInterval;
-
- msg->header.flagField[1] = tp->flags;
+ msg->header.logMessageInterval = p->ds.logAnnounceInterval;
+
+ if (tp->leap61)
+ msg->header.flagField[1] |= LEAP_61;
+ if (tp->leap59)
+ msg->header.flagField[1] |= LEAP_59;
+ if (tp->currentUtcOffsetValid)
+ msg->header.flagField[1] |= UTC_OFF_VALID;
+ if (tp->ptpTimescale)
+ msg->header.flagField[1] |= PTP_TIMESCALE;
+ if (tp->timeTraceable)
+ msg->header.flagField[1] |= TIME_TRACEABLE;
+ if (tp->frequencyTraceable)
+ msg->header.flagField[1] |= FREQ_TRACEABLE;
msg->announce.currentUtcOffset = tp->currentUtcOffset;
- msg->announce.grandmasterPriority1 = dad->pds.grandmasterPriority1;
- msg->announce.grandmasterClockQuality = dad->pds.grandmasterClockQuality;
- msg->announce.grandmasterPriority2 = dad->pds.grandmasterPriority2;
- msg->announce.grandmasterIdentity = dad->pds.grandmasterIdentity;
+ msg->announce.grandmasterPriority1 = dad->grandmasterPriority1;
+ msg->announce.grandmasterClockQuality = dad->grandmasterClockQuality;
+ msg->announce.grandmasterPriority2 = dad->grandmasterPriority2;
+ msg->announce.grandmasterIdentity = dad->grandmasterIdentity;
msg->announce.stepsRemoved = clock_steps_removed(p->clock);
msg->announce.timeSource = tp->timeSource;
@@ -706,7 +794,7 @@ static int port_tx_announce(struct port *p)
err = -1;
goto out;
}
- cnt = transport_send(p->trp, &p->fda, 0, msg, pdulen, &msg->hwts);
+ cnt = transport_send(p->trp, &p->fda, 0, msg, msg->total_msg_len, &msg->hwts);
if (cnt <= 0) {
pr_err("port %hu: send announce failed", portnum(p));
err = -1;
static int port_tx_sync(struct port *p)
{
struct ptp_message *msg, *fup;
- int cnt, err = 0, pdulen;
+ int cnt, err = 0;
int event = p->timestamping == TS_ONESTEP ? TRANS_ONESTEP : TRANS_EVENT;
+ struct tlv tlv;
msg = msg_allocate();
if (!msg)
@@ -731,17 +820,16 @@ static int port_tx_sync(struct port *p)
return -1;
}
- pdulen = sizeof(struct sync_msg);
msg->hwts.type = p->timestamping;
msg->header.tsmt = SYNC | p->transportSpecific;
msg->header.ver = PTP_VERSION;
- msg->header.messageLength = pdulen;
+ msg->header.messageLength = sizeof(struct sync_msg);
msg->header.domainNumber = clock_domain_number(p->clock);
- msg->header.sourcePortIdentity = p->portIdentity;
+ msg->header.sourcePortIdentity = p->ds.portIdentity;
msg->header.sequenceId = p->seqnum.sync++;
msg->header.control = CTL_SYNC;
- msg->header.logMessageInterval = p->logSyncInterval;
+ msg->header.logMessageInterval = p->ds.logSyncInterval;
if (p->timestamping != TS_ONESTEP)
msg->header.flagField[0] |= TWO_STEP;
@@ -750,7 +838,7 @@ static int port_tx_sync(struct port *p)
err = -1;
goto out;
}
- cnt = transport_send(p->trp, &p->fda, event, msg, pdulen, &msg->hwts);
+ cnt = transport_send(p->trp, &p->fda, event, msg, fup->total_msg_len, &msg->hwts);
if (cnt <= 0) {
pr_err("port %hu: send sync failed", portnum(p));
err = -1;
@@ -767,20 +855,19 @@ static int port_tx_sync(struct port *p)
/*
* Send the follow up message right away.
*/
- pdulen = sizeof(struct follow_up_msg);
fup->hwts.type = p->timestamping;
if (p->pod.follow_up_info)
- pdulen += follow_up_info_append(p, fup);
+ follow_up_info_append(p, fup, &tlv);
fup->header.tsmt = FOLLOW_UP | p->transportSpecific;
fup->header.ver = PTP_VERSION;
- fup->header.messageLength = pdulen;
+ fup->header.messageLength = sizeof(struct follow_up_msg);
fup->header.domainNumber = clock_domain_number(p->clock);
- fup->header.sourcePortIdentity = p->portIdentity;
+ fup->header.sourcePortIdentity = p->ds.portIdentity;
fup->header.sequenceId = p->seqnum.sync - 1;
fup->header.control = CTL_FOLLOW_UP;
- fup->header.logMessageInterval = p->logSyncInterval;
+ fup->header.logMessageInterval = p->ds.logSyncInterval;
ts_to_timestamp(&msg->hwts.ts, &fup->follow_up.preciseOriginTimestamp);
@@ -788,7 +875,7 @@ static int port_tx_sync(struct port *p)
err = -1;
goto out;
}
- cnt = transport_send(p->trp, &p->fda, 0, fup, pdulen, &fup->hwts);
+ cnt = transport_send(p->trp, &p->fda, 0, fup, fup->total_msg_len, &fup->hwts);
if (cnt <= 0) {
pr_err("port %hu: send follow up failed", portnum(p));
err = -1;
*/
static int port_is_enabled(struct port *p)
{
- switch (p->state) {
+ switch (p->ds.portState) {
@@ -864,15 +951,15 @@ static int port_initialize(struct port *p)
{
int fd[N_TIMER_FDS], i;
- p->logMinDelayReqInterval = p->pod.logMinDelayReqInterval;
- p->peerMeanPathDelay = 0;
- p->logAnnounceInterval = p->pod.logAnnounceInterval;
- p->announceReceiptTimeout = p->pod.announceReceiptTimeout;
+ p->ds.logMinDelayReqInterval = p->pod.logMinDelayReqInterval;
+ p->ds.peerMeanPathDelay = 0;
+ p->ds.logAnnounceInterval = p->pod.logAnnounceInterval;
+ p->ds.announceReceiptTimeout = p->pod.announceReceiptTimeout;
p->transportSpecific = p->pod.transportSpecific;
- p->logSyncInterval = p->pod.logSyncInterval;
- p->logMinPdelayReqInterval = p->pod.logMinPdelayReqInterval;
+ p->ds.logSyncInterval = p->pod.logSyncInterval;
+ p->ds.logMinPdelayReqInterval = p->pod.logMinPdelayReqInterval;
- tmtab_init(&p->tmtab, 1 + p->logMinDelayReqInterval);
+ tmtab_init(&p->tmtab, 1 + p->ds.logMinDelayReqInterval);
for (i = 0; i < N_TIMER_FDS; i++) {
fd[i] = -1;
@@ -931,17 +1018,21 @@ static int update_current_master(struct port *p, struct ptp_message *m)
{
struct foreign_clock *fc = p->best;
struct ptp_message *tmp;
- struct parent_ds *dad;
- struct path_trace_tlv *ptt;
+ struct parentDS *dad;
+ struct tlv *tlv;
if (!msg_source_equal(m, fc))
return add_foreign_master(p, m);
if (p->pod.path_trace_enabled) {
- ptt = (struct path_trace_tlv *) m->announce.suffix;
dad = clock_parent_ds(p->clock);
- memcpy(dad->ptl, ptt->cid, ptt->length);
- dad->path_length = path_length(ptt);
+ tlv = m->tlv;
+ if (tlv && tlv->type == TLV_PATH_TRACE) {
+ memcpy(dad->ptl, tlv->path_trace.cid, tlv->length);
+ dad->path_length = path_length(tlv);
+ } else {
+ dad->path_length = 0;
+ }
}
port_set_announce_tmo(p);
fc_prune(fc);
@@ -968,7 +1059,7 @@ struct dataset *port_best_foreign(struct port *port)
static int process_announce(struct port *p, struct ptp_message *m)
{
int result = 0;
- switch (p->state) {
+ switch (p->ds.portState) {
@@ -993,10 +1084,10 @@ static int process_delay_req(struct port *p, struct ptp_message *m)
struct ptp_message *msg;
int cnt, err = 0, pdulen;
- if (p->state != PS_MASTER && p->state != PS_GRAND_MASTER)
+ if (p->ds.portState != PS_MASTER && p->ds.portState != PS_GRAND_MASTER)
return 0;
- if (p->delayMechanism == DM_P2P) {
+ if (p->ds.delayMechanism == DM_P2P) {
pr_warning("port %hu: delay request on P2P port", portnum(p));
return 0;
}
@@ -1013,10 +1104,10 @@ static int process_delay_req(struct port *p, struct ptp_message *m)
msg->header.messageLength = pdulen;
msg->header.domainNumber = m->header.domainNumber;
msg->header.correction = m->header.correction;
- msg->header.sourcePortIdentity = p->portIdentity;
+ msg->header.sourcePortIdentity = p->ds.portIdentity;
msg->header.sequenceId = m->header.sequenceId;
msg->header.control = CTL_DELAY_RESP;
- msg->header.logMessageInterval = p->logMinDelayReqInterval;
+ msg->header.logMessageInterval = p->ds.logMinDelayReqInterval;
ts_to_timestamp(&m->hwts.ts, &msg->delay_resp.receiveTimestamp);
@@ -1046,7 +1137,7 @@ static void process_delay_resp(struct port *p, struct ptp_message *m)
req = &p->delay_req->delay_req;
- if (p->state != PS_UNCALIBRATED && p->state != PS_SLAVE)
+ if (p->ds.portState != PS_UNCALIBRATED && p->ds.portState != PS_SLAVE)
return;
if (!pid_eq(&rsp->requestingPortIdentity, &req->hdr.sourcePortIdentity))
return;
@@ -1056,12 +1147,12 @@ static void process_delay_resp(struct port *p, struct ptp_message *m)
clock_path_delay(p->clock, p->delay_req->hwts.ts, m->ts.pdu,
m->header.correction);
- if (p->logMinDelayReqInterval != rsp->hdr.logMessageInterval) {
+ if (p->ds.logMinDelayReqInterval != rsp->hdr.logMessageInterval) {
// TODO - validate the input.
- p->logMinDelayReqInterval = rsp->hdr.logMessageInterval;
+ p->ds.logMinDelayReqInterval = rsp->hdr.logMessageInterval;
pr_notice("port %hu: minimum delay request interval 2^%d",
- portnum(p), p->logMinDelayReqInterval);
- tmtab_init(&p->tmtab, 1 + p->logMinDelayReqInterval);
+ portnum(p), p->ds.logMinDelayReqInterval);
+ tmtab_init(&p->tmtab, 1 + p->ds.logMinDelayReqInterval);
}
}
@@ -1069,7 +1160,7 @@ static void process_follow_up(struct port *p, struct ptp_message *m)
{
struct ptp_message *syn;
struct PortIdentity master, *pid;
- switch (p->state) {
+ switch (p->ds.portState) {
@@ -1122,13 +1213,13 @@ static int process_pdelay_req(struct port *p, struct ptp_message *m)
struct ptp_message *rsp, *fup;
int cnt, err = -1, rsp_len, fup_len;
- if (p->delayMechanism == DM_E2E) {
+ if (p->ds.delayMechanism == DM_E2E) {
pr_warning("port %hu: pdelay_req on E2E port", portnum(p));
return 0;
}
- if (p->delayMechanism == DM_AUTO) {
+ if (p->ds.delayMechanism == DM_AUTO) {
pr_info("port %hu: peer detected, switch to P2P", portnum(p));
- p->delayMechanism = DM_P2P;
+ p->ds.delayMechanism = DM_P2P;
}
rsp = msg_allocate();
@@ -1147,7 +1238,7 @@ static int process_pdelay_req(struct port *p, struct ptp_message *m)
rsp->header.ver = PTP_VERSION;
rsp->header.messageLength = rsp_len;
rsp->header.domainNumber = m->header.domainNumber;
- rsp->header.sourcePortIdentity = p->portIdentity;
+ rsp->header.sourcePortIdentity = p->ds.portIdentity;
rsp->header.sequenceId = m->header.sequenceId;
rsp->header.control = CTL_OTHER;
rsp->header.logMessageInterval = 0x7f;
@@ -1172,7 +1263,7 @@ static int process_pdelay_req(struct port *p, struct ptp_message *m)
fup->header.messageLength = fup_len;
fup->header.domainNumber = m->header.domainNumber;
fup->header.correction = m->header.correction;
- fup->header.sourcePortIdentity = p->portIdentity;
+ fup->header.sourcePortIdentity = p->ds.portIdentity;
fup->header.sequenceId = m->header.sequenceId;
fup->header.control = CTL_OTHER;
fup->header.logMessageInterval = 0x7f;
@@ -1222,7 +1313,7 @@ static void port_peer_delay(struct port *p)
if (!rsp)
return;
- if (!pid_eq(&rsp->pdelay_resp.requestingPortIdentity, &p->portIdentity))
+ if (!pid_eq(&rsp->pdelay_resp.requestingPortIdentity, &p->ds.portIdentity))
return;
if (rsp->header.sequenceId != ntohs(req->header.sequenceId))
@@ -1245,7 +1336,7 @@ static void port_peer_delay(struct port *p)
if (!fup)
return;
- if (!pid_eq(&fup->pdelay_resp_fup.requestingPortIdentity, &p->portIdentity))
+ if (!pid_eq(&fup->pdelay_resp_fup.requestingPortIdentity, &p->ds.portIdentity))
return;
if (fup->header.sequenceId != rsp->header.sequenceId)
p->peer_delay = mave_accumulate(p->avg_delay, pd);
- p->peerMeanPathDelay = tmv_to_TimeInterval(p->peer_delay);
+ p->ds.peerMeanPathDelay = tmv_to_TimeInterval(p->peer_delay);
pr_debug("pdelay %hu %10lld %10lld", portnum(p), p->peer_delay, pd);
if (p->pod.follow_up_info)
port_nrate_calculate(p, t3, t4, tmv_add(c1, c2));
- if (p->state == PS_UNCALIBRATED || p->state == PS_SLAVE) {
+ if (p->ds.portState == PS_UNCALIBRATED || p->ds.portState == PS_SLAVE) {
clock_peer_delay(p->clock, p->peer_delay, p->nrate.ratio);
}
}
@@ -1315,7 +1406,7 @@ static void process_sync(struct port *p, struct ptp_message *m)
{
struct ptp_message *fup;
struct PortIdentity master;
- switch (p->state) {
+ switch (p->ds.portState) {
@@ -1414,9 +1505,9 @@ void port_dispatch(struct port *p, enum fsm_event event, int mdiff)
if (event == EV_RS_MASTER || event == EV_RS_GRAND_MASTER) {
port_slave_priority_warning(p);
}
- next = ptp_slave_fsm(p->state, event, mdiff);
+ next = ptp_slave_fsm(p->ds.portState, event, mdiff);
} else {
- next = ptp_fsm(p->state, event, mdiff);
+ next = ptp_fsm(p->ds.portState, event, mdiff);
}
if (PS_INITIALIZING == next) {
@@ -1430,11 +1521,11 @@ void port_dispatch(struct port *p, enum fsm_event event, int mdiff)
}
next = port_initialize(p) ? PS_FAULTY : PS_LISTENING;
port_show_transition(p, next, event);
- p->state = next;
+ p->ds.portState = next;
return;
}
- if (next == p->state)
+ if (next == p->ds.portState)
return;
port_show_transition(p, next, event);
@@ -1472,7 +1563,7 @@ void port_dispatch(struct port *p, enum fsm_event event, int mdiff)
port_set_delay_tmo(p);
break;
};
- if (p->delayMechanism == DM_P2P) {
+ if (p->ds.delayMechanism == DM_P2P) {
switch (next) {
@@ -1491,7 +1582,7 @@ void port_dispatch(struct port *p, enum fsm_event event, int mdiff)
break;
};
}
- p->state = next;
+ p->ds.portState = next;
}
enum fsm_event port_event(struct port *p, int fd_index)
@@ -1602,91 +1693,63 @@ int port_forward(struct port *p, struct ptp_message *msg, int msglen)
struct PortIdentity port_identity(struct port *p)
{
- return p->portIdentity;
+ return p->ds.portIdentity;
}
+/**
+ * other ports,
+ * 0 if message was not handled,
+ * -1 if message is invalid somehow
+ */
int port_manage(struct port *p, struct port *ingress, struct ptp_message *msg)
{
- struct management_tlv *mgt;
+ int result;
+ struct tlv *tlv = msg->tlv;
UInteger16 target = msg->management.targetPortIdentity.portNumber;
-
if (target != portnum(p) && target != 0xffff) {
return 0;
}
- mgt = (struct management_tlv *) msg->management.suffix;
-
+ result = 0;
switch (management_action(msg)) {
- if (port_management_get_response(p, ingress, mgt->id, msg))
- return 0;
+ result = port_management_response(p, ingress, tlv, msg);
break;
- if (port_managment_error(p->portIdentity, ingress, msg,
- NOT_SUPPORTED))
- pr_err("port %hu: management error failed", portnum(p));
+ result = port_management_set(p, ingress, tlv, msg);
+ break;
+ /* TODO: Support ENABLE_PORT and DISABLE_PORT */
+ result = -MGMT_ERROR_NOT_SUPPORTED;
break;
- return -1;
}
- switch (mgt->id) {
- if (port_managment_error(p->portIdentity, ingress, msg,
- NOT_SUPPORTED))
- pr_err("port %hu: management error failed", portnum(p));
- break;
- return -1;
+ if (result < 0) {
+ port_management_error_port(p, ingress, msg, tlv, -result);
}
- return 0;
+ return result;
}
-int port_managment_error(struct PortIdentity pid, struct port *ingress,
- struct ptp_message *req, Enumeration16 error_id)
+static int port_management_error_port(struct port *p, struct port *ingress,
+ struct ptp_message *req, struct tlv *req_tlv,
+ enum management_error_types error_id)
{
- struct ptp_message *msg;
- struct management_tlv *mgt;
- struct management_error_status *mes;
- int err = 0, pdulen;
+ int result = port_management_error(p->ds.portIdentity, ingress, req, req_tlv, error_id);
+ if (result)
+ pr_err("port %hu: management error failed: %d", portnum(p), error_id);
+ return result;
+}
- msg = port_management_reply(pid, ingress, req);
- if (!msg) {
- return -1;
- }
- mgt = (struct management_tlv *) req->management.suffix;
- mes = (struct management_error_status *) msg->management.suffix;
- mes->type = TLV_MANAGEMENT_ERROR_STATUS;
- mes->length = 8;
- mes->error = error_id;
- mes->id = mgt->id;
- pdulen = msg->header.messageLength + sizeof(*mes);
- msg->header.messageLength = pdulen;
- msg->tlv_count = 1;
-
- err = msg_pre_send(msg);
- if (err) {
- goto out;
- }
- err = port_forward(ingress, msg, pdulen);
- msg_put(msg);
- return err;
+int port_management_error(struct PortIdentity pid, struct port *ingress,
+ struct ptp_message *req, struct tlv *req_tlv,
+ enum management_error_types error_id)
+{
+ struct tlv tlv;
+ tlv.type = TLV_MANAGEMENT_ERROR_STATUS;
+ tlv.error.managementErrorId = error_id;
+ tlv.error.managementId = req_tlv->mgmt.id;
+ ptp_text_set(&tlv.error.displayData, NULL);
+ return port_management_reply_send(ingress, pid, req, &tlv);
}
struct ptp_message *port_management_reply(struct PortIdentity pid,
@@ -1718,16 +1781,35 @@ struct ptp_message *port_management_reply(struct PortIdentity pid,
msg->management.boundaryHops = msg->management.startingBoundaryHops;
switch (management_action(req)) {
- msg->management.flags = RESPONSE;
+ msg->management.flags = ACTION_RESPONSE;
break;
- msg->management.flags = ACKNOWLEDGE;
+ msg->management.flags = ACTION_ACKNOWLEDGE;
+ break;
+ msg_put(msg);
+ msg = NULL;
break;
}
return msg;
}
+int port_management_reply_send(struct port *p, struct PortIdentity pid,
+ struct ptp_message *req, struct tlv *tlv)
+{
+ int err = -1;
+ struct ptp_message *rsp = port_management_reply(pid, p, req);
+ if (rsp) {
+ msg_add_tlv(rsp, tlv);
+ if (msg_pre_send(rsp) == 0) {
+ err = port_forward(p, rsp, rsp->total_msg_len);
+ }
+ msg_put(rsp);
+ }
+ return err;
+}
+
struct port *port_open(int phc_index,
enum timestamp_type timestamping,
int number,
@@ -1762,11 +1844,11 @@ struct port *port_open(int phc_index,
return NULL;
}
p->timestamping = timestamping;
- p->portIdentity.clockIdentity = clock_identity(clock);
- p->portIdentity.portNumber = number;
- p->state = PS_INITIALIZING;
- p->delayMechanism = interface->dm;
- p->versionNumber = PTP_VERSION;
+ p->ds.portIdentity.clockIdentity = clock_identity(clock);
+ p->ds.portIdentity.portNumber = number;
+ p->ds.portState = PS_INITIALIZING;
+ p->ds.delayMechanism = interface->dm;
+ p->ds.versionNumber = PTP_VERSION;
p->avg_delay = mave_create(PORT_MAVE_LENGTH);
if (!p->avg_delay) {
@@ -1781,5 +1863,5 @@ struct port *port_open(int phc_index,
enum port_state port_state(struct port *port)
{
- return port->state;
+ return port->ds.portState;
}
diff --git a/port.h b/port.h
index 2fc71e4..397a02e 100644
--- a/port.h
+++ b/port.h
@@ -28,6 +28,7 @@
/* forward declarations */
struct interface;
struct clock;
+struct tlv;
/** Opaque type. */
struct port;
@@ -111,11 +112,13 @@ int port_manage(struct port *p, struct port *ingress, struct ptp_message *msg);
*/
-int port_managment_error(struct PortIdentity pid, struct port *ingress,
- struct ptp_message *req, Enumeration16 error_id);
+int port_management_error(struct PortIdentity pid, struct port *ingress,
+ struct ptp_message *req, struct tlv *req_tlv,
+ enum management_error_types error_id);
/**
* Allocate a reply to a management message.
@@ -134,6 +137,21 @@ struct ptp_message *port_management_reply(struct PortIdentity pid,
struct ptp_message *req);
/**
+ * Creates and sends a reply to a management message.
+ *
+ * will be sent to.
+ */
+int port_management_reply_send(struct port *p,
+ struct PortIdentity pid,
+ struct ptp_message *req,
+ struct tlv *tlv);
+
+/**
* Open a network port.
diff --git a/ptp4l.c b/ptp4l.c
index 4fc0c88..7506ba0 100644
--- a/ptp4l.c
+++ b/ptp4l.c
@@ -41,15 +41,14 @@ static int running = 1;
static struct config cfg_settings = {
.dds = {
- .dds = {
- .flags = DDS_TWO_STEP_FLAG,
- .priority1 = 128,
- .clockQuality.clockClass = 248,
- .clockQuality.clockAccuracy = 0xfe,
- .clockQuality.offsetScaledLogVariance = 0xffff,
- .priority2 = 128,
- .domainNumber = 0,
- },
+ .twoStepFlag = TRUE,
+ .slaveOnly = FALSE,
+ .priority1 = 128,
+ .clockQuality.clockClass = 248,
+ .clockQuality.clockAccuracy = 0xfe,
+ .clockQuality.offsetScaledLogVariance = 0xffff,
+ .priority2 = 128,
+ .domainNumber = 0,
.free_running = 0,
.freq_est_interval = 1,
},
@@ -140,7 +139,7 @@ int main(int argc, char *argv[])
enum transport_type *transport = &cfg_settings.transport;
enum timestamp_type *timestamping = &cfg_settings.timestamping;
struct clock *clock;
- struct defaultDS *ds = &cfg_settings.dds.dds;
+ struct defaultDS *ds = &cfg_settings.dds;
int phc_index = -1, required_modes = 0;
if (SIG_ERR == signal(SIGINT, handle_int_quit_term)) {
@@ -208,7 +207,7 @@ int main(int argc, char *argv[])
req_phc = optarg;
break;
- ds->flags |= DDS_SLAVE_ONLY;
+ ds->slaveOnly = TRUE;
*cfg_ignore |= CFG_IGNORE_SLAVEONLY;
break;
@@ -241,7 +240,7 @@ int main(int argc, char *argv[])
if (config && (c = config_read(config, &cfg_settings))) {
return c;
}
- if (ds->flags & DDS_SLAVE_ONLY) {
+ if (ds->slaveOnly) {
ds->clockQuality.clockClass = 255;
}
@@ -262,7 +261,7 @@ int main(int argc, char *argv[])
return -1;
}
- if (!(ds->flags & DDS_TWO_STEP_FLAG)) {
+ if (!ds->twoStepFlag) {
switch (*timestamping) {
@@ -308,7 +307,7 @@ int main(int argc, char *argv[])
}
/* determine PHC Clock index */
- if (cfg_settings.dds.free_running) {
+ if (ds->free_running) {
phc_index = -1;
} else if (*timestamping == TS_SOFTWARE || *timestamping == TS_LEGACY_HW) {
phc_index = -1;
@@ -336,7 +335,7 @@ int main(int argc, char *argv[])
}
clock = clock_create(phc_index, iface, cfg_settings.nports,
- *timestamping, &cfg_settings.dds,
+ *timestamping, ds,
cfg_settings.clock_servo);
if (!clock) {
fprintf(stderr, "failed to create a clock\n");
diff --git a/tlv.c b/tlv.c
index 7b19969..363c160 100644
--- a/tlv.c
+++ b/tlv.c
@@ -16,247 +16,680 @@
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
-#include <arpa/inet.h>
-#include <string.h>
-
-#include "port.h"
#include "tlv.h"
+#include <string.h>
-uint8_t ieee8021_id[3] = { IEEE_802_1_COMMITTEE };
+#include "tlv_pack.h"
-static void scaled_ns_n2h(ScaledNs *sns)
-{
- sns->nanoseconds_msb = ntohs(sns->nanoseconds_msb);
- sns->nanoseconds_lsb = net2host64(sns->nanoseconds_msb);
- sns->fractional_nanoseconds = ntohs(sns->fractional_nanoseconds);
+static int packed_size_fault_record(const struct FaultRecord *r) {
+ int offset = 0;
+ offset += 2;
+ offset += 10;
+ offset += 1;
+ offset += 1 + r->faultName.length;
+ offset += 1 + r->faultValue.length;
+ offset += 1 + r->faultDescription.length;
+ return offset;
}
-static void scaled_ns_h2n(ScaledNs *sns)
-{
- sns->nanoseconds_msb = htons(sns->nanoseconds_msb);
- sns->nanoseconds_lsb = host2net64(sns->nanoseconds_msb);
- sns->fractional_nanoseconds = htons(sns->fractional_nanoseconds);
-}
+static int packFaultLog(const struct FaultLog *data, Octet *buf, int len) {
+ int offset = 0;
+ uint16_t num_records = 0;
+ int rec_len = 0;
+ struct FaultRecord *rec;
-static void mgt_post_recv(struct management_tlv *m)
-{
- struct defaultDS *dds;
- struct currentDS *cds;
- struct parentDS *pds;
- struct timePropertiesDS *tp;
- struct portDS *p;
- struct time_status_np *tsn;
- switch (m->id) {
- dds = (struct defaultDS *) m->data;
- dds->numberPorts = ntohs(dds->numberPorts);
- dds->clockQuality.offsetScaledLogVariance =
- ntohs(dds->clockQuality.offsetScaledLogVariance);
- break;
- cds = (struct currentDS *) m->data;
- cds->stepsRemoved = ntohs(cds->stepsRemoved);
- cds->offsetFromMaster = net2host64(cds->offsetFromMaster);
- cds->meanPathDelay = net2host64(cds->meanPathDelay);
- break;
- pds = (struct parentDS *) m->data;
- pds->parentPortIdentity.portNumber =
- ntohs(pds->parentPortIdentity.portNumber);
- pds->observedParentOffsetScaledLogVariance =
- ntohs(pds->observedParentOffsetScaledLogVariance);
- pds->observedParentClockPhaseChangeRate =
- ntohl(pds->observedParentClockPhaseChangeRate);
- pds->grandmasterClockQuality.offsetScaledLogVariance =
- ntohs(pds->grandmasterClockQuality.offsetScaledLogVariance);
- break;
- tp = (struct timePropertiesDS *) m->data;
- tp->currentUtcOffset = ntohs(tp->currentUtcOffset);
- break;
- p = (struct portDS *) m->data;
- p->portIdentity.portNumber = ntohs(p->portIdentity.portNumber);
- p->peerMeanPathDelay = net2host64(p->peerMeanPathDelay);
- break;
- tsn = (struct time_status_np *) m->data;
- tsn->master_offset = net2host64(tsn->master_offset);
- tsn->ingress_time = net2host64(tsn->ingress_time);
- tsn->cumulativeScaledRateOffset = ntohl(tsn->cumulativeScaledRateOffset);
- tsn->scaledLastGmPhaseChange = ntohl(tsn->scaledLastGmPhaseChange);
- tsn->gmTimeBaseIndicator = ntohs(tsn->gmTimeBaseIndicator);
- scaled_ns_n2h(&tsn->lastGmPhaseChange);
- tsn->gmPresent = ntohl(tsn->gmPresent);
- break;
+ switch (data->type) {
+ /* pack a linked list of record lists. If the list is
+ * too long to fit in the list, skip the records
+ * earlier in the list until the remaining will fit.
+ *
+ * TODO: Is this the right thing to do? This behaviour
+ * isn't in the spec and assumes that records later in
+ * the list occurred later and that faults that are
+ * older are less important.
+ */
+
+ /* Find the first record that we can include by
+ * iterating over records in reverse and summing
+ * their size */
+ offset = 2; /* account for numberRecords field */
+ TAILQ_FOREACH_REVERSE(rec, &data->list, FaultRecordList, entry) {
+ rec_len = packed_size_fault_record(rec);
+ /* record rec len in record so it doesn't need
+ to be recalculated when writing. - 2
+ because it doesn't include length field */
+ rec->faultRecordLength = rec_len - 2;
+ offset += rec_len;
+ if (offset > len) {
+ /* move back to the first record that
+ can be sent */
+ rec = TAILQ_NEXT(rec, entry);
+ break;
+ }
+ num_records++;
+ }
+ if (rec == NULL) {
+ /* Can fit all records, so send from the first */
+ rec = TAILQ_FIRST(&data->list);
+ }
+
+ packUInteger16(&num_records, buf);
+ offset = 2; /* reset to start writing values */
+ for (; rec; rec = TAILQ_NEXT(rec, entry)) {
+ offset += packFaultRecord(rec, buf + offset);
+ }
+ return offset;
+ if (len < 2 + data->buffer.len)
+ return -1;
+
+ packUInteger16(&data->numberRecords, buf + offset);
+ offset += 2;
+ if (data->buffer.len) {
+ packOctetPtr(&data->buffer.buf, buf, data->buffer.len);
+ offset += data->buffer.len;
+ }
+ return offset;
+ return -1;
}
}
-static void mgt_pre_send(struct management_tlv *m)
-{
- struct defaultDS *dds;
- struct currentDS *cds;
- struct parentDS *pds;
- struct timePropertiesDS *tp;
- struct portDS *p;
- struct time_status_np *tsn;
- switch (m->id) {
- dds = (struct defaultDS *) m->data;
- dds->numberPorts = htons(dds->numberPorts);
- dds->clockQuality.offsetScaledLogVariance =
- htons(dds->clockQuality.offsetScaledLogVariance);
- break;
- cds = (struct currentDS *) m->data;
- cds->stepsRemoved = htons(cds->stepsRemoved);
- cds->offsetFromMaster = host2net64(cds->offsetFromMaster);
- cds->meanPathDelay = host2net64(cds->meanPathDelay);
- break;
- pds = (struct parentDS *) m->data;
- pds->parentPortIdentity.portNumber =
- htons(pds->parentPortIdentity.portNumber);
- pds->observedParentOffsetScaledLogVariance =
- htons(pds->observedParentOffsetScaledLogVariance);
- pds->observedParentClockPhaseChangeRate =
- htonl(pds->observedParentClockPhaseChangeRate);
- pds->grandmasterClockQuality.offsetScaledLogVariance =
- htons(pds->grandmasterClockQuality.offsetScaledLogVariance);
- break;
- tp = (struct timePropertiesDS *) m->data;
- tp->currentUtcOffset = htons(tp->currentUtcOffset);
- break;
- p = (struct portDS *) m->data;
- p->portIdentity.portNumber = htons(p->portIdentity.portNumber);
- p->peerMeanPathDelay = host2net64(p->peerMeanPathDelay);
- break;
- tsn = (struct time_status_np *) m->data;
- tsn->master_offset = host2net64(tsn->master_offset);
- tsn->ingress_time = host2net64(tsn->ingress_time);
- tsn->cumulativeScaledRateOffset = htonl(tsn->cumulativeScaledRateOffset);
- tsn->scaledLastGmPhaseChange = htonl(tsn->scaledLastGmPhaseChange);
- tsn->gmTimeBaseIndicator = htons(tsn->gmTimeBaseIndicator);
- scaled_ns_h2n(&tsn->lastGmPhaseChange);
- tsn->gmPresent = htonl(tsn->gmPresent);
- break;
+static int unpackFaultLog(const Octet *buf, int len, struct FaultLog *data) {
+ int i;
+ uint16_t rec_len;
+ int offset = 0;
+ unpackUInteger16(buf, &data->numberRecords);
+ offset += 2;
+ data->type = FAULT_RECORD_BUFFER;
+ data->buffer.buf = (Octet*)buf + offset;
+
+ /* Sum up lengths of fault records */
+ for (i = 0; i < data->numberRecords; i++) {
+ /* Ensure malformed TLV doesn't make us read outside
+ * the TLV bounds */
+ if (offset > len - 2)
+ return -1;
+ unpackUInteger16(buf + offset, &rec_len);
+ offset += 2 + rec_len;
}
+ data->buffer.len = offset - 2;
+ return offset;
}
-static void org_post_recv(struct organization_tlv *org)
-{
- struct follow_up_info_tlv *f;
+/* check that a clock descriptions address lengths are valid */
+static int check_clock_description(const struct ClockDescription *cd) {
+ if (cd->physicalAddress.addressLength > MAX_PORT_ADDR_LEN) {
+ return -1;
+ }
+ if (cd->protocolAddress.addressLength > MAX_PORT_ADDR_LEN) {
+ return -1;
+ }
+ return 0;
+}
- if (0 == memcmp(org->id, ieee8021_id, sizeof(ieee8021_id))) {
- if (org->subtype[0] || org->subtype[1]) {
- return;
- }
- switch (org->subtype[2]) {
- f = (struct follow_up_info_tlv *) org;
- f->cumulativeScaledRateOffset = ntohl(f->cumulativeScaledRateOffset);
- f->gmTimeBaseIndicator = ntohs(f->gmTimeBaseIndicator);
- scaled_ns_n2h(&f->lastGmPhaseChange);
- f->scaledLastGmPhaseChange = ntohl(f->scaledLastGmPhaseChange);
- break;
+static int tlv_write_mgmt_data(enum management_types type,
+ const union ManagementTLVData *data, uint8_t *buf, int len) {
+ int n;
+ switch (type) {
+ return 0;
+ if (check_clock_description(&data->cd)) return -1;
+ if (len < 2 + 1 + data->cd.physicalLayerProtocol.length + 2 +
+ data->cd.physicalAddress.addressLength + 4 +
+ data->cd.protocolAddress.addressLength + 3 + 1 + 1 +
+ data->cd.productDescription.length + 1 +
+ data->cd.revisionData.length + 1 +
+ data->cd.userDescription.length + 6)
+ {
+ return -1;
}
+
+ return packClockDescription(&data->cd, buf);
+ if (len < 1 + data->cd.userDescription.length)
+ return -1;
+ packPTPText(&data->cd.userDescription, buf);
+ return 1 + data->cd.userDescription.length;
+ return 0;
+ if (len < 2)
+ return -1;
+ packUInteger16(&data->ik.initializeKey, buf);
+ return 2;
+ return packFaultLog(&data->fl, buf, len);
+ return 0;
+ if (len < 1 + 1 + 2 + 1 + 4 + 1 + CLOCK_IDENTITY_LENGTH + 1 + 1)
+ return -1;
+ return packDefaultDataSet(&data->dds, buf);
+ if (len < 2 + 8 + 8)
+ return -1;
+ return packCurrentDataSet(&data->cds, buf);
+ if (len < 10 + 1 + 1 + 2 + 4 + 1 + 4 + 1 + CLOCK_IDENTITY_LENGTH)
+ return -1;
+ return packParentDataSet(&data->parent_ds, buf);
+ if (len < 2 + 1 + 1)
+ return -1;
+ return packTimePropertiesDataSet(&data->tpds, buf);
+ if (len < 10 + 1 + 1 + 8 + 1 + 1 + 1 + 1 + 1 + 1)
+ return -1;
+ return packPortDataSet(&data->port_ds, buf);
+ if (len < 1) return -1;
+ packUInteger8(&data->dds.priority1, buf);
+ return 1;
+ if (len < 1) return -1;
+ packUInteger8(&data->dds.priority2, buf);
+ return 1;
+ if (len < 1) return -1;
+ packUInteger8(&data->dds.domainNumber, buf);
+ return 1;
+ if (len < 1) return -1;
+ packBoolean(&data->dds.slaveOnly, buf);
+ return 1;
+ if (len < 1) return -1;
+ packInteger8(&data->port_ds.logAnnounceInterval, buf);
+ return 1;
+ if (len < 1) return -1;
+ packUInteger8(&data->port_ds.announceReceiptTimeout, buf);
+ return 1;
+ if (len < 1) return -1;
+ packInteger8(&data->port_ds.logSyncInterval, buf);
+ return 1;
+ if (len < 1) return -1;
+ packUInteger4Lower(&data->port_ds.versionNumber, buf);
+ return 1;
+ return 0;
+ if (len < 10) return -1;
+ packTimestamp(&data->time.currentTime, buf);
+ return 10;
+ if (len < 2) return -1;
+ packEnumeration8(&data->ca.clockAccuracy, buf);
+ packReserved(buf + 1, 1);
+ return 2;
+ if (len < 2 + 1 + 1) return -1;
+ return packUTCProperties(&data->tpds, buf);
+ if (len < 1 + 1) return -1;
+ return packTraceabilityProperties(&data->tpds, buf);
+ if (len < 1 + 1) return -1;
+ return packTimescaleProperties(&data->tpds, buf);
+ break; /* unimplemented */
+ n = data->path.num * sizeof(struct ClockIdentity);
+ if (len < n) return -1;
+ memmove(buf, data->path.list, n);
+ return n;
+ break; /* unimplemented */
+ break; /* unimplemented */
+ break; /* unimplemented */
+ break; /* unimplemented */
+ break; /* unimplemented */
+ break; /* unimplemented */
+ break; /* unimplemented */
+ break; /* unimplemented */
+ break; /* unimplemented */
+ if (len < 1) return -1;
+ packEnumeration8(&data->port_ds.delayMechanism, buf);
+ return 1;
+ if (len < 1) return -1;
+ packInteger8(&data->port_ds.logMinPdelayReqInterval, buf);
+ return 1;
+ if (len < 8 + 8 + 4 + 4 + 2 + 12 + 4 + CLOCK_IDENTITY_LENGTH)
+ return -1;
+ return packTimeStatusNP(&data->tsn, buf);
}
+ return -1;
}
-static void org_pre_send(struct organization_tlv *org)
-{
- struct follow_up_info_tlv *f;
+int tlv_read_mgmt_data(enum management_types type,
+ const uint8_t *buf, int len, union ManagementTLVData *data) {
+ switch (type) {
+ return 0;
+ return unpackClockDescription(buf, &data->cd);
+ unpackPTPText(buf, &data->cd.userDescription);
+ return 1 + data->cd.userDescription.length;
+ return 0;
+ unpackUInteger16(buf, &data->ik.initializeKey);
+ return 2;
+ return unpackFaultLog(buf, len, &data->fl);
+ return 0;
+ return unpackDefaultDataSet(buf, &data->dds);
+ return unpackCurrentDataSet(buf, &data->cds);
+ return unpackParentDataSet(buf, &data->parent_ds);
+ return unpackTimePropertiesDataSet(buf, &data->tpds);
+ return unpackPortDataSet(buf, &data->port_ds);
+ unpackUInteger8(buf, &data->dds.priority1);
+ return 1;
+ unpackUInteger8(buf, &data->dds.priority2);
+ return 1;
+ unpackUInteger8(buf, &data->dds.domainNumber);
+ return 1;
+ unpackBoolean(buf, &data->dds.slaveOnly);
+ return 1;
+ unpackInteger8(buf, &data->port_ds.logAnnounceInterval);
+ return 1;
+ unpackUInteger8(buf, &data->port_ds.announceReceiptTimeout);
+ return 1;
+ unpackInteger8(buf, &data->port_ds.logSyncInterval);
+ return 1;
+ unpackUInteger4Lower(buf, &data->port_ds.versionNumber);
+ return 1;
+ return 0;
+ unpackTimestamp(buf, &data->time.currentTime);
+ return 10;
+ unpackEnumeration8(buf, &data->ca.clockAccuracy);
+ return 2;
+ return unpackUTCProperties(buf, &data->tpds);
+ return unpackTraceabilityProperties(buf, &data->tpds);
+ return unpackTimescaleProperties(buf, &data->tpds);
+ break; /* unimplemented */
+ data->path.num = len / sizeof(struct ClockIdentity);
+ data->path.list = (struct ClockIdentity*) buf;
+ return data->path.num * sizeof(struct ClockIdentity);
+ break; /* unimplemented */
+ break; /* unimplemented */
+ break; /* unimplemented */
+ break; /* unimplemented */
+ break; /* unimplemented */
+ break; /* unimplemented */
+ break; /* unimplemented */
+ break; /* unimplemented */
+ break; /* unimplemented */
+ unpackEnumeration8(buf, &data->port_ds.delayMechanism);
+ return 1;
+ unpackInteger8(buf, &data->port_ds.logMinPdelayReqInterval);
+ return 1;
+ return unpackTimeStatusNP(buf, &data->tsn);
+ }
+ return -MGMT_ERROR_NO_SUCH_ID;
+}
- if (0 == memcmp(org->id, ieee8021_id, sizeof(ieee8021_id))) {
- if (org->subtype[0] || org->subtype[1]) {
- return;
+static int tlv_write_org_data(struct OrgTLV *org, uint8_t *buf, int len) {
+ union OrgTLVData *data = &org->data;
+ switch (org->id) {
+ switch (org->subtype) {
+ if (len < 4 + 2 + 12 + 4)
+ return -1;
+ return packAVBFollowUp(&data->fup, buf);
}
- switch (org->subtype[2]) {
- f = (struct follow_up_info_tlv *) org;
- f->cumulativeScaledRateOffset = htonl(f->cumulativeScaledRateOffset);
- f->gmTimeBaseIndicator = htons(f->gmTimeBaseIndicator);
- scaled_ns_h2n(&f->lastGmPhaseChange);
- f->scaledLastGmPhaseChange = htonl(f->scaledLastGmPhaseChange);
- break;
+ return -1;
+ }
+ return -1;
+}
+
+static int tlv_read_org_data(uint8_t *buf, struct OrgTLV *org) {
+ union OrgTLVData *data = &org->data;
+ switch (org->id) {
+ switch (org->subtype) {
+ return unpackAVBFollowUp(buf, &data->fup);
}
+ return -1;
}
+ return -1;
}
-void tlv_post_recv(struct TLV *tlv)
-{
- struct management_tlv *mgt;
- struct management_error_status *mes;
- struct path_trace_tlv *ptt;
+#define TLV_HDR_LEN 4
+
+int tlv_write(struct tlv *tlv, uint8_t *buf, int len) {
+ int offset;
+ int result;
+ struct TLVHeader hdr;
+ Enumeration16 mgmt_id;
+ hdr.tlvType = tlv->type;
+ hdr.lengthField = tlv->length;
+ if (len < TLV_HDR_LEN)
+ return -1;
+ offset = packTLVHeader(&hdr, buf);
switch (tlv->type) {
- mgt = (struct management_tlv *) tlv;
- mgt->id = ntohs(mgt->id);
- mgt_post_recv(mgt);
+ if (len - offset < 2)
+ return -1;
+ mgmt_id = tlv->mgmt.id;
+ packEnumeration16(&mgmt_id, buf + offset);
+ offset += 2;
+ if (tlv->mgmt.error)
+ return -1;
+ if (!tlv->mgmt.empty_body) {
+ result = tlv_write_mgmt_data(tlv->mgmt.id,
+ &tlv->mgmt.data,
+ buf + offset, len - offset);
+ if (result < 0)
+ return -1;
+ offset += result;
+ }
break;
- mes = (struct management_error_status *) tlv;
- mes->error = ntohs(mes->error);
- mes->id = ntohs(mes->id);
+ /* account for displayData being entirely optional */
+ if (len - offset < 2 + 2 + 4 +
+ (tlv->error.displayData.length ? 1 + tlv->error.displayData.length : 0))
+ return -1;
+ offset += packErrorStatus(&tlv->error, buf + offset);
break;
- org_post_recv((struct organization_tlv *) tlv);
- break;
+ if (len - offset < 6)
+ return -1;
+ offset += packOrgTLV(&tlv->org, buf + offset);
+ result = tlv_write_org_data(&tlv->org, buf + offset, len - offset);
+ if (result < 0)
+ return -1;
+ offset += result;
break;
- ptt = (struct path_trace_tlv *) tlv;
- if (path_length(ptt) > PATH_TRACE_MAX) {
- ptt->length = PATH_TRACE_MAX * sizeof(struct ClockIdentity);
+ result = tlv->path_trace.num * sizeof(struct ClockIdentity);
+ if (len - offset < result)
+ return -1;
+ memmove(buf + offset, tlv->path_trace.cid, result);
+ offset += result;
+ switch (tlv->path_trace.op) {
+ break;
+ /* add additional ClockIdentity */
+ result = sizeof(struct ClockIdentity);
+ if (len - offset < result)
+ return -1;
+ memmove(buf + offset, &tlv->path_trace.op_arg.append, result);
+ offset += result;
+ break;
}
break;
- break;
+ return -1;
}
+
+ /* write padding if necessary */
+ if (offset % 2 == 1) {
+ if (len <= offset) return -1;
+ buf[offset++] = 0;
+ }
+
+ /* update tlv length in both the msg struct and
+ * written buffer.
+ */
+ tlv->length = offset - TLV_HDR_LEN;
+ packUInteger16(&tlv->length, buf + 2);
+ return offset;
}
-void tlv_pre_send(struct TLV *tlv)
-{
- struct management_tlv *mgt;
- struct management_error_status *mes;
+int tlv_read(uint8_t *buf, int len, struct tlv *tlv) {
+ int result;
+ int offset;
+ struct TLVHeader hdr;
+ Enumeration16 mgmt_id;
+ if (len < TLV_HDR_LEN) {
+ return 0;
+ }
+ offset = unpackTLVHeader(buf, &hdr);
+ tlv->type = hdr.tlvType;
+ tlv->length = hdr.lengthField;
switch (tlv->type) {
- mgt = (struct management_tlv *) tlv;
- mgt_pre_send(mgt);
- mgt->id = htons(mgt->id);
+ unpackEnumeration16(buf + offset, &mgmt_id);
+ tlv->mgmt.id = mgmt_id;
+ offset += 2;
+ if (offset > len) goto skip;
+ tlv->mgmt.error = 0;
+ if (tlv->length == 2) {
+ /* management TLVs can have empty bodies */
+ tlv->mgmt.empty_body = 1;
+ memset(&tlv->mgmt.data, 0, sizeof(tlv->mgmt.data));
+ } else {
+ tlv->mgmt.empty_body = 0;
+ result = tlv_read_mgmt_data(tlv->mgmt.id,
+ buf + offset,
+ tlv->length - 2,
+ &tlv->mgmt.data);
+ if (result < 0) {
+ tlv->mgmt.error = -result;
+ offset += tlv->length - 2;
+ } else {
+ offset += result;
+ }
+ }
break;
- mes = (struct management_error_status *) tlv;
- mes->error = htons(mes->error);
- mes->id = htons(mes->id);
+ offset += unpackErrorStatus(buf + offset, &tlv->error, len - offset);
break;
- org_pre_send((struct organization_tlv *) tlv);
+ offset += unpackOrgTLV(buf + offset, &tlv->org);
+ if (offset > len) goto skip;
+ result = tlv_read_org_data(buf + offset, &tlv->org);
+ if (result == -1)
+ goto skip;
+ offset += result;
break;
+ tlv->path_trace.num = tlv->length / sizeof(struct ClockIdentity);
+ tlv->path_trace.cid = (struct ClockIdentity*) (buf + offset);
+ tlv->path_trace.op = OP_NONE;
+ offset += tlv->path_trace.num * sizeof(struct ClockIdentity);
break;
+ goto skip;
+ }
+ /* skip over padding */
+ if (offset % 2 == 1) {
+ offset++;
+ }
+
+ /* ensure length field in TLV header is correct */
+ if (offset != tlv->length + TLV_HDR_LEN) {
+ if (tlv->type == TLV_MANAGEMENT) {
+ /* Instead of skipping the TLV, flag size
+ errors in management TLVs so higher levels
+ can send error messages if appropriate */
+ tlv->mgmt.error = MGMT_ERROR_WRONG_LENGTH;
+ } else {
+ goto skip;
+ }
+ }
+
+ return tlv->length + TLV_HDR_LEN;
+ /* Return negative of total TLV length so that it can still be
+ skipped over but an error is signalled. */
+ return -tlv->length - TLV_HDR_LEN;
+}
+
+#define GET_CHECK(action) \
+ switch(action) { \
+ case ACTION_GET: return 1; \
+ case ACTION_SET: return -MGMT_ERROR_NOT_SETABLE; \
+ case ACTION_RESPONSE: return 1; \
+ case ACTION_COMMAND: return -MGMT_ERROR_NOT_SUPPORTED; \
+ default: return 0;}
This is kinda gross.
Post by Geoff Salmon
+#define GET_SET_CHECK(action) \
+ switch(action) { \
+ case ACTION_GET: \
+ case ACTION_SET: \
+ case ACTION_RESPONSE: return 1; \
+ case ACTION_COMMAND: return -MGMT_ERROR_NOT_SUPPORTED; \
+ default: return 0;}
+#define COMMAND_CHECK(action) \
+ switch(action) { \
+ case ACTION_GET: \
+ case ACTION_SET: return -MGMT_ERROR_NOT_SUPPORTED; \
+ case ACTION_COMMAND: \
+ case ACTION_ACKNOWLEDGE: return 1; \
+ default: return 0;}
+
+int tlv_check_mgmt_action(enum management_actions action, enum management_types type) {
+ switch (type) {
+ return 1;
+ GET_CHECK(action);
+ GET_SET_CHECK(action);
+ COMMAND_CHECK(action);
+ GET_CHECK(action);
+ COMMAND_CHECK(action);
+ GET_CHECK(action);
+ GET_SET_CHECK(action);
+ COMMAND_CHECK(action);
+ GET_SET_CHECK(action);
+ GET_CHECK(action);
+ GET_SET_CHECK(action);
+ GET_CHECK(action);
+ GET_SET_CHECK(action);
+ GET_CHECK(action);
+ GET_SET_CHECK(action);
+ GET_CHECK(action);
+ GET_SET_CHECK(action);
+ GET_CHECK(action);
+ GET_SET_CHECK(action);
+ GET_CHECK(action);
+ }
+ /* Unknown type id. Return error message if appropriate. */
+ switch (action) {
+ return -MGMT_ERROR_NO_SUCH_ID;
+ return 0;
}
}
diff --git a/tlv.h b/tlv.h
index 7c95e2d..434dc0a 100644
--- a/tlv.h
+++ b/tlv.h
@@ -21,172 +21,367 @@
#define HAVE_TLV_H
#include "ddt.h"
-#include "msg.h"
-
-/* TLV types */
-#define TLV_MANAGEMENT 0x0001
-#define TLV_MANAGEMENT_ERROR_STATUS 0x0002
-#define TLV_ORGANIZATION_EXTENSION 0x0003
-#define TLV_REQUEST_UNICAST_TRANSMISSION 0x0004
-#define TLV_GRANT_UNICAST_TRANSMISSION 0x0005
-#define TLV_CANCEL_UNICAST_TRANSMISSION 0x0006
-#define TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION 0x0007
-#define TLV_PATH_TRACE 0x0008
-#define TLV_ALTERNATE_TIME_OFFSET_INDICATOR 0x0009
-#define TLV_AUTHENTICATION 0x2000
-#define TLV_AUTHENTICATION_CHALLENGE 0x2001
-#define TLV_SECURITY_ASSOCIATION_UPDATE 0x2002
-#define TLV_CUM_FREQ_SCALE_FACTOR_OFFSET 0x2003
-
-enum management_action {
- GET,
- SET,
- RESPONSE,
- COMMAND,
- ACKNOWLEDGE,
+#include "ds.h"
+
+#define TLV_HDR_LEN 4
+
+enum tlv_types {
Changing the defines to enums doesn't really add any value.
Post by Geoff Salmon
+ /* Reserved 0x0000 */
+ /* Standard TLVs */
+ TLV_MANAGEMENT = 0x0001,
+ TLV_MANAGEMENT_ERROR_STATUS,
+ TLV_ORGANIZATION_EXTENSION,
+ /* Optional unicast message negotiation TLVs */
+ TLV_REQUEST_UNICAST_TRANSMISSION,
+ TLV_GRANT_UNICAST_TRANSMISSION,
+ TLV_CANCEL_UNICAST_TRANSMISSION,
+ TLV_ACKNOWLEDGE_CANCEL_UNICAST_TRANSMISSION,
+ /* Optional path trace mechanism TLV */
+ TLV_PATH_TRACE,
+ /* Optional alternate timescale TLV */
+ TLV_ALTERNATE_TIME_OFFSET_INDICATOR,
+ /* Reserved for standard TLVs 0x000A - 0x1FFF */
+ /* Experimental TLVs */
+ /* Security TLVs */
+ TLV_AUTHENTICATION = 0x2000,
+ TLV_AUTHENTICATION_CHALLENGE,
+ TLV_SECURITY_ASSOCIATION_UPDATE,
+ /* Cumulative frequency scale factor offset */
+ TLV_CUM_FREQ_SCALE_FACTOR_OFFSET,
+ /* Reserved for Experimental TLVs 0x2004 - 0x3FFF */
+ /* Reserved 0x4000 - 0xFFFF */
+};
+
+/* Organizationally Unique Identifiers */
+enum OUI {
+ OUI_CISCO_SYSTEMS = 0x0080C2
+};
+
+enum cisco_systems_subtype {
+ SUBTYPE_AVB_FOLLOW_UP = 1
+};
+
+enum management_actions {
+ ACTION_GET = 0,
+ ACTION_SET,
+ ACTION_RESPONSE,
+ ACTION_COMMAND,
+ ACTION_ACKNOWLEDGE,
+ /* reserved 0x5 - 0xF */
};
/* Clock management ID values */
-#define USER_DESCRIPTION 0x0002
-#define SAVE_IN_NON_VOLATILE_STORAGE 0x0003
-#define RESET_NON_VOLATILE_STORAGE 0x0004
-#define INITIALIZE 0x0005
-#define FAULT_LOG 0x0006
-#define FAULT_LOG_RESET 0x0007
-#define DEFAULT_DATA_SET 0x2000
-#define CURRENT_DATA_SET 0x2001
-#define PARENT_DATA_SET 0x2002
-#define TIME_PROPERTIES_DATA_SET 0x2003
-#define PRIORITY1 0x2005
-#define PRIORITY2 0x2006
-#define DOMAIN 0x2007
-#define SLAVE_ONLY 0x2008
-#define TIME 0x200F
-#define CLOCK_ACCURACY 0x2010
-#define UTC_PROPERTIES 0x2011
-#define TRACEABILITY_PROPERTIES 0x2012
-#define TIMESCALE_PROPERTIES 0x2013
-#define PATH_TRACE_LIST 0x2015
-#define PATH_TRACE_ENABLE 0x2016
-#define GRANDMASTER_CLUSTER_TABLE 0x2017
-#define ACCEPTABLE_MASTER_TABLE 0x201A
-#define ACCEPTABLE_MASTER_MAX_TABLE_SIZE 0x201C
-#define ALTERNATE_TIME_OFFSET_ENABLE 0x201E
-#define ALTERNATE_TIME_OFFSET_NAME 0x201F
-#define ALTERNATE_TIME_OFFSET_MAX_KEY 0x2020
-#define ALTERNATE_TIME_OFFSET_PROPERTIES 0x2021
-#define TRANSPARENT_CLOCK_DEFAULT_DATA_SET 0x4000
-#define PRIMARY_DOMAIN 0x4002
-#define TIME_STATUS_NP 0xC000
-
-/* Port management ID values */
-#define NULL_MANAGEMENT 0x0000
-#define CLOCK_DESCRIPTION 0x0001
-#define PORT_DATA_SET 0x2004
-#define LOG_ANNOUNCE_INTERVAL 0x2009
-#define ANNOUNCE_RECEIPT_TIMEOUT 0x200A
-#define LOG_SYNC_INTERVAL 0x200B
-#define VERSION_NUMBER 0x200C
-#define ENABLE_PORT 0x200D
-#define DISABLE_PORT 0x200E
-#define UNICAST_NEGOTIATION_ENABLE 0x2014
-#define UNICAST_MASTER_TABLE 0x2018
-#define UNICAST_MASTER_MAX_TABLE_SIZE 0x2019
-#define ACCEPTABLE_MASTER_TABLE_ENABLED 0x201B
-#define ALTERNATE_MASTER 0x201D
-#define TRANSPARENT_CLOCK_PORT_DATA_SET 0x4001
-#define DELAY_MECHANISM 0x6000
-#define LOG_MIN_PDELAY_REQ_INTERVAL 0x6001
+enum management_types {
+ /* Applicable to all node types */
+ MGMT_NULL_MANAGEMENT = 0x0000,
+ MGMT_CLOCK_DESCRIPTION,
+ MGMT_USER_DESCRIPTION,
+ MGMT_SAVE_IN_NON_VOLATILE_STORAGE,
+ MGMT_RESET_NON_VOLATILE_STORAGE,
+ MGMT_INITIALIZE,
+ MGMT_FAULT_LOG,
+ MGMT_FAULT_LOG_RESET,
+
+ /* reserved 0x0008 - 0x1FFF */
+
+ /* Applicable to ordinary and boundary clocks */
+ MGMT_DEFAULT_DATA_SET = 0x2000,
+ MGMT_CURRENT_DATA_SET,
+ MGMT_PARENT_DATA_SET,
+ MGMT_TIME_PROPERTIES_DATA_SET,
+ MGMT_PORT_DATA_SET,
+ MGMT_PRIORITY1,
+ MGMT_PRIORITY2,
+ MGMT_DOMAIN,
+ MGMT_SLAVE_ONLY,
+ MGMT_LOG_ANNOUNCE_INTERVAL,
+ MGMT_ANNOUNCE_RECEIPT_TIMEOUT,
+ MGMT_LOG_SYNC_INTERVAL,
+ MGMT_VERSION_NUMBER,
+ MGMT_ENABLE_PORT,
+ MGMT_DISABLE_PORT,
+ MGMT_TIME,
+ MGMT_CLOCK_ACCURACY,
+ MGMT_UTC_PROPERTIES,
+ MGMT_TRACEABILITY_PROPERTIES,
+ MGMT_TIMESCALE_PROPERTIES,
+
+ /* optional parts */
+ /* 16.1 */
+ MGMT_UNICAST_NEGOTIATION_ENABLE,
+ /* 16.2 */
+ MGMT_PATH_TRACE_LIST,
+ MGMT_PATH_TRACE_ENABLE,
+ /* 17.3 */
+ MGMT_GRANDMASTER_CLUSTER_TABLE,
+ /* 17.5 */
+ MGMT_UNICAST_MASTER_TABLE,
+ MGMT_UNICAST_MASTER_MAX_TABLE_SIZE,
+ /* 17.6 */
+ MGMT_ACCEPTABLE_MASTER_TABLE,
+ MGMT_ACCEPTABLE_MASTER_TABLE_ENABLED,
+ MGMT_ACCEPTABLE_MASTER_MAX_TABLE_SIZE,
+ /* 17.4 */
+ MGMT_ALTERNATE_MASTER,
+ /* 16.3 */
+ MGMT_ALTERNATE_TIME_OFFSET_ENABLE,
+ MGMT_ALTERNATE_TIME_OFFSET_NAME,
+ MGMT_ALTERNATE_TIME_OFFSET_MAX_KEY,
+ MGMT_ALTERNATE_TIME_OFFSET_PROPERTIES,
+
+ /* reserved 0x2022 - 0x3FFF */
+
+ /* Applicable to transparent clocks */
+ MGMT_TRANSPARENT_CLOCK_DEFAULT_DATA_SET = 0x4000,
+ MGMT_TRANSPARENT_CLOCK_PORT_DATA_SET,
+ MGMT_PRIMARY_DOMAIN,
+
+ /* reserved 0x4003 - 0x5FFF */
+
+ /* Applicable to ordinary, boundary, and transparent clocks */
+ MGMT_DELAY_MECHANISM = 0x6000,
+ MGMT_LOG_MIN_PDELAY_REQ_INTERVAL,
+
+ /* reserved 0x6002 - 0xBFFF */
+
+ /* This range is to be used for implementation-specific
+ * identifiers 0xC000 - 0xDFFF
+ */
+ MGMT_TIME_STATUS_NP = 0xC000
+
+ /* This range is to be assigned by an alternate PTP profile
+ * 0xE000 - 0xFFFE
+ */
+
+ /* reserved 0xFFFF */
+};
/* Management error ID values */
-#define RESPONSE_TOO_BIG 0x0001
-#define NO_SUCH_ID 0x0002
-#define WRONG_LENGTH 0x0003
-#define WRONG_VALUE 0x0004
-#define NOT_SETABLE 0x0005
-#define NOT_SUPPORTED 0x0006
-#define GENERAL_ERROR 0xFFFE
-
-struct management_tlv {
- Enumeration16 type;
- UInteger16 length;
- Enumeration16 id;
- Octet data[0];
-} PACKED;
-
-struct management_error_status {
- Enumeration16 type;
- UInteger16 length;
- Enumeration16 error;
- Enumeration16 id;
- Octet reserved[4];
- Octet data[0];
-} PACKED;
+enum management_error_types {
+ /* Reserved 0x0000 */
+ MGMT_ERROR_RESPONSE_TOO_BIG = 0x0001,
+ MGMT_ERROR_NO_SUCH_ID,
+ MGMT_ERROR_WRONG_LENGTH,
+ MGMT_ERROR_WRONG_VALUE,
+ MGMT_ERROR_NOT_SETABLE,
+ MGMT_ERROR_NOT_SUPPORTED,
+ /* Reserved 0x0007 - 0xBFFF */
+ /* Implementation-specific 0xC000 - 0xDFFF */
+ /* PTP profile defined 0xE000 - 0xFFFD */
+ MGMT_ERROR_GENERAL_ERROR = 0xFFFE
+ /* Reserved 0xFFFF */
+};
-/* Organizationally Unique Identifiers */
-#define IEEE_802_1_COMMITTEE 0x00, 0x80, 0xC2
-extern uint8_t ieee8021_id[3];
-
-struct organization_tlv {
- Enumeration16 type;
- UInteger16 length;
- Octet id[3];
- Octet subtype[3];
-} PACKED;
-
-#define PATH_TRACE_MAX \
- ((sizeof(struct message_data) - sizeof(struct announce_msg) - sizeof(struct TLV)) / \
- sizeof(struct ClockIdentity))
-
-struct path_trace_tlv {
- Enumeration16 type;
- UInteger16 length;
- struct ClockIdentity cid[0];
-} PACKED;
-
-static inline unsigned int path_length(struct path_trace_tlv *p)
-{
- return p->length / sizeof(struct ClockIdentity);
-}
+enum clock_type {
+ CLOCK_TYPE_ORDINARY = 0x80,
+ CLOCK_TYPE_BOUNDARY = 0x40,
+ CLOCK_TYPE_P2P = 0x20,
+ CLOCK_TYPE_E2E = 0x10,
+ CLOCK_TYPE_MANAGEMENT = 0x08
+};
-typedef struct Integer96 {
- uint16_t nanoseconds_msb;
- uint64_t nanoseconds_lsb;
- uint16_t fractional_nanoseconds;
-} PACKED ScaledNs;
+struct TLVHeader {
+ Enumeration16 tlvType;
+ UInteger16 lengthField;
+};
-struct follow_up_info_tlv {
- Enumeration16 type;
- UInteger16 length;
- Octet id[3];
- Octet subtype[3];
- Integer32 cumulativeScaledRateOffset;
- UInteger16 gmTimeBaseIndicator;
- ScaledNs lastGmPhaseChange;
- Integer32 scaledLastGmPhaseChange;
-} PACKED;
+/**** Structs for Manangement TLV Data ****/
+
+struct PathTraceList {
+ int num;
+ struct ClockIdentity *list;
+};
+
+struct PhysicalAddress {
+ UInteger16 addressLength;
+ OctetPtr addressField;
+};
+
+struct ScaledNs {
+ UInteger16 nanoseconds_msb;
+ UInteger64 nanoseconds_lsb;
+ UInteger16 fractional_nanoseconds;
+};
+
+struct ClockAccuracy {
+ Enumeration8 clockAccuracy;
+};
+
+struct ClockDescription {
+ UInteger16 clockType;
+ struct PTPText physicalLayerProtocol;
+ struct PhysicalAddress physicalAddress;
+ struct PortAddress protocolAddress;
+ Octet manufacturerIdentity[3];
+ struct PTPText productDescription;
+ struct PTPText revisionData;
+ struct PTPText userDescription;
+ Octet profileIdentity[6];
+};
+
+struct Initialize {
+ UInteger16 initializeKey;
+};
+
+struct Time {
+ struct Timestamp currentTime;
+};
+
+enum fault_record_type {
+ FAULT_RECORD_LIST,
+ FAULT_RECORD_BUFFER
+};
+
+struct FaultLog {
+ UInteger16 numberRecords;
+ /* FaultLogs are stored in one of two ways. Either as a linked
+ * list of FaultRecord structs or as pointer to a byte buffer
+ * containing serialized FaultRecords. This distinction exists
+ * so that we don't have to parse the FaultRecords when
+ * receiving a FaultLog message RESPONSE that will be
+ * forwarded on. */
+ enum fault_record_type type;
+ union {
+ struct FaultRecordList list;
+ struct {
+ uint8_t *buf;
+ int len;
+ } buffer;
+ };
+};
struct time_status_np {
- int64_t master_offset; /*nanoseconds*/
- int64_t ingress_time; /*nanoseconds*/
- Integer32 cumulativeScaledRateOffset;
- Integer32 scaledLastGmPhaseChange;
- UInteger16 gmTimeBaseIndicator;
- ScaledNs lastGmPhaseChange;
- Integer32 gmPresent;
+ Integer64 master_offset;
+ Integer64 ingress_time;
+ Integer32 cumulativeScaledRateOffset;
+ Integer32 scaledLastGmPhaseChange;
+ UInteger16 gmTimeBaseIndicator;
+ struct ScaledNs lastGmPhaseChange;
+ Integer32 gmPresent;
struct ClockIdentity gmIdentity;
-} PACKED;
+};
+
+union ManagementTLVData {
+ struct ClockDescription cd;
+ struct Initialize ik;
+ struct FaultLog fl;
+ struct Time time;
+ struct ClockAccuracy ca;
+ struct defaultDS dds;
+ struct currentDS cds;
+ struct parentDS parent_ds;
+ struct timePropertiesDS tpds;
+ struct portDS port_ds;
+ struct PathTraceList path;
+
+ /* doesn't include transparent clock specific data */
+
+ /* implementation specific */
+ struct time_status_np tsn;
+};
+
+/**** Structs for Organization Exception TLVs ****/
+
+/* Follow_Up TLV from IEEE 802.1 AVB */
+struct follow_up_info_tlv {
+ Integer32 cumulativeScaledRateOffset;
+ UInteger16 gmTimeBaseIndicator;
+ struct ScaledNs lastGmPhaseChange;
+ Integer32 scaledLastGmPhaseChange;
+};
+
+union OrgTLVData {
+ struct follow_up_info_tlv fup;
+};
+
+/**** Structs for TLV Types ****/
+
+struct ManagementTLV {
+ enum management_types id;
+ /* empty_body is a flag that determines if the body of the TLV
+ * is or should be empty. Only mgmt TLVs in messages with the
+ * SET and RESPONSE actions should have non-empty bodies. */
+ int empty_body;
+ enum management_error_types error;
+ union ManagementTLVData data;
+};
+
+struct ErrorStatus {
+ Enumeration16 managementErrorId;
+ Enumeration16 managementId;
+ struct PTPText displayData;
+};
+
+struct OrgTLV {
+ uint32_t id;
+ uint32_t subtype;
+ Octet *dataPtr;
+ union OrgTLVData data;
+};
+
+struct PathTraceTLV {
+ int num;
+ struct ClockIdentity *cid;
+ enum {
+ OP_NONE,
+ OP_APPEND
+ } op;
+ struct {
+ struct ClockIdentity append;
+ } op_arg;
+};
+
+struct tlv {
+ enum tlv_types type;
+ uint16_t length;
+ union {
+ /* TLV_MANAGEMENT */
+ struct ManagementTLV mgmt;
+ /* TLV_MANAGEMENT_ERROR_STATUS */
+ struct ErrorStatus error;
+ /* TLV_ORGANIZATION_EXTENSION */
+ struct OrgTLV org;
+ /* TLV_PATH_TRACE */
+ struct PathTraceTLV path_trace;
+ };
+};
+
+static inline unsigned int path_length(struct tlv *tlv)
+{
+ return tlv->length / sizeof(struct ClockIdentity);
+}
/**
- * Converts recognized value sub-fields into host byte order.
+ * Writes a tlv to a buffer. Can fail if there is not enough room in
+ * the buffer or if the tlv is malformed somehow. As a side affect,
+ * the tlv->length field will be set.
+ *
+ */
+int tlv_write(struct tlv *tlv, uint8_t *buf, int len);
+
+/**
+ * Reads a tlv from a buffer into a tlv struct.
+ *
+ * read will return a negative number that is the number of bytes that
+ * the tlv takes in the buffer. This negative result can be used to
+ * skip over bad tlvs and continue reading.
*/
-void tlv_post_recv(struct TLV *tlv);
+int tlv_read(uint8_t *buf, int len, struct tlv *tlv);
/**
- * Converts recognized value sub-fields into network byte order.
+ * Determines if a managmenet action and type pair is valid.
+ * negative management error or 0 if no error message should be sent
+ * for the action.
*/
-void tlv_pre_send(struct TLV *tlv);
+int tlv_check_mgmt_action(enum management_actions action, enum management_types type);
#endif
diff --git a/tlv_pack.h b/tlv_pack.h
new file mode 100644
index 0000000..559e919
--- /dev/null
+++ b/tlv_pack.h
@@ -0,0 +1,544 @@
+/**
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef HAVE_TLV_PACK_H
+#define HAVE_TLV_PACK_H
+
+#include "ddt_pack.h"
+
+static inline int packTLVHeader(const struct TLVHeader *data, Octet *buf) {
+ int offset = 0;
+ packEnumeration16(&data->tlvType, buf + offset);
+ offset += 2;
+ packUInteger16(&data->lengthField, buf + offset);
+ offset += 2;
+ return offset;
+}
+
+static inline int unpackTLVHeader(const Octet *buf, struct TLVHeader *data) {
+ int offset = 0;
+ unpackEnumeration16(buf + offset, &data->tlvType);
+ offset += 2;
+ unpackUInteger16(buf + offset, &data->lengthField);
+ offset += 2;
+ return offset;
+}
+
+static inline int packPhysicalAddress(const struct PhysicalAddress *data, Octet *buf) {
+ int offset = 0;
+ packUInteger16(&data->addressLength, buf + offset);
+ offset += 2;
+ packOctetPtr(&data->addressField, buf + offset, data->addressLength);
+ offset += data->addressLength;
+ return offset;
+}
+
+static inline int unpackPhysicalAddress(const Octet *buf, struct PhysicalAddress *data) {
+ int offset = 0;
+ unpackUInteger16(buf + offset, &data->addressLength);
+ offset += 2;
+ unpackOctetPtr(buf + offset, &data->addressField, data->addressLength);
+ offset += data->addressLength;
+ return offset;
+}
+
+static inline int packScaledNs(const struct ScaledNs *data, Octet *buf) {
+ int offset = 0;
+ packUInteger16(&data->nanoseconds_msb, buf + offset);
+ offset += 2;
+ packUInteger64(&data->nanoseconds_lsb, buf + offset);
+ offset += 8;
+ packUInteger16(&data->fractional_nanoseconds, buf + offset);
+ offset += 2;
+ return offset;
+}
+
+static inline int unpackScaledNs(const Octet *buf, struct ScaledNs *data) {
+ int offset = 0;
+ unpackUInteger16(buf + offset, &data->nanoseconds_msb);
+ offset += 2;
+ unpackUInteger64(buf + offset, &data->nanoseconds_lsb);
+ offset += 8;
+ unpackUInteger16(buf + offset, &data->fractional_nanoseconds);
+ offset += 2;
+ return offset;
+}
+
+static inline int packCurrentDataSet(const struct currentDS *data, Octet *buf) {
+ int offset = 0;
+ packUInteger16(&data->stepsRemoved, buf + offset);
+ offset += 2;
+ packInteger64(&data->offsetFromMaster, buf + offset);
+ offset += 8;
+ packInteger64(&data->meanPathDelay, buf + offset);
+ offset += 8;
+ return offset;
+}
+
+static inline int unpackCurrentDataSet(const Octet *buf, struct currentDS *data) {
+ int offset = 0;
+ unpackUInteger16(buf + offset, &data->stepsRemoved);
+ offset += 2;
+ unpackInteger64(buf + offset, &data->offsetFromMaster);
+ offset += 8;
+ unpackInteger64(buf + offset, &data->meanPathDelay);
+ offset += 8;
+ return offset;
+}
+
+static inline int packDefaultDataSet(const struct defaultDS *data, Octet *buf) {
+ int offset = 0;
+ uint8_t flags = ((data->twoStepFlag & 1) << 0) |
+ ((data->slaveOnly & 1) << 1);
+ packUInteger8(&flags, buf + offset);
+ offset++;
+ packReserved(buf + offset, 1);
+ offset += 1;
+ packUInteger16(&data->numberPorts, buf + offset);
+ offset += 2;
+ packUInteger8(&data->priority1, buf + offset);
+ offset += 1;
+ packClockQuality(&data->clockQuality, buf + offset);
+ offset += 4;
+ packUInteger8(&data->priority2, buf + offset);
+ offset += 1;
+ packClockIdentity(&data->clockIdentity, buf + offset);
+ offset += 8;
+ packUInteger8(&data->domainNumber, buf + offset);
+ offset += 1;
+ packReserved(buf + offset, 1);
+ offset += 1;
+ return offset;
+}
+
+static inline int unpackDefaultDataSet(const Octet *buf, struct defaultDS *data) {
+ int offset = 0;
+ uint8_t flags;
+ unpackUInteger8(buf + offset, &flags);
+ offset++;
+ data->twoStepFlag = (flags >> 0) & 1;
+ data->slaveOnly = (flags >> 1) & 1;
+ offset += 1;
+ unpackUInteger16(buf + offset, &data->numberPorts);
+ offset += 2;
+ unpackUInteger8(buf + offset, &data->priority1);
+ offset += 1;
+ unpackClockQuality(buf + offset, &data->clockQuality);
+ offset += 4;
+ unpackUInteger8(buf + offset, &data->priority2);
+ offset += 1;
+ unpackClockIdentity(buf + offset, &data->clockIdentity);
+ offset += 8;
+ unpackUInteger8(buf + offset, &data->domainNumber);
+ offset += 1;
+ offset += 1;
+ return offset;
+}
+
+static inline int packErrorStatus(const struct ErrorStatus *data, Octet *buf) {
+ int offset = 0;
+ packEnumeration16(&data->managementErrorId, buf + offset);
+ offset += 2;
+ packEnumeration16(&data->managementId, buf + offset);
+ offset += 2;
+ packReserved(buf + offset, 4);
+ offset += 4;
+ /* The displayData is totally optional. Don't even write
+ length field if there is no text. */
+ if (data->displayData.length) {
+ packPTPText(&data->displayData, buf + offset);
+ offset += 1 + data->displayData.length;
+ }
+ return offset;
+}
+
+static inline int unpackErrorStatus(const Octet *buf, struct ErrorStatus *data, int len) {
+ int offset = 0;
+ unpackEnumeration16(buf + offset, &data->managementErrorId);
+ offset += 2;
+ unpackEnumeration16(buf + offset, &data->managementId);
+ offset += 2;
+ offset += 4;
+ if (offset < len) {
+ /* the optional displayData is present. unpack it. */
+ unpackPTPText(buf + offset, &data->displayData);
+ offset += 1 + data->displayData.length;
+ } else {
+ data->displayData.length = 0;
+ data->displayData.text = NULL;
+ }
+ return offset;
+}
+
+static inline int packParentDataSet(const struct parentDS *data, Octet *buf) {
+ int offset = 0;
+ packPortIdentity(&data->parentPortIdentity, buf + offset);
+ offset += 10;
+ packBoolean(&data->parentStats, buf + offset);
+ offset += 1;
+ packReserved(buf + offset, 1);
+ offset += 1;
+ packUInteger16(&data->observedParentOffsetScaledLogVariance, buf + offset);
+ offset += 2;
+ packInteger32(&data->observedParentClockPhaseChangeRate, buf + offset);
+ offset += 4;
+ packUInteger8(&data->grandmasterPriority1, buf + offset);
+ offset += 1;
+ packClockQuality(&data->grandmasterClockQuality, buf + offset);
+ offset += 4;
+ packUInteger8(&data->grandmasterPriority2, buf + offset);
+ offset += 1;
+ packClockIdentity(&data->grandmasterIdentity, buf + offset);
+ offset += 8;
+ return offset;
+}
+
+static inline int unpackParentDataSet(const Octet *buf, struct parentDS *data) {
+ int offset = 0;
+ unpackPortIdentity(buf + offset, &data->parentPortIdentity);
+ offset += 10;
+ unpackBoolean(buf + offset, &data->parentStats);
+ offset += 1;
+ offset += 1;
+ unpackUInteger16(buf + offset, &data->observedParentOffsetScaledLogVariance);
+ offset += 2;
+ unpackInteger32(buf + offset, &data->observedParentClockPhaseChangeRate);
+ offset += 4;
+ unpackUInteger8(buf + offset, &data->grandmasterPriority1);
+ offset += 1;
+ unpackClockQuality(buf + offset, &data->grandmasterClockQuality);
+ offset += 4;
+ unpackUInteger8(buf + offset, &data->grandmasterPriority2);
+ offset += 1;
+ unpackClockIdentity(buf + offset, &data->grandmasterIdentity);
+ offset += 8;
+ return offset;
+}
+
+static inline int packPortDataSet(const struct portDS *data, Octet *buf) {
+ int offset = 0;
+ packPortIdentity(&data->portIdentity, buf + offset);
+ offset += 10;
+ packEnumeration8(&data->portState, buf + offset);
+ offset += 1;
+ packInteger8(&data->logMinDelayReqInterval, buf + offset);
+ offset += 1;
+ packInteger64(&data->peerMeanPathDelay, buf + offset);
+ offset += 8;
+ packInteger8(&data->logAnnounceInterval, buf + offset);
+ offset += 1;
+ packUInteger8(&data->announceReceiptTimeout, buf + offset);
+ offset += 1;
+ packInteger8(&data->logSyncInterval, buf + offset);
+ offset += 1;
+ packEnumeration8(&data->delayMechanism, buf + offset);
+ offset += 1;
+ packInteger8(&data->logMinPdelayReqInterval, buf + offset);
+ offset += 1;
+ packReserved(buf + offset, 0);
+ offset += 0;
+ packUInteger4Lower(&data->versionNumber, buf + offset);
+ offset += 1;
+ return offset;
+}
+
+static inline int unpackPortDataSet(const Octet *buf, struct portDS *data) {
+ int offset = 0;
+ unpackPortIdentity(buf + offset, &data->portIdentity);
+ offset += 10;
+ unpackEnumeration8(buf + offset, &data->portState);
+ offset += 1;
+ unpackInteger8(buf + offset, &data->logMinDelayReqInterval);
+ offset += 1;
+ unpackInteger64(buf + offset, &data->peerMeanPathDelay);
+ offset += 8;
+ unpackInteger8(buf + offset, &data->logAnnounceInterval);
+ offset += 1;
+ unpackUInteger8(buf + offset, &data->announceReceiptTimeout);
+ offset += 1;
+ unpackInteger8(buf + offset, &data->logSyncInterval);
+ offset += 1;
+ unpackEnumeration8(buf + offset, &data->delayMechanism);
+ offset += 1;
+ unpackInteger8(buf + offset, &data->logMinPdelayReqInterval);
+ offset += 1;
+ offset += 0;
+ unpackUInteger4Lower(buf + offset, &data->versionNumber);
+ offset += 1;
+ return offset;
+}
+
+static inline int packTimePropertiesDataSet(const struct timePropertiesDS *data, Octet *buf) {
+ int offset = 0;
+ packInteger16(&data->currentUtcOffset, buf + offset);
+ offset += 2;
+ uint8_t flags = ((data->leap61 & 1) << 0) |
+ ((data->leap59 & 1) << 1) |
+ ((data->currentUtcOffsetValid & 1) << 2) |
+ ((data->ptpTimescale & 1) << 3) |
+ ((data->timeTraceable & 1) << 4) |
+ ((data->frequencyTraceable & 1) << 5);
+ packUInteger8(&flags, buf + offset);
+ offset++;
+ packEnumeration8(&data->timeSource, buf + offset);
+ offset += 1;
+ return offset;
+}
+
+static inline int unpackTimePropertiesDataSet(const Octet *buf, struct timePropertiesDS *data) {
+ int offset = 0;
+ unpackInteger16(buf + offset, &data->currentUtcOffset);
+ offset += 2;
+ uint8_t flags;
+ unpackUInteger8(buf + offset, &flags);
+ offset++;
+ data->leap61 = (flags >> 0) & 1;
+ data->leap59 = (flags >> 1) & 1;
+ data->currentUtcOffsetValid = (flags >> 2) & 1;
+ data->ptpTimescale = (flags >> 3) & 1;
+ data->timeTraceable = (flags >> 4) & 1;
+ data->frequencyTraceable = (flags >> 5) & 1;
+ unpackEnumeration8(buf + offset, &data->timeSource);
+ offset += 1;
+ return offset;
+}
+
+static inline int packTraceabilityProperties(const struct timePropertiesDS *data, Octet *buf) {
+ int offset = 0;
+ uint8_t flags = ((data->timeTraceable & 1) << 4) |
+ ((data->frequencyTraceable & 1) << 5);
+ packUInteger8(&flags, buf + offset);
+ offset++;
+ packReserved(buf + offset, 1);
+ offset += 1;
+ return offset;
+}
+
+static inline int unpackTraceabilityProperties(const Octet *buf, struct timePropertiesDS *data) {
+ int offset = 0;
+ uint8_t flags;
+ unpackUInteger8(buf + offset, &flags);
+ offset++;
+ data->timeTraceable = (flags >> 4) & 1;
+ data->frequencyTraceable = (flags >> 5) & 1;
+ offset += 1;
+ return offset;
+}
+
+static inline int packTimescaleProperties(const struct timePropertiesDS *data, Octet *buf) {
+ int offset = 0;
+ uint8_t flags = ((data->ptpTimescale & 1) << 3);
+ packUInteger8(&flags, buf + offset);
+ offset++;
+ packOctet(&data->timeSource, buf + offset);
+ offset += 1;
+ return offset;
+}
+
+static inline int unpackTimescaleProperties(const Octet *buf, struct timePropertiesDS *data) {
+ int offset = 0;
+ uint8_t flags;
+ unpackUInteger8(buf + offset, &flags);
+ offset++;
+ data->ptpTimescale = (flags >> 3) & 1;
+ unpackOctet(buf + offset, &data->timeSource);
+ offset += 1;
+ return offset;
+}
+
+static inline int packUTCProperties(const struct timePropertiesDS *data, Octet *buf) {
+ int offset = 0;
+ packInteger16(&data->currentUtcOffset, buf + offset);
+ offset += 2;
+ uint8_t flags = ((data->leap61 & 1) << 0) |
+ ((data->leap59 & 1) << 1) |
+ ((data->currentUtcOffsetValid & 1) << 2);
+ packUInteger8(&flags, buf + offset);
+ offset++;
+ packReserved(buf + offset, 1);
+ offset += 1;
+ return offset;
+}
+
+static inline int unpackUTCProperties(const Octet *buf, struct timePropertiesDS *data) {
+ int offset = 0;
+ unpackInteger16(buf + offset, &data->currentUtcOffset);
+ offset += 2;
+ uint8_t flags;
+ unpackUInteger8(buf + offset, &flags);
+ offset++;
+ data->leap61 = (flags >> 0) & 1;
+ data->leap59 = (flags >> 1) & 1;
+ data->currentUtcOffsetValid = (flags >> 2) & 1;
+ offset += 1;
+ return offset;
+}
+
+static inline int packTimeStatusNP(const struct time_status_np *data, Octet *buf) {
+ int offset = 0;
+ packInteger64(&data->master_offset, buf + offset);
+ offset += 8;
+ packInteger64(&data->ingress_time, buf + offset);
+ offset += 8;
+ packInteger32(&data->cumulativeScaledRateOffset, buf + offset);
+ offset += 4;
+ packInteger32(&data->scaledLastGmPhaseChange, buf + offset);
+ offset += 4;
+ packUInteger16(&data->gmTimeBaseIndicator, buf + offset);
+ offset += 2;
+ packScaledNs(&data->lastGmPhaseChange, buf + offset);
+ offset += 12;
+ packInteger32(&data->gmPresent, buf + offset);
+ offset += 4;
+ packClockIdentity(&data->gmIdentity, buf + offset);
+ offset += 8;
+ return offset;
+}
+
+static inline int unpackTimeStatusNP(const Octet *buf, struct time_status_np *data) {
+ int offset = 0;
+ unpackInteger64(buf + offset, &data->master_offset);
+ offset += 8;
+ unpackInteger64(buf + offset, &data->ingress_time);
+ offset += 8;
+ unpackInteger32(buf + offset, &data->cumulativeScaledRateOffset);
+ offset += 4;
+ unpackInteger32(buf + offset, &data->scaledLastGmPhaseChange);
+ offset += 4;
+ unpackUInteger16(buf + offset, &data->gmTimeBaseIndicator);
+ offset += 2;
+ unpackScaledNs(buf + offset, &data->lastGmPhaseChange);
+ offset += 12;
+ unpackInteger32(buf + offset, &data->gmPresent);
+ offset += 4;
+ unpackClockIdentity(buf + offset, &data->gmIdentity);
+ offset += 8;
+ return offset;
+}
+
+static inline int packClockDescription(const struct ClockDescription *data, Octet *buf) {
+ int offset = 0;
+ packUInteger16(&data->clockType, buf + offset);
+ offset += 2;
+ packPTPText(&data->physicalLayerProtocol, buf + offset);
+ offset += 1 + data->physicalLayerProtocol.length;
+ packPhysicalAddress(&data->physicalAddress, buf + offset);
+ offset += 2 + data->physicalAddress.addressLength;
+ packPortAddress(&data->protocolAddress, buf + offset);
+ offset += 4 + data->protocolAddress.addressLength;
+ packOctetArray(data->manufacturerIdentity, buf + offset, 3);
+ offset += 3;
+ packReserved(buf + offset, 1);
+ offset += 1;
+ packPTPText(&data->productDescription, buf + offset);
+ offset += 1 + data->productDescription.length;
+ packPTPText(&data->revisionData, buf + offset);
+ offset += 1 + data->revisionData.length;
+ packPTPText(&data->userDescription, buf + offset);
+ offset += 1 + data->userDescription.length;
+ packOctetArray(data->profileIdentity, buf + offset, 6);
+ offset += 6;
+ return offset;
+}
+
+static inline int unpackClockDescription(const Octet *buf, struct ClockDescription *data) {
+ int offset = 0;
+ uint16_t addr_len;
+ unpackUInteger16(buf + offset, &data->clockType);
+ offset += 2;
+ unpackPTPText(buf + offset, &data->physicalLayerProtocol);
+ offset += 1 + data->physicalLayerProtocol.length;
+
+ unpackUInteger16(buf + offset, &addr_len);
+ if (addr_len > MAX_PORT_ADDR_LEN) {
+ return -MGMT_ERROR_WRONG_LENGTH;
+ }
+ unpackPhysicalAddress(buf + offset, &data->physicalAddress);
+ offset += 2 + data->physicalAddress.addressLength;
+
+ unpackUInteger16(buf + offset + 2, &addr_len);
+ if (addr_len > MAX_PORT_ADDR_LEN) {
+ return -MGMT_ERROR_WRONG_LENGTH;
+ }
+ unpackPortAddress(buf + offset, &data->protocolAddress);
+ offset += 4 + data->protocolAddress.addressLength;
+
+ unpackOctetArray(buf + offset, data->manufacturerIdentity, 3);
+ offset += 3;
+ offset += 1;
+ unpackPTPText(buf + offset, &data->productDescription);
+ offset += 1 + data->productDescription.length;
+ unpackPTPText(buf + offset, &data->revisionData);
+ offset += 1 + data->revisionData.length;
+ unpackPTPText(buf + offset, &data->userDescription);
+ offset += 1 + data->userDescription.length;
+ unpackOctetArray(buf + offset, data->profileIdentity, 6);
+ offset += 6;
+ return offset;
+}
+
+static inline int packOrgTLV(const struct OrgTLV *data, Octet *buf) {
+ uint32_t v;
+ v = htonl(data->id);
+ memcpy(buf, (uint8_t*)&v + 1, 3);
+
+ v = htonl(data->subtype);
+ memcpy(buf + 3, (uint8_t*)&v + 1, 3);
+ return 6;
+}
+
+static inline int unpackOrgTLV(const Octet *buf, struct OrgTLV *data) {
+ uint32_t v = 0;
+ memcpy((uint8_t*)&v + 1, buf, 3);
+ data->id = ntohl(v);
+
+ v = 0;
+ memcpy((uint8_t*)&v + 1, buf + 3, 3);
+ data->subtype = ntohl(v);
+ data->dataPtr = (Octet*)buf;
+ return 6;
+}
+
+static inline int packAVBFollowUp(const struct follow_up_info_tlv *data, Octet *buf) {
+ int offset = 0;
+ packInteger32(&data->cumulativeScaledRateOffset, buf + offset);
+ offset += 4;
+ packUInteger16(&data->gmTimeBaseIndicator, buf + offset);
+ offset += 2;
+ packScaledNs(&data->lastGmPhaseChange, buf + offset);
+ offset += 12;
+ packInteger32(&data->scaledLastGmPhaseChange, buf + offset);
+ offset += 4;
+ return offset;
+}
+
+static inline int unpackAVBFollowUp(const Octet *buf, struct follow_up_info_tlv *data) {
+ int offset = 0;
+ unpackInteger32(buf + offset, &data->cumulativeScaledRateOffset);
+ offset += 4;
+ unpackUInteger16(buf + offset, &data->gmTimeBaseIndicator);
+ offset += 2;
+ unpackScaledNs(buf + offset, &data->lastGmPhaseChange);
+ offset += 12;
+ unpackInteger32(buf + offset, &data->scaledLastGmPhaseChange);
+ offset += 4;
+ return offset;
+}
+
+#endif
This packing/unpacking code that you introduce looks okay to me
(except for coding style), and I think it makes sense to do it that
way for the variable length USER_DESCRIPTION, for example. But I do
wish you would do it within the framework of the existing code,
without changing tons of tangentially related code.

When working with a big chunk of code having been written by some one
else, I used to have the habit of changing a lot of stuff just to make
the code more pleasing to my own eye. It actually is a good way to get
to know the code, by "making it your own." But I had to learn to stop
doing that, in order to contribute to the kernel, for example.

So anyhow, I think you can get the improvements that you want, without
changing quite so much of the exisiting code.

Thanks,
Richard
Geoff Salmon
2012-12-28 21:51:26 UTC
Permalink
Adds test directory containing TAP style tests. Run "make check" in
the root directory to build and run the tests.

The tests can fork a child ptp4l process to interact with or can link
to object files in the root directory and test functions directly.

Should consider building the core of ptp4l as a static library that
the tests can link against.
---
.gitignore | 11 +-
makefile | 5 +
tests/TESTS | 6 +
tests/childproc-t.c | 41 ++
tests/childproc.c | 477 ++++++++++++++++++++
tests/childproc.h | 55 +++
tests/description-t.c | 87 ++++
tests/errors-t.c | 121 +++++
tests/example-t.c | 30 ++
tests/faultlog-t.c | 189 ++++++++
tests/makefile | 71 +++
tests/mgmt.c | 239 ++++++++++
tests/mgmt.h | 68 +++
tests/priority-t.c | 39 ++
tests/runtests.c | 1204 +++++++++++++++++++++++++++++++++++++++++++++++++
tests/tap/basic.c | 629 ++++++++++++++++++++++++++
tests/tap/basic.h | 134 ++++++
tests/tap/float.c | 67 +++
tests/tap/float.h | 42 ++
tests/tap/libtap.sh | 246 ++++++++++
tests/tap/macros.h | 88 ++++
tests/testutil.c | 115 +++++
tests/testutil.h | 42 ++
23 files changed, 4004 insertions(+), 2 deletions(-)
create mode 100644 tests/TESTS
create mode 100644 tests/childproc-t.c
create mode 100644 tests/childproc.c
create mode 100644 tests/childproc.h
create mode 100644 tests/description-t.c
create mode 100644 tests/errors-t.c
create mode 100644 tests/example-t.c
create mode 100644 tests/faultlog-t.c
create mode 100644 tests/makefile
create mode 100644 tests/mgmt.c
create mode 100644 tests/mgmt.h
create mode 100644 tests/priority-t.c
create mode 100644 tests/runtests.c
create mode 100644 tests/tap/basic.c
create mode 100644 tests/tap/basic.h
create mode 100644 tests/tap/float.c
create mode 100644 tests/tap/float.h
create mode 100644 tests/tap/libtap.sh
create mode 100644 tests/tap/macros.h
create mode 100644 tests/testutil.c
create mode 100644 tests/testutil.h

diff --git a/.gitignore b/.gitignore
index e0710ad..0ed84ca 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,13 @@
-/*.d
-/*.o
+*.d
+*.o
/hwstamp_ctl
/phc2sys
/pmc
/ptp4l
+/tests/runtests
+/tests/example-t
+/tests/childproc-t
+/tests/errors-t
+/tests/priority-t
+/tests/description-t
+/tests/faultlog-t
diff --git a/makefile b/makefile
index 53b9e4d..18a5e6b 100644
--- a/makefile
+++ b/makefile
@@ -71,10 +71,15 @@ install: $(PRG)

clean:
rm -f $(OBJECTS) $(DEPEND)
+ make -C tests clean

distclean: clean
rm -f $(PRG)
rm -f .version
+ make -C tests distclean
+
+check: all
+ make -C tests check

# Implicit rule to generate a C source file's dependencies.
%.d: %.c
diff --git a/tests/TESTS b/tests/TESTS
new file mode 100644
index 0000000..361cf3b
--- /dev/null
+++ b/tests/TESTS
@@ -0,0 +1,6 @@
+example
+childproc
+errors
+priority
+description
+faultlog
diff --git a/tests/childproc-t.c b/tests/childproc-t.c
new file mode 100644
index 0000000..c08a0be
--- /dev/null
+++ b/tests/childproc-t.c
@@ -0,0 +1,41 @@
+/**
+ * @file childproc-t.c
+ * @brief Tests childproc
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "tap/basic.h"
+#include "testutil.h"
+
+int main() {
+ struct childproc *proc;
+ int res;
+ plan(5);
+
+ ok((res = childproc_new(&proc)) == 0, "new");
+ if (res != 0) return 0;
+ ok((res = childproc_spawn(proc, 0)) == 0, "spawn");
+ if (res != 0) return 0;
+
+ struct mgmt_msg out;
+ struct mgmt_msg in;
+ mgmt_msg_init(&out, ACTION_GET, MGMT_NULL_MANAGEMENT);
+ ok(childproc_send_recv(proc, &out, &in) == 0, "send and recv NULL");
+
+ ok(childproc_kill(proc) == 0, "kill");
+ ok(childproc_free(proc) == 0, "free");
+ return 0;
+}
diff --git a/tests/childproc.c b/tests/childproc.c
new file mode 100644
index 0000000..446fda9
--- /dev/null
+++ b/tests/childproc.c
@@ -0,0 +1,477 @@
+/**
+ * @file childproc.c
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "childproc.h"
+
+#include <unistd.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <string.h>
+#include <signal.h>
+#include <sys/socket.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <stddef.h>
+#include <poll.h>
+
+#include "msg.h"
+#include "uds.h"
+#include "mgmt.h"
+
+#include "testutil.h"
+
+#define CHILDERR_DEV_NULL 100
+#define CHILDERR_DUP 101
+#define CHILDERR_EXEC 102
+
+struct childproc {
+ pid_t pid;
+ int running; /* flag */
+
+ int skt;
+ struct sockaddr_un server;
+ socklen_t server_len;
+
+ struct sequence_ids seqs;
+
+ struct pollfd pollfd;
+};
+
+int childproc_new(struct childproc **proc) {
+ struct childproc *cp = malloc(sizeof(*cp));
+ *proc = 0;
+ if (cp == 0) {
+ return -ENOMEM;
+ }
+ memset(cp, 0, sizeof(*cp));
+ cp->skt = -1;
+ *proc = cp;
+ return 0;
+}
+
+int childproc_free(struct childproc *proc) {
+ if (proc->running) {
+ childproc_kill(proc);
+ }
+ free(proc);
+ return 0;
+}
+
+int childproc_kill(struct childproc *proc) {
+ int status;
+ if (!proc->running) {
+ /* already spawned */
+ return -EALREADY;
+ }
+
+ if (proc->skt >= 0) {
+ close(proc->skt);
+ proc->skt = -1;
+ }
+
+ if (kill(proc->pid, SIGINT) == -1) {
+ return -errno;
+ }
+
+ if (waitpid(proc->pid, &status, 0) == -1) {
+ return -errno;
+ }
+ if (WIFEXITED(status)) {
+ switch (WEXITSTATUS(status)) {
+ case CHILDERR_DEV_NULL:
+ printf("Child Process failed to open /dev/null\n");
+ break;
+ case CHILDERR_DUP:
+ printf("Child Process failed to redirect streams\n");
+ break;
+ case CHILDERR_EXEC:
+ printf("Child Process failed to exec ptp4l\n");
+ break;
+ default:
+ printf("Child Process exited with status %d\n",
+ WEXITSTATUS(status));
+ break;
+ }
+ } else if (WIFSIGNALED(status)) {
+ printf("Child Process was signalled %d\n", WTERMSIG(status));
+ }
+
+ proc->running = 0;
+ memset(&proc->seqs, 0, sizeof(proc->seqs));
+ return 0;
+}
+
+#define UDS_FILEMODE (S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP) /*0660*/
+
+static int init_child_skt(struct childproc *proc,
+ const char *bind_name,
+ const char *server_name) {
+ struct sockaddr_un sa;
+ int skt = socket(AF_LOCAL, SOCK_DGRAM, 0);
+ if (skt < 0) {
+ printf("Failed to create socket: %m\n");
+ return -1;
+ }
+ memset(&sa, 0, sizeof(sa));
+ sa.sun_family = AF_LOCAL;
+ strcpy(sa.sun_path, bind_name);
+
+ unlink(bind_name);
+ if (bind(skt, (struct sockaddr*) &sa, sizeof(sa)) == -1) {
+ printf("Failed to bind socket: %m\n");
+ close(skt);
+ return -1;
+ }
+ chmod(bind_name, UDS_FILEMODE);
+
+ proc->skt = skt;
+ proc->pollfd.fd = skt;
+ proc->pollfd.events = POLLIN;
+
+ memset(&proc->server, 0, sizeof(proc->server));
+ proc->server.sun_family = AF_LOCAL;
+ strcpy(proc->server.sun_path, server_name);
+ proc->server_len = offsetof(struct sockaddr_un, sun_path) +
+ strlen(server_name) + 1;
+ return 0;
+}
+
+/* Returns 0 if out and in are management messages and in appears to
+ * be a reply to out */
+static int verify_is_request_reply(struct mgmt_msg *out,
+ struct mgmt_msg *in)
+{
+
+ /*printf("verify\n"
+ "sequenceId %d %d\n"
+ "messageType %d %d\n",
+ out->hdr.sequenceId,
+ in->hdr.sequenceId,
+ out->hdr.messageType,
+ in->hdr.messageType);*/
+
+ if (out->hdr.sequenceId == in->hdr.sequenceId &&
+ out->hdr.messageType == MSG_MANAGEMENT &&
+ in->hdr.messageType == MSG_MANAGEMENT)
+ {
+ /* check that actions make sense */
+ switch (out->msg.actionField) {
+ case ACTION_GET:
+ case ACTION_SET:
+ if (in->msg.actionField != ACTION_RESPONSE) {
+ return -1;
+ }
+ break;
+ case ACTION_COMMAND:
+ if (in->msg.actionField != ACTION_ACKNOWLEDGE) {
+ return -1;
+ }
+ break;
+ default:
+ return -1;
+
+ }
+ switch (in->tlv.type) {
+ case TLV_MANAGEMENT:
+ /*printf("mgmt %d %d\n",
+ out->tlv.mgmt.id,
+ in->tlv.mgmt.id);*/
+ if (out->tlv.mgmt.id == in->tlv.mgmt.id) {
+ return 0;
+ }
+ break;
+ case TLV_MANAGEMENT_ERROR_STATUS:
+ /*printf("mgmterror %d %d\n",
+ out->tlv.mgmt.id,
+ in->tlv.error.managementId);*/
+ if (out->tlv.mgmt.id == in->tlv.error.managementId) {
+ return 0;
+ }
+ break;
+ default:
+ break;
+ }
+ }
+ return -1;
+}
+
+#define NUM_WAIT_SENDS 10
+#define NORMAL_DELAY 30
+#define LONG_DELAY 200
+
+static int wait_for_running(struct childproc *proc) {
+ int i;
+ int err;
+ int sent_successful = 0;
+ struct mgmt_msg msg;
+ struct mgmt_msg recv_msg;
+ uint8_t buf[300];
+ UInteger16 seq;
+ int len;
+
+ /* Repeatedly try send a NULL_MANAGEMENT to the ptp4l process
+ * until it replies. */
+ mgmt_msg_init(&msg, ACTION_GET, MGMT_NULL_MANAGEMENT);
+ for (i = 0; i < NUM_WAIT_SENDS; i++) {
+ /* attempt to send */
+ if (sent_successful == 0) {
+ seq = proc->seqs.mgmt++;
+ msg.hdr.sequenceId = seq;
+ len = mgmt_write_msg(&msg, buf, sizeof(buf));
+ if (len < 0) {
+ printf("Failed to serialize wait msg\n");
+ return -1;
+ }
+ printf("Sending wakeup msg\n");
+ if ((err = childproc_send(proc, buf, len)) < 0) {
+ /* ignore errors that mean the ptp4l
+ process hasn't called bind yet. */
+ if (err != -ECONNREFUSED && err != -ENOENT) {
+ return -1;
+ }
+ } else {
+ sent_successful = 1;
+ }
+ }
+
+ switch(poll(&proc->pollfd, 1,
+ i == NUM_WAIT_SENDS - 1 ? LONG_DELAY : NORMAL_DELAY))
+ {
+ case -1:
+ return -1;
+ break;
+ case 0:
+ /* timeout occured */
+ break;
+ case 1:
+ printf("Receiving wakeup msg\n");
+ len = childproc_recv(proc, buf, sizeof(buf));
+ if (len == -1) {
+ return -1;
+ }
+ if (mgmt_read_msg(buf, len, &recv_msg) < 0) {
+ printf("Unable to read wakeup reply\n");
+ }
+ if (verify_is_request_reply(&msg, &recv_msg) == 0) {
+ return 0;
+ }
+ break;
+ }
+ }
+
+ return -1;
+}
+
+int childproc_spawn(struct childproc *proc, const char *config) {
+ int infds[2];
+ int nullfd;
+ pid_t child;
+ int len;
+ int err;
+
+ if (proc->running) {
+ /* already spawned */
+ return -EALREADY;
+ }
+
+ /* create stdin pipe */
+ if (pipe(infds) == -1) {
+ return -errno;
+ }
+ child = fork();
+ if (child == -1) {
+ err = errno;
+ close(infds[0]);
+ close(infds[1]);
+ return -err;
+ } else if (child == 0) {
+ /* in child */
+ /* redirect stdout and stderr to /dev/null */
+ if ((nullfd = open("/dev/null", O_WRONLY)) == -1) {
+ _exit(CHILDERR_DEV_NULL);
+ }
+ /* redirect streams */
+ if (dup2(nullfd, STDERR_FILENO) == -1) {
+ _exit(CHILDERR_DUP);
+ }
+ if (dup2(nullfd, STDOUT_FILENO) == -1) {
+ _exit(CHILDERR_DUP);
+ }
+ if (dup2(infds[0], STDIN_FILENO) == -1) {
+ _exit(CHILDERR_DUP);
+ }
+ close(infds[0]);
+ close(infds[1]);
+
+ char *argv[7];
+ argv[0] = "ptp4l";
+ argv[1] = "-S"; /* sw timestamps */
+ argv[2] = "-i"; argv[3] = "lo"; /* network interface
+ * */
+ if (config) {
+ argv[4] = "-f"; argv[5] = "-"; /* config from stdin */
+ argv[6] = 0;
+ } else {
+ argv[4] = 0;
+ }
+ execv("../ptp4l", argv);
+ /* if execv returns, an error occured */
+ _exit(CHILDERR_EXEC);
+ } else {
+ /* in parent */
+ close(infds[0]); /* close read end */
+ proc->running = 1;
+ proc->pid = child;
+
+ if (config) {
+ /* write config to the child's stdin */
+ len = strlen(config);
+ if ((err = write(infds[1], config, len)) == -1) {
+ printf("failed to write config file %m\n");
+ goto kill_child;
+ }
+ if (err < len) {
+ printf("Only able to partially write config file %d < %d\n", err, len);
+ }
+ }
+ close(infds[1]);
+
+ /* open the ptp4l's unix domain socket.
+ *
+ * TODO: This currently uses a name hardcoded in
+ * ptp4l. Would be better to support passing the
+ * socket name to ptp4l. Possibly even use an abstract
+ * socket address and avoid filesystem completely..
+ */
+ if (init_child_skt(proc, "/tmp/ptptest", UDS_PATH) != 0) {
+ goto kill_child;
+ }
+
+
+ if (wait_for_running(proc) != 0) {
+ goto kill_child;
+ }
+ return 0;
+ }
+kill_child:
+ childproc_kill(proc);
+ return -1;
+}
+
+int childproc_pid(struct childproc *proc) {
+ if (proc->running) {
+ return proc->pid;
+ }
+ return -1;
+}
+
+int childproc_skt(struct childproc *proc) {
+ return proc->skt;
+}
+
+struct sequence_ids *childproc_seqs(struct childproc *proc) {
+ return &proc->seqs;
+}
+
+ssize_t childproc_send(struct childproc *proc, uint8_t *buf, size_t len) {
+ /*printf("Sending to fd %d msg len %zd\n", proc->skt, len);*/
+ ssize_t result = sendto(proc->skt, buf, len, 0,
+ (struct sockaddr*) &proc->server,
+ sizeof(proc->server));
+ if (result == -1) {
+ result = -errno;
+ printf("sendto UDS failed(%d): %m\n", errno);
+ }
+ return result;
+}
+
+ssize_t childproc_recv(struct childproc *proc, uint8_t *buf, size_t len) {
+ struct sockaddr_un sa;
+ socklen_t sa_len = sizeof(sa);
+ ssize_t result = recvfrom(proc->skt, buf, len, 0,
+ (struct sockaddr*) &sa, &sa_len);
+ if (result == -1) {
+ result = -errno;
+ printf("recvfrom UDS failed(%d): %m\n", errno);
+ } else {
+ /* ensure message came from the server */
+ if (sa_len != proc->server_len ||
+ strcmp(proc->server.sun_path, sa.sun_path) != 0) {
+ printf("Ignoring message from unknown sender\n");
+ return 0;
+ }
+ }
+ return result;
+}
+
+#define RECV_TIMEOUT 500
+
+int childproc_send_recv(struct childproc *proc,
+ struct mgmt_msg *out,
+ struct mgmt_msg *in)
+{
+ uint8_t buf[1500];
+ int len;
+ int err;
+
+ out->hdr.sequenceId = proc->seqs.mgmt++;
+ len = mgmt_write_msg(out, buf, sizeof(buf));
+ if (len < 0) {
+ printf("Failed to serialize msg\n");
+ return -1;
+ }
+ if ((err = childproc_send(proc, buf, len)) < 0) {
+ return err;
+ }
+ return childproc_recv_response(proc, out, in);
+}
+
+int childproc_recv_response(struct childproc *proc,
+ struct mgmt_msg *out,
+ struct mgmt_msg *in)
+{
+ uint8_t buf[1500];
+ int len;
+ switch (poll(&proc->pollfd, 1, RECV_TIMEOUT)) {
+ case -1:
+ printf("poll failed: %m\n");
+ return -1;
+ case 0:
+ /* timeout */
+ return -1;
+ case 1:
+ len = childproc_recv(proc, buf, sizeof(buf));
+ if (len == -1) {
+ return -1;
+ }
+ printf("RECV %d bytes\n", len);
+ if (mgmt_read_msg(buf, len, in) < 0) {
+ printf("Unable to read response\n");
+ return -1;
+ }
+ if (verify_is_request_reply(out, in) == 0) {
+ return 0;
+ }
+ break;
+ }
+ return -1;
+}
diff --git a/tests/childproc.h b/tests/childproc.h
new file mode 100644
index 0000000..4c02e42
--- /dev/null
+++ b/tests/childproc.h
@@ -0,0 +1,55 @@
+/**
+ * @file childproc.h
+ * @brief Utilities for forking a ptp4l process to test
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef TESTS_CHILDPROC_H
+#define TESTS_CHILDPROC_H
+
+#include <inttypes.h>
+#include <sys/types.h>
+
+#include "mgmt.h"
+
+/*
+ Support for running ptp4l as a child process.
+ */
+
+struct childproc;
+
+int childproc_new(struct childproc **proc);
+int childproc_free(struct childproc *proc);
+
+int childproc_spawn(struct childproc *proc, const char *config);
+int childproc_kill(struct childproc *proc);
+
+int childproc_pid(struct childproc *proc);
+int childproc_skt(struct childproc *proc);
+
+struct sequence_ids *childproc_seqs(struct childproc *proc);
+
+ssize_t childproc_send(struct childproc *proc, uint8_t *buf, size_t len);
+ssize_t childproc_recv(struct childproc *proc, uint8_t *buf, size_t len);
+
+int childproc_send_recv(struct childproc *proc,
+ struct mgmt_msg *out,
+ struct mgmt_msg *in);
+int childproc_recv_response(struct childproc *proc,
+ struct mgmt_msg *out,
+ struct mgmt_msg *in);
+
+#endif
diff --git a/tests/description-t.c b/tests/description-t.c
new file mode 100644
index 0000000..f9cd9ff
--- /dev/null
+++ b/tests/description-t.c
@@ -0,0 +1,87 @@
+/**
+ * @file description-t.c
+ * @brief Tests management CLOCK_DESCRIPTION
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "tap/basic.h"
+#include "testutil.h"
+
+const char *config =
+"[global]\n\
+product_description product\n\
+revision_data revision\n\
+user_description user\n\
+manufacturer_id aa:bb:cc\n\
+";
+
+int cmp_text(const struct PTPText *pt, const char *t) {
+ int len = strlen(t);
+ if (pt->length != len) return -1;
+ return memcmp(pt->text, t, len);
+}
+
+int main() {
+ struct childproc *proc = test_childproc(0);
+ plan(16);
+ struct mgmt_msg out;
+ struct mgmt_msg in;
+
+ /* Get CLOCK_DESCRIPTION */
+ mgmt_msg_init(&out, ACTION_GET, MGMT_CLOCK_DESCRIPTION);
+ ok(childproc_send_recv(proc, &out, &in) == 0, "send and recv GET CLOCK_DESCRIPTION");
+ ok(cmp_text(&in.tlv.mgmt.data.cd.productDescription, "") == 0, "default productDescription");
+ ok(cmp_text(&in.tlv.mgmt.data.cd.revisionData, "") == 0, "default revisionData");
+ ok(cmp_text(&in.tlv.mgmt.data.cd.userDescription, "") == 0, "default userDescription");
+
+ ok(in.tlv.mgmt.data.cd.manufacturerIdentity[0] == 0 &&
+ in.tlv.mgmt.data.cd.manufacturerIdentity[1] == 0 &&
+ in.tlv.mgmt.data.cd.manufacturerIdentity[2] == 0,
+ "default manufacturerIdentity");
+
+ /* restart ptp4l with a config file */
+ proc = test_restart_childproc(config);
+
+ /* Get USER_DESCRIPTION */
+ mgmt_msg_init(&out, ACTION_GET, MGMT_USER_DESCRIPTION);
+ ok(childproc_send_recv(proc, &out, &in) == 0, "send and recv GET USER_DESCRIPTION");
+ ok(cmp_text(&in.tlv.mgmt.data.cd.userDescription, "user") == 0, "config userDescription");
+
+ /* Get CLOCK_DESCRIPTION */
+ mgmt_msg_init(&out, ACTION_GET, MGMT_CLOCK_DESCRIPTION);
+ ok(childproc_send_recv(proc, &out, &in) == 0, "send and recv GET CLOCK_DESCRIPTION");
+ ok(cmp_text(&in.tlv.mgmt.data.cd.productDescription, "product") == 0, "config productDescription");
+ ok(cmp_text(&in.tlv.mgmt.data.cd.revisionData, "revision") == 0, "config revisionData");
+ ok(cmp_text(&in.tlv.mgmt.data.cd.userDescription, "user") == 0, "config userDescription");
+
+ ok(in.tlv.mgmt.data.cd.manufacturerIdentity[0] == 0xaa &&
+ in.tlv.mgmt.data.cd.manufacturerIdentity[1] == 0xbb &&
+ in.tlv.mgmt.data.cd.manufacturerIdentity[2] == 0xcc,
+ "config manufacturerIdentity");
+
+ /* Set a new user description "ok" */
+ mgmt_msg_init(&out, ACTION_SET, MGMT_USER_DESCRIPTION);
+ out.tlv.mgmt.data.cd.userDescription.length = 2;
+ out.tlv.mgmt.data.cd.userDescription.text = (Octet*)"ok";
+ ok(childproc_send_recv(proc, &out, &in) == 0, "send and recv SET USER_DESCRIPTION");
+ ok(cmp_text(&in.tlv.mgmt.data.cd.userDescription, "ok") == 0, "set userDescription");
+
+ /* Double check that set "ok" stuck */
+ mgmt_msg_init(&out, ACTION_GET, MGMT_CLOCK_DESCRIPTION);
+ ok(childproc_send_recv(proc, &out, &in) == 0, "send and recv GET CLOCK_DESCRIPTION");
+ ok(cmp_text(&in.tlv.mgmt.data.cd.userDescription, "ok") == 0, "config userDescription");
+ return 0;
+}
diff --git a/tests/errors-t.c b/tests/errors-t.c
new file mode 100644
index 0000000..61e9150
--- /dev/null
+++ b/tests/errors-t.c
@@ -0,0 +1,121 @@
+/**
+ * @file errors-t.c
+ * @brief Tests management error messages
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "tap/basic.h"
+#include "testutil.h"
+
+#include "pdt_pack.h"
+
+void send_check_error(struct childproc *proc, struct mgmt_msg *send,
+ uint8_t *buf, int len,
+ enum management_error_types error)
+{
+ struct mgmt_msg recv;
+ if (childproc_send(proc, buf, len) < 0) {
+ ok_block(2, 0, "send request failed");
+ } else if (childproc_recv_response(proc, send, &recv) == 0) {
+ ok(recv.tlv.type == TLV_MANAGEMENT_ERROR_STATUS,
+ "receive error");
+ ok(recv.tlv.error.managementErrorId == error,
+ "correct error %d =?= %d",
+ recv.tlv.error.managementErrorId, error);
+ } else {
+ ok_block(2, 0, "recv response failed");
+ }
+}
+
+/* Send malformed messages and ensure errors are returned */
+int main() {
+ int len;
+ uint8_t buf[1500];
+ UInteger16 *msg_len_field = (UInteger16 *)(buf + 2);
+ UInteger16 *tlv_len_field = (UInteger16 *)(buf + 34 + 14 + 2);
+ UInteger16 *mgmt_id_field = (UInteger16 *)(buf + 34 + 14 + 4);
+ struct childproc *proc = test_childproc(0);
+ Enumeration16 val;
+ plan(10);
+
+ struct mgmt_msg out;
+
+ /* invalid management id */
+ mgmt_msg_init(&out, ACTION_GET, MGMT_NULL_MANAGEMENT);
+ out.hdr.sequenceId = childproc_seqs(proc)->mgmt++;
+ len = mgmt_write_msg(&out, buf, sizeof(buf));
+ if (len < 0) {
+ printf("Failed to serialize msg\n");
+ return -1;
+ }
+
+ /* tweak management id to invalid one */
+ val = 0xFFF0;
+ out.tlv.mgmt.id = val;
+ packUInteger16(&val, mgmt_id_field);
+
+ send_check_error(proc, &out, buf, len, MGMT_ERROR_NO_SUCH_ID);
+
+
+ /* DEFAULT_DATA_SET is not settable */
+ mgmt_msg_init(&out, ACTION_SET, MGMT_DEFAULT_DATA_SET);
+ out.hdr.sequenceId = childproc_seqs(proc)->mgmt++;
+ len = mgmt_write_msg(&out, buf, sizeof(buf));
+ if (len < 0) {
+ printf("Failed to serialize msg\n");
+ return -1;
+ }
+ send_check_error(proc, &out, buf, len, MGMT_ERROR_NOT_SETABLE);
+
+ /* ENABLE_PORT is a command so setting is not supported */
+ mgmt_msg_init(&out, ACTION_SET, MGMT_ENABLE_PORT);
+ out.hdr.sequenceId = childproc_seqs(proc)->mgmt++;
+ len = mgmt_write_msg(&out, buf, sizeof(buf));
+ if (len < 0) {
+ printf("Failed to serialize msg\n");
+ return -1;
+ }
+ /* TODO: Should this be MGMT_ERROR_NOT_SETABLE instead? */
+ send_check_error(proc, &out, buf, len, MGMT_ERROR_NOT_SUPPORTED);
+
+ /* Test invalid tlv sizes */
+ mgmt_msg_init(&out, ACTION_SET, MGMT_USER_DESCRIPTION);
+ out.hdr.sequenceId = childproc_seqs(proc)->mgmt++;
+ out.tlv.mgmt.data.cd.userDescription.length = 5;
+ out.tlv.mgmt.data.cd.userDescription.text = (uint8_t*)"hello";
+ len = mgmt_write_msg(&out, buf, sizeof(buf));
+ if (len < 0) {
+ printf("Failed to serialize msg\n");
+ return -1;
+ }
+ UInteger16 msg_len;
+ unpackUInteger16(msg_len_field, &msg_len);
+
+ /* tlv len too small */
+ val = msg_len - 5;
+ packUInteger16(&val, msg_len_field);
+ val = 3;
+ packUInteger16(&val, tlv_len_field);
+ send_check_error(proc, &out, buf, len - 5, MGMT_ERROR_WRONG_LENGTH);
+
+ /* tlv len too large */
+ val = msg_len + 2;
+ packUInteger16(&val, msg_len_field);
+ val = 10;
+ packUInteger16(&val, tlv_len_field);
+ send_check_error(proc, &out, buf, len + 2, MGMT_ERROR_WRONG_LENGTH);
+ return 0;
+}
diff --git a/tests/example-t.c b/tests/example-t.c
new file mode 100644
index 0000000..d7b8137
--- /dev/null
+++ b/tests/example-t.c
@@ -0,0 +1,30 @@
+/**
+ * @file example-t.c
+ * @brief Example of a test
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "tap/basic.h"
+#include "testutil.h"
+
+int main() {
+ struct childproc *proc;
+ plan(1);
+
+ proc = test_childproc(0);
+ ok(0 < childproc_pid(proc), "valid pid");
+ return 0;
+}
diff --git a/tests/faultlog-t.c b/tests/faultlog-t.c
new file mode 100644
index 0000000..f51a305
--- /dev/null
+++ b/tests/faultlog-t.c
@@ -0,0 +1,189 @@
+/**
+ * @file faultlog-t.c
+ * @brief Tests FaultLog packing and unpacking
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "tap/basic.h"
+#include "testutil.h"
+
+#include <string.h>
+
+#include "tlv.h"
+#include "ddt_pack.h"
+
+int cmp_timestamp(struct Timestamp *t1, struct Timestamp *t2) {
+ if (t1->seconds_msb != t2->seconds_msb ||
+ t1->seconds_lsb != t2->seconds_lsb ||
+ t1->nanoseconds != t2->nanoseconds)
+ return -1;
+ return 0;
+}
+
+int cmp_ptptext(struct PTPText *t1, struct PTPText *t2) {
+ if (t1->length != t2->length)
+ return -1;
+ return memcmp(t1->text, t2->text, t1->length);
+}
+
+int cmp_record(struct FaultRecord *r1, struct FaultRecord *r2) {
+ if (cmp_timestamp(&r1->faultTime, &r2->faultTime)) return -1;
+ if (r1->severityCode != r2->severityCode) return -1;
+ if (cmp_ptptext(&r1->faultName, &r2->faultName)) return -1;
+ if (cmp_ptptext(&r1->faultValue, &r2->faultValue)) return -1;
+ if (cmp_ptptext(&r1->faultDescription, &r2->faultDescription)) return -1;
+ return 0;
+}
+
+int cmp_logs(struct FaultLog *l1, struct FaultLog *l2) {
+ int offset;
+ int i;
+ struct FaultRecord fault, *f;
+ struct FaultLog *t;
+ if (l1->type == l2->type) {
+ if (l1->type == FAULT_RECORD_BUFFER) {
+ /* both buffers */
+ if (l1->buffer.len != l2->buffer.len)
+ return -1;
+ return memcmp(l1->buffer.buf, l2->buffer.buf, l1->buffer.len);
+ } else {
+ /* both lists - this shouldn't occur in tests */
+ return -1;
+ }
+ } else {
+ /* one of each type */
+ if (l1->type == FAULT_RECORD_BUFFER) {
+ /* swap so l1 is always the list type */
+ t = l1;
+ l1 = l2;
+ l2 = t;
+ }
+
+ /* When fault records are written, if all of the
+ * records don't fit in the message then the records
+ * are skipped. How should the tests and comparison
+ * code account for that?
+ */
+ f = TAILQ_FIRST(&l1->list);
+ offset = 0;
+ for (i = 0; i < l2->numberRecords; i++) {
+ if (f == NULL) {
+ /* ran out of l1 records */
+ return -1;
+ }
+ offset += unpackFaultRecord(l2->buffer.buf + offset, &fault);
+ if (cmp_record(f, &fault))
+ return -1;
+ f = TAILQ_NEXT(f, entry);
+ }
+ if (f != NULL) {
+ /* ran out of l2 records */
+ return -1;
+ }
+ return 0;
+ }
+ return -1;
+}
+
+void set_text(struct PTPText *t, char *s) {
+ if (s) {
+ t->text = (uint8_t*)s;
+ t->length = strlen(s);
+ } else {
+ t->text = 0;
+ t->length = 0;
+ }
+}
+
+static int test_read(uint8_t *buf, int len, struct tlv *tlv, const char *msg) {
+ ok(len == tlv_read(buf, len, tlv), msg);
+ ok(tlv->mgmt.error == 0, "read successful");
+ return tlv->mgmt.error == 0;
+}
+
+
+int main() {
+ plan(18);
+
+ struct tlv tlv;
+ struct FaultLog *log = &tlv.mgmt.data.fl;
+
+ memset(&tlv, 0, sizeof(tlv));
+ tlv.type = TLV_MANAGEMENT;
+ tlv.mgmt.id = MGMT_FAULT_LOG;
+ log->numberRecords = 0;
+ log->type = FAULT_RECORD_LIST;
+ TAILQ_INIT(&log->list);
+
+ struct tlv recv_tlv;
+
+ uint8_t buf[1500];
+ int len;
+ len = tlv_write(&tlv, buf, sizeof(buf));
+ ok(len > 0, "write empty list log");
+
+ if (test_read(buf, len, &recv_tlv, "read empty log"))
+ ok(0 == cmp_logs(log, &recv_tlv.mgmt.data.fl), "empty logs match");
+ else
+ skip("can't compare after failed read");
+
+ log->type = FAULT_RECORD_BUFFER;
+ log->buffer.len = 0;
+ len = tlv_write(&tlv, buf, sizeof(buf));
+ ok(len > 0, "write empty buffer log");
+ printf("write=%d read=%d\n", len, tlv_read(buf, len, &recv_tlv));
+ if(test_read(buf, len, &recv_tlv, "read empty buffer log"))
+ ok(0 == cmp_logs(log, &recv_tlv.mgmt.data.fl), "empty logs match");
+ else
+ skip("can't compare after failed read");
+
+ struct FaultRecord r1, r2;
+ memset(&r1, 0, sizeof(r1));
+ memset(&r2, 0, sizeof(r2));
+
+ r1.severityCode = FAULT_ALERT;
+ set_text(&r1.faultName, "explosion");
+ set_text(&r1.faultValue, NULL);
+ set_text(&r1.faultValue, "lots of smoke");
+
+ log->type = FAULT_RECORD_LIST;
+ TAILQ_INIT(&log->list);
+ TAILQ_INSERT_TAIL(&log->list, &r1, entry);
+
+ len = tlv_write(&tlv, buf, sizeof(buf));
+ ok(len > 0, "write 1 log");
+
+ if (test_read(buf, len, &recv_tlv, "read log")) {
+ ok(recv_tlv.mgmt.data.fl.numberRecords == 1, "got 1 record");
+ ok(0 == cmp_logs(log, &recv_tlv.mgmt.data.fl), "1 record matches");
+ } else
+ skip_block(2, "can't compare after failed read");
+
+ r2.severityCode = FAULT_NOTICE;
+ set_text(&r2.faultName, "meal");
+ set_text(&r2.faultValue, "dinner");
+ set_text(&r2.faultValue, "fish and chips");
+ TAILQ_INSERT_TAIL(&log->list, &r2, entry);
+
+ len = tlv_write(&tlv, buf, sizeof(buf));
+ ok(len > 0, "write 2 logs");
+ if(test_read(buf, len, &recv_tlv, "read logs")) {
+ ok(recv_tlv.mgmt.data.fl.numberRecords == 2, "got 2 records");
+ ok(0 == cmp_logs(log, &recv_tlv.mgmt.data.fl), "2 record logs match");
+ } else
+ skip_block(2, "can't compare after failed read");
+ return 0;
+}
diff --git a/tests/makefile b/tests/makefile
new file mode 100644
index 0000000..318c011
--- /dev/null
+++ b/tests/makefile
@@ -0,0 +1,71 @@
+#
+# Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation; either version 2 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License along
+# with this program; if not, write to the Free Software Foundation, Inc.,
+# 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+
+DEBUG =
+CC = $(CROSS_COMPILE)gcc
+INC = -I..
+CFLAGS = -Wall -Werror $(INC) $(DEBUG)$(EXTRA_CFLAGS)
+LDLIBS = $(EXTRA_LDFLAGS)
+
+PRG = runtests example-t childproc-t errors-t priority-t description-t faultlog-t
+
+OBJECTS = $(PRG:=.o) tap/basic.o testutil.o childproc.o mgmt.o
+SRC = $(OBJECTS:.o=.c)
+DEPEND = $(OBJECTS:.o=.d)
+srcdir := $(dir $(lastword $(MAKEFILE_LIST)))
+VPATH = $(srcdir)
+
+all: $(PRG)
+
+clean:
+ rm -f $(OBJECTS) $(DEPEND)
+
+distclean: clean
+ rm -f $(PRG)
+
+TEST_OBJS = testutil.o tap/basic.o childproc.o mgmt.o ../tlv.o
+
+example-t: example-t.o $(TEST_OBJS)
+childproc-t: childproc-t.o $(TEST_OBJS)
+errors-t: errors-t.o $(TEST_OBJS)
+priority-t: priority-t.o $(TEST_OBJS)
+description-t: description-t.o $(TEST_OBJS)
+faultlog-t: faultlog-t.o $(TEST_OBJS)
+
+#%-t.o: %-t.c
+# $(CC) -c $(CFLAGS) $(CPPFLAGS) $< -o $@
+
+runtests: runtests.o
+# $(CC) -DSOURCE='"tests"' -DBUILD='"tests"' $< -o $@
+
+check: all
+ ./runtests TESTS
+
+
+# Implicit rule to generate a C source file's dependencies.
+%.d: %.c
+ @echo DEPEND $<; \
+ rm -f $@; \
+ $(CC) -MM $(CPPFLAGS) $(CFLAGS) $< > $@.$$$$; \
+ sed 's,\($*\)\.o[ :]*,\1.o $@ : ,g' < $@.$$$$ > $@; \
+ rm -f $@.$$$$
+
+ifneq ($(MAKECMDGOALS), clean)
+ifneq ($(MAKECMDGOALS), distclean)
+-include $(DEPEND)
+endif
+endif
diff --git a/tests/mgmt.c b/tests/mgmt.c
new file mode 100644
index 0000000..f3d57f0
--- /dev/null
+++ b/tests/mgmt.c
@@ -0,0 +1,239 @@
+/**
+ * @file mgmt.c
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "mgmt.h"
+
+#include <string.h>
+
+#include "tlv_pack.h"
+#include "tlv.h"
+#include "msg.h"
+
+static const int size_msg_header = 1 + 1 + 2 + 1 + 1 + 2 + 8 + 4 + 10 + 2 + 1 + 1;
+static const int size_mgmt_msg = 10 + 1 + 1 + 1 + 1;
+
+static inline int packMsgHeader(const struct MsgHeader *data, Octet *buf) {
+ int offset = 0;
+ packNibbleUpper(&data->transportSpecific, buf + offset);
+ packEnumeration4Lower(&data->messageType, buf + offset);
+ offset += 1;
+ packReserved(buf + offset, 0);
+ packUInteger4Lower(&data->versionPTP, buf + offset);
+ offset += 1;
+ packUInteger16(&data->messageLength, buf + offset);
+ offset += 2;
+ packUInteger8(&data->domainNumber, buf + offset);
+ offset += 1;
+ packReserved(buf + offset, 1);
+ offset += 1;
+ packOctetArray(data->flagField, buf + offset, 2);
+ offset += 2;
+ packInteger64(&data->correctionField, buf + offset);
+ offset += 8;
+ packReserved(buf + offset, 4);
+ offset += 4;
+ packPortIdentity(&data->sourcePortIdentity, buf + offset);
+ offset += 10;
+ packUInteger16(&data->sequenceId, buf + offset);
+ offset += 2;
+ packUInteger8(&data->controlField, buf + offset);
+ offset += 1;
+ packInteger8(&data->logMessageInterval, buf + offset);
+ offset += 1;
+ return offset;
+}
+
+static inline int packManagementMsg(const struct ManagementMsg *data, Octet *buf) {
+ int offset = 0;
+ packPortIdentity(&data->targetPortIdentity, buf + offset);
+ offset += 10;
+ packUInteger8(&data->startingBoundaryHops, buf + offset);
+ offset += 1;
+ packUInteger8(&data->boundaryHops, buf + offset);
+ offset += 1;
+ packReserved(buf + offset, 0);
+ packEnumeration4Lower(&data->actionField, buf + offset);
+ offset += 1;
+ packReserved(buf + offset, 1);
+ offset += 1;
+ return offset;
+}
+
+static inline int unpackManagementMsg(const Octet *buf, struct ManagementMsg *data) {
+ int offset = 0;
+ unpackPortIdentity(buf + offset, &data->targetPortIdentity);
+ offset += 10;
+ unpackUInteger8(buf + offset, &data->startingBoundaryHops);
+ offset += 1;
+ unpackUInteger8(buf + offset, &data->boundaryHops);
+ offset += 1;
+ unpackEnumeration4Lower(buf + offset, &data->actionField);
+ offset += 1;
+ offset += 1;
+ return offset;
+}
+
+static inline int unpackMsgHeader(const Octet *buf, struct MsgHeader *data) {
+ int offset = 0;
+ unpackNibbleUpper(buf + offset, &data->transportSpecific);
+ unpackEnumeration4Lower(buf + offset, &data->messageType);
+ offset += 1;
+ unpackUInteger4Lower(buf + offset, &data->versionPTP);
+ offset += 1;
+ unpackUInteger16(buf + offset, &data->messageLength);
+ offset += 2;
+ unpackUInteger8(buf + offset, &data->domainNumber);
+ offset += 1;
+ offset += 1;
+ unpackOctetArray(buf + offset, data->flagField, 2);
+ offset += 2;
+ unpackInteger64(buf + offset, &data->correctionField);
+ offset += 8;
+ offset += 4;
+ unpackPortIdentity(buf + offset, &data->sourcePortIdentity);
+ offset += 10;
+ unpackUInteger16(buf + offset, &data->sequenceId);
+ offset += 2;
+ unpackUInteger8(buf + offset, &data->controlField);
+ offset += 1;
+ unpackInteger8(buf + offset, &data->logMessageInterval);
+ offset += 1;
+ return offset;
+}
+
+static void msg_hdr_init(struct MsgHeader *hdr) {
+ hdr->transportSpecific = 0;
+ hdr->messageType = MSG_MANAGEMENT;
+ hdr->versionPTP = 2;
+ /* hdr->messageLength = 0; is set when writing */
+ hdr->domainNumber = 0;
+ hdr->flagField[0] = 0;
+ hdr->flagField[1] = 0;
+ hdr->correctionField = 0;
+ memset(&hdr->sourcePortIdentity.clockIdentity, 0,
+ sizeof(struct ClockIdentity));
+ hdr->sourcePortIdentity.portNumber = 0;
+ hdr->sequenceId = 0;
+ hdr->controlField = CONTROL_MANAGEMENT;
+ hdr->logMessageInterval = 0x7F;
+}
+
+static void msg_init(struct mgmt_msg *m, uint8_t hops, enum management_actions action) {
+ memset(m, 0, sizeof(*m));
+ msg_hdr_init(&m->hdr);
+ /* use the wildcard clock identity by default */
+ memset(&m->msg.targetPortIdentity.clockIdentity, 0xFF, CLOCK_IDENTITY_LENGTH);
+ /* use the wildcard port identity by default */
+ m->msg.targetPortIdentity.portNumber = 0xFFFF;
+ m->msg.startingBoundaryHops = hops;
+ m->msg.boundaryHops = hops;
+ m->msg.actionField = action;
+ /* m->tlv.lengthField = 0; is set when writing */
+}
+
+void mgmt_msg_init(struct mgmt_msg *m, enum management_actions action,
+ enum management_types type)
+{
+ msg_init(m, 0, action);
+ m->tlv.type = TLV_MANAGEMENT;
+ m->tlv.mgmt.id = type;
+ m->tlv.mgmt.empty_body = action == ACTION_GET;
+}
+
+void mgmt_error_msg_init(struct mgmt_msg *m,
+ enum management_actions action,
+ enum management_error_types error,
+ enum management_types type)
+{
+ msg_init(m, 0, action);
+ m->tlv.type = TLV_MANAGEMENT_ERROR_STATUS;
+ m->tlv.error.managementErrorId = error;
+ m->tlv.error.managementId = type;
+}
+
+int mgmt_write_msg(struct mgmt_msg *msg, uint8_t *buf, int len) {
+ int offset;
+ int result;
+ if (msg->hdr.messageType != MSG_MANAGEMENT) {
+ return -1;
+ }
+
+ len -= size_msg_header + size_mgmt_msg;
+ if (len < 0)
+ return -1;
+
+ offset = packMsgHeader(&msg->hdr, buf);
+ offset += packManagementMsg(&msg->msg, buf + offset);
+
+ /* write single TLV */
+ result = tlv_write(&msg->tlv, buf + offset, len - offset);
+ if (result < 0)
+ return -1;
+
+ offset += result;
+
+ /* update message length in both the msg struct and
+ * written buffer.
+ */
+ msg->hdr.messageLength = offset;
+ packUInteger16(&msg->hdr.messageLength, buf + 2);
+ return offset;
+}
+
+int mgmt_read_msg(uint8_t *buf, int len, struct mgmt_msg *msg) {
+ int result;
+ int offset = unpackMsgHeader(buf, &msg->hdr);
+ if (offset > len) return -1;
+ if (msg->hdr.messageType != MSG_MANAGEMENT) return -1;
+ offset += unpackManagementMsg(buf + offset, &msg->msg);
+ if (offset > len) return -1;
+
+ /* read a single TLV */
+ result = tlv_read(buf + offset, len - offset, &msg->tlv);
+ if (result < 0)
+ return -1;
+
+ switch (msg->tlv.type) {
+ case TLV_MANAGEMENT:
+ if (tlv_check_mgmt_action(msg->msg.actionField, msg->tlv.mgmt.id) != 1) {
+ return -1;
+ }
+ if (msg->tlv.mgmt.empty_body && msg->tlv.mgmt.id != MGMT_NULL_MANAGEMENT) {
+ if (msg->msg.actionField == ACTION_SET ||
+ msg->msg.actionField == ACTION_RESPONSE) {
+ /* SET and RESPONSE must have bodies */
+ return -1;
+ }
+ }
+ break;
+ case TLV_MANAGEMENT_ERROR_STATUS:
+ if (msg->msg.actionField != ACTION_RESPONSE &&
+ msg->msg.actionField != ACTION_ACKNOWLEDGE) {
+ return -1;
+ }
+ break;
+ default:
+ return -1;
+ }
+
+ offset += result;
+ /* require that whole buffer is read */
+ if (offset != len)
+ return -1;
+ return offset;
+}
diff --git a/tests/mgmt.h b/tests/mgmt.h
new file mode 100644
index 0000000..1f3067d
--- /dev/null
+++ b/tests/mgmt.h
@@ -0,0 +1,68 @@
+/**
+ * @file mgmt.h
+ * @brief Utils for reading/writing management messages
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef HAVE_TESTS_MGMT_H
+#define HAVE_TESTS_MGMT_H
+
+#include "tlv.h"
+#include "ds.h"
+
+struct MsgHeader {
+ Nibble transportSpecific;
+ Enumeration4 messageType;
+ UInteger4 versionPTP;
+ UInteger16 messageLength;
+ UInteger8 domainNumber;
+ Octet flagField[2];
+ Integer64 correctionField;
+ struct PortIdentity sourcePortIdentity;
+ UInteger16 sequenceId;
+ UInteger8 controlField;
+ Integer8 logMessageInterval;
+};
+
+struct ManagementMsg {
+ struct PortIdentity targetPortIdentity;
+ UInteger8 startingBoundaryHops;
+ UInteger8 boundaryHops;
+ Enumeration4 actionField;
+};
+
+struct mgmt_msg {
+ struct MsgHeader hdr;
+ struct ManagementMsg msg;
+
+ /* Manangement messages contain 1 TLV. Assume profiles don't
+ * add TLVs to management messages. */
+ struct tlv tlv;
+};
+
+void mgmt_msg_init(struct mgmt_msg *m,
+ enum management_actions action,
+ enum management_types type);
+
+void mgmt_error_msg_init(struct mgmt_msg *m,
+ enum management_actions action,
+ enum management_error_types error,
+ enum management_types type);
+
+int mgmt_write_msg(struct mgmt_msg *msg, uint8_t *buf, int len);
+int mgmt_read_msg(uint8_t *buf, int len, struct mgmt_msg *msg);
+
+#endif
diff --git a/tests/priority-t.c b/tests/priority-t.c
new file mode 100644
index 0000000..d958681
--- /dev/null
+++ b/tests/priority-t.c
@@ -0,0 +1,39 @@
+/**
+ * @file priority-t.c
+ * @brief Tests management PRIORITY1 messages
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "tap/basic.h"
+#include "testutil.h"
+
+
+const char *config =
+"[global]\n\
+priority1 42\n\
+";
+
+int main() {
+ struct childproc *proc = test_childproc(config);
+ plan(2);
+
+ struct mgmt_msg out;
+ struct mgmt_msg in;
+ mgmt_msg_init(&out, ACTION_GET, MGMT_PRIORITY1);
+ ok(childproc_send_recv(proc, &out, &in) == 0, "send and recv GET");
+ ok(in.tlv.mgmt.data.dds.priority1 == 42, "priority1 is 42 as in config");
+ return 0;
+}
diff --git a/tests/runtests.c b/tests/runtests.c
new file mode 100644
index 0000000..c361def
--- /dev/null
+++ b/tests/runtests.c
@@ -0,0 +1,1204 @@
+/*
+ * Run a set of tests, reporting results.
+ *
+ * Usage:
+ *
+ * runtests [-b <build-dir>] [-s <source-dir>] <test-list>
+ * runtests -o [-b <build-dir>] [-s <source-dir>] <test>
+ *
+ * In the first case, expects a list of executables located in the given file,
+ * one line per executable. For each one, runs it as part of a test suite,
+ * reporting results. Test output should start with a line containing the
+ * number of tests (numbered from 1 to this number), optionally preceded by
+ * "1..", although that line may be given anywhere in the output. Each
+ * additional line should be in the following format:
+ *
+ * ok <number>
+ * not ok <number>
+ * ok <number> # skip
+ * not ok <number> # todo
+ *
+ * where <number> is the number of the test. An optional comment is permitted
+ * after the number if preceded by whitespace. ok indicates success, not ok
+ * indicates failure. "# skip" and "# todo" are a special cases of a comment,
+ * and must start with exactly that formatting. They indicate the test was
+ * skipped for some reason (maybe because it doesn't apply to this platform)
+ * or is testing something known to currently fail. The text following either
+ * "# skip" or "# todo" and whitespace is the reason.
+ *
+ * As a special case, the first line of the output may be in the form:
+ *
+ * 1..0 # skip some reason
+ *
+ * which indicates that this entire test case should be skipped and gives a
+ * reason.
+ *
+ * Any other lines are ignored, although for compliance with the TAP protocol
+ * all lines other than the ones in the above format should be sent to
+ * standard error rather than standard output and start with #.
+ *
+ * This is a subset of TAP as documented in Test::Harness::TAP or
+ * TAP::Parser::Grammar, which comes with Perl.
+ *
+ * If the -o option is given, instead run a single test and display all of its
+ * output. This is intended for use with failing tests so that the person
+ * running the test suite can get more details about what failed.
+ *
+ * If built with the C preprocessor symbols SOURCE and BUILD defined, C TAP
+ * Harness will export those values in the environment so that tests can find
+ * the source and build directory and will look for tests under both
+ * directories. These paths can also be set with the -b and -s command-line
+ * options, which will override anything set at build time.
+ *
+ * Any bug reports, bug fixes, and improvements are very much welcome and
+ * should be sent to the e-mail address below. This program is part of C TAP
+ * Harness <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2000, 2001, 2004, 2006, 2007, 2008, 2009, 2010, 2011
+ * Russ Allbery <***@stanford.edu>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+*/
+
+/* Required for fdopen(), getopt(), and putenv(). */
+#if defined(__STRICT_ANSI__) || defined(PEDANTIC)
+# ifndef _XOPEN_SOURCE
+# define _XOPEN_SOURCE 500
+# endif
+#endif
+
+#include <ctype.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <strings.h>
+#include <sys/stat.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <time.h>
+#include <unistd.h>
+
+/* sys/time.h must be included before sys/resource.h on some platforms. */
+#include <sys/resource.h>
+
+/* AIX doesn't have WCOREDUMP. */
+#ifndef WCOREDUMP
+# define WCOREDUMP(status) ((unsigned)(status) & 0x80)
+#endif
+
+/*
+ * The source and build versions of the tests directory. This is used to set
+ * the SOURCE and BUILD environment variables and find test programs, if set.
+ * Normally, this should be set as part of the build process to the test
+ * subdirectories of $(abs_top_srcdir) and $(abs_top_builddir) respectively.
+ */
+#ifndef SOURCE
+# define SOURCE NULL
+#endif
+#ifndef BUILD
+# define BUILD NULL
+#endif
+
+/* Test status codes. */
+enum test_status {
+ TEST_FAIL,
+ TEST_PASS,
+ TEST_SKIP,
+ TEST_INVALID
+};
+
+/* Indicates the state of our plan. */
+enum plan_status {
+ PLAN_INIT, /* Nothing seen yet. */
+ PLAN_FIRST, /* Plan seen before any tests. */
+ PLAN_PENDING, /* Test seen and no plan yet. */
+ PLAN_FINAL /* Plan seen after some tests. */
+};
+
+/* Error exit statuses for test processes. */
+#define CHILDERR_DUP 100 /* Couldn't redirect stderr or stdout. */
+#define CHILDERR_EXEC 101 /* Couldn't exec child process. */
+#define CHILDERR_STDERR 102 /* Couldn't open stderr file. */
+
+/* Structure to hold data for a set of tests. */
+struct testset {
+ char *file; /* The file name of the test. */
+ char *path; /* The path to the test program. */
+ enum plan_status plan; /* The status of our plan. */
+ unsigned long count; /* Expected count of tests. */
+ unsigned long current; /* The last seen test number. */
+ unsigned int length; /* The length of the last status message. */
+ unsigned long passed; /* Count of passing tests. */
+ unsigned long failed; /* Count of failing lists. */
+ unsigned long skipped; /* Count of skipped tests (passed). */
+ unsigned long allocated; /* The size of the results table. */
+ enum test_status *results; /* Table of results by test number. */
+ unsigned int aborted; /* Whether the set as aborted. */
+ int reported; /* Whether the results were reported. */
+ int status; /* The exit status of the test. */
+ unsigned int all_skipped; /* Whether all tests were skipped. */
+ char *reason; /* Why all tests were skipped. */
+
+ struct testset *next;
+};
+
+/* Structure to hold a linked list of test sets. */
+struct testlist {
+ struct testset *ts;
+ struct testlist *next;
+};
+
+/*
+ * Usage message. Should be used as a printf format with two arguments: the
+ * path to runtests, given twice.
+ */
+static const char usage_message[] = "\
+Usage: %s [-b <build-dir>] [-s <source-dir>] <test-list>\n\
+ %s -o [-b <build-dir>] [-s <source-dir>] <test>\n\
+\n\
+Options:\n\
+ -b <build-dir> Set the build directory to <build-dir>\n\
+ -o Run a single test rather than a list of tests\n\
+ -s <source-dir> Set the source directory to <source-dir>\n\
+\n\
+runtests normally runs each test listed in a file whose path is given as\n\
+its command-line argument. With the -o option, it instead runs a single\n\
+test and shows its complete output.\n";
+
+/*
+ * Header used for test output. %s is replaced by the file name of the list
+ * of tests.
+ */
+static const char banner[] = "\n\
+Running all tests listed in %s. If any tests fail, run the failing\n\
+test program with runtests -o to see more details.\n\n";
+
+/* Header for reports of failed tests. */
+static const char header[] = "\n\
+Failed Set Fail/Total (%) Skip Stat Failing Tests\n\
+-------------------------- -------------- ---- ---- ------------------------";
+
+/* Include the file name and line number in malloc failures. */
+#define xmalloc(size) x_malloc((size), __FILE__, __LINE__)
+#define xrealloc(p, size) x_realloc((p), (size), __FILE__, __LINE__)
+#define xstrdup(p) x_strdup((p), __FILE__, __LINE__)
+
+
+/*
+ * Report a fatal error, including the results of strerror, and exit.
+ */
+static void
+sysdie(const char *format, ...)
+{
+ int oerrno;
+ va_list args;
+
+ oerrno = errno;
+ fflush(stdout);
+ fprintf(stderr, "runtests: ");
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+ fprintf(stderr, ": %s\n", strerror(oerrno));
+ exit(1);
+}
+
+
+/*
+ * Allocate memory, reporting a fatal error and exiting on failure.
+ */
+static void *
+x_malloc(size_t size, const char *file, int line)
+{
+ void *p;
+
+ p = malloc(size);
+ if (p == NULL)
+ sysdie("failed to malloc %lu bytes at %s line %d",
+ (unsigned long) size, file, line);
+ return p;
+}
+
+
+/*
+ * Reallocate memory, reporting a fatal error and exiting on failure.
+ */
+static void *
+x_realloc(void *p, size_t size, const char *file, int line)
+{
+ p = realloc(p, size);
+ if (p == NULL)
+ sysdie("failed to realloc %lu bytes at %s line %d",
+ (unsigned long) size, file, line);
+ return p;
+}
+
+
+/*
+ * Copy a string, reporting a fatal error and exiting on failure.
+ */
+static char *
+x_strdup(const char *s, const char *file, int line)
+{
+ char *p;
+ size_t len;
+
+ len = strlen(s) + 1;
+ p = malloc(len);
+ if (p == NULL)
+ sysdie("failed to strdup %lu bytes at %s line %d",
+ (unsigned long) len, file, line);
+ memcpy(p, s, len);
+ return p;
+}
+
+
+/*
+ * Given a struct timeval, return the number of seconds it represents as a
+ * double. Use difftime() to convert a time_t to a double.
+ */
+static double
+tv_seconds(const struct timeval *tv)
+{
+ return difftime(tv->tv_sec, 0) + tv->tv_usec * 1e-6;
+}
+
+
+/*
+ * Given two struct timevals, return the difference in seconds.
+ */
+static double
+tv_diff(const struct timeval *tv1, const struct timeval *tv0)
+{
+ return tv_seconds(tv1) - tv_seconds(tv0);
+}
+
+
+/*
+ * Given two struct timevals, return the sum in seconds as a double.
+ */
+static double
+tv_sum(const struct timeval *tv1, const struct timeval *tv2)
+{
+ return tv_seconds(tv1) + tv_seconds(tv2);
+}
+
+
+/*
+ * Given a pointer to a string, skip any leading whitespace and return a
+ * pointer to the first non-whitespace character.
+ */
+static const char *
+skip_whitespace(const char *p)
+{
+ while (isspace((unsigned char)(*p)))
+ p++;
+ return p;
+}
+
+
+/*
+ * Start a program, connecting its stdout to a pipe on our end and its stderr
+ * to /dev/null, and storing the file descriptor to read from in the two
+ * argument. Returns the PID of the new process. Errors are fatal.
+ */
+static pid_t
+test_start(const char *path, int *fd)
+{
+ int fds[2], errfd;
+ pid_t child;
+
+ if (pipe(fds) == -1) {
+ puts("ABORTED");
+ fflush(stdout);
+ sysdie("can't create pipe");
+ }
+ child = fork();
+ if (child == (pid_t) -1) {
+ puts("ABORTED");
+ fflush(stdout);
+ sysdie("can't fork");
+ } else if (child == 0) {
+ /* In child. Set up our stdout and stderr. */
+ errfd = open("/dev/null", O_WRONLY);
+ if (errfd < 0)
+ _exit(CHILDERR_STDERR);
+ if (dup2(errfd, 2) == -1)
+ _exit(CHILDERR_DUP);
+ close(fds[0]);
+ if (dup2(fds[1], 1) == -1)
+ _exit(CHILDERR_DUP);
+
+ /* Now, exec our process. */
+ if (execl(path, path, (char *) 0) == -1)
+ _exit(CHILDERR_EXEC);
+ } else {
+ /* In parent. Close the extra file descriptor. */
+ close(fds[1]);
+ }
+ *fd = fds[0];
+ return child;
+}
+
+
+/*
+ * Back up over the output saying what test we were executing.
+ */
+static void
+test_backspace(struct testset *ts)
+{
+ unsigned int i;
+
+ if (!isatty(STDOUT_FILENO))
+ return;
+ for (i = 0; i < ts->length; i++)
+ putchar('\b');
+ for (i = 0; i < ts->length; i++)
+ putchar(' ');
+ for (i = 0; i < ts->length; i++)
+ putchar('\b');
+ ts->length = 0;
+}
+
+
+/*
+ * Read the plan line of test output, which should contain the range of test
+ * numbers. We may initialize the testset structure here if we haven't yet
+ * seen a test. Return true if initialization succeeded and the test should
+ * continue, false otherwise.
+ */
+static int
+test_plan(const char *line, struct testset *ts)
+{
+ unsigned long i;
+ long n;
+
+ /*
+ * Accept a plan without the leading 1.. for compatibility with older
+ * versions of runtests. This will only be allowed if we've not yet seen
+ * a test result.
+ */
+ line = skip_whitespace(line);
+ if (strncmp(line, "1..", 3) == 0)
+ line += 3;
+
+ /*
+ * Get the count, check it for validity, and initialize the struct. If we
+ * have something of the form "1..0 # skip foo", the whole file was
+ * skipped; record that. If we do skip the whole file, zero out all of
+ * our statistics, since they're no longer relevant. strtol is called
+ * with a second argument to advance the line pointer past the count to
+ * make it simpler to detect the # skip case.
+ */
+ n = strtol(line, (char **) &line, 10);
+ if (n == 0) {
+ line = skip_whitespace(line);
+ if (*line == '#') {
+ line = skip_whitespace(line + 1);
+ if (strncasecmp(line, "skip", 4) == 0) {
+ line = skip_whitespace(line + 4);
+ if (*line != '\0') {
+ ts->reason = xstrdup(line);
+ ts->reason[strlen(ts->reason) - 1] = '\0';
+ }
+ ts->all_skipped = 1;
+ ts->aborted = 1;
+ ts->count = 0;
+ ts->passed = 0;
+ ts->skipped = 0;
+ ts->failed = 0;
+ return 0;
+ }
+ }
+ }
+ if (n <= 0) {
+ puts("ABORTED (invalid test count)");
+ ts->aborted = 1;
+ ts->reported = 1;
+ return 0;
+ }
+ if (ts->plan == PLAN_INIT && ts->allocated == 0) {
+ ts->count = n;
+ ts->allocated = n;
+ ts->plan = PLAN_FIRST;
+ ts->results = xmalloc(ts->count * sizeof(enum test_status));
+ for (i = 0; i < ts->count; i++)
+ ts->results[i] = TEST_INVALID;
+ } else if (ts->plan == PLAN_PENDING) {
+ if ((unsigned long) n < ts->count) {
+ printf("ABORTED (invalid test number %lu)\n", ts->count);
+ ts->aborted = 1;
+ ts->reported = 1;
+ return 0;
+ }
+ ts->count = n;
+ if ((unsigned long) n > ts->allocated) {
+ ts->results = xrealloc(ts->results, n * sizeof(enum test_status));
+ for (i = ts->allocated; i < ts->count; i++)
+ ts->results[i] = TEST_INVALID;
+ ts->allocated = n;
+ }
+ ts->plan = PLAN_FINAL;
+ }
+ return 1;
+}
+
+
+/*
+ * Given a single line of output from a test, parse it and return the success
+ * status of that test. Anything printed to stdout not matching the form
+ * /^(not )?ok \d+/ is ignored. Sets ts->current to the test number that just
+ * reported status.
+ */
+static void
+test_checkline(const char *line, struct testset *ts)
+{
+ enum test_status status = TEST_PASS;
+ const char *bail;
+ char *end;
+ long number;
+ unsigned long i, current;
+ int outlen;
+
+ /* Before anything, check for a test abort. */
+ bail = strstr(line, "Bail out!");
+ if (bail != NULL) {
+ bail = skip_whitespace(bail + strlen("Bail out!"));
+ if (*bail != '\0') {
+ size_t length;
+
+ length = strlen(bail);
+ if (bail[length - 1] == '\n')
+ length--;
+ test_backspace(ts);
+ printf("ABORTED (%.*s)\n", (int) length, bail);
+ ts->reported = 1;
+ }
+ ts->aborted = 1;
+ return;
+ }
+
+ /*
+ * If the given line isn't newline-terminated, it was too big for an
+ * fgets(), which means ignore it.
+ */
+ if (line[strlen(line) - 1] != '\n')
+ return;
+
+ /* If the line begins with a hash mark, ignore it. */
+ if (line[0] == '#')
+ return;
+
+ /* If we haven't yet seen a plan, look for one. */
+ if (ts->plan == PLAN_INIT && isdigit((unsigned char)(*line))) {
+ if (!test_plan(line, ts))
+ return;
+ } else if (strncmp(line, "1..", 3) == 0) {
+ if (ts->plan == PLAN_PENDING) {
+ if (!test_plan(line, ts))
+ return;
+ } else {
+ puts("ABORTED (multiple plans)");
+ ts->aborted = 1;
+ ts->reported = 1;
+ return;
+ }
+ }
+
+ /* Parse the line, ignoring something we can't parse. */
+ if (strncmp(line, "not ", 4) == 0) {
+ status = TEST_FAIL;
+ line += 4;
+ }
+ if (strncmp(line, "ok", 2) != 0)
+ return;
+ line = skip_whitespace(line + 2);
+ errno = 0;
+ number = strtol(line, &end, 10);
+ if (errno != 0 || end == line)
+ number = ts->current + 1;
+ current = number;
+ if (number <= 0 || (current > ts->count && ts->plan == PLAN_FIRST)) {
+ test_backspace(ts);
+ printf("ABORTED (invalid test number %lu)\n", current);
+ ts->aborted = 1;
+ ts->reported = 1;
+ return;
+ }
+
+ /* We have a valid test result. Tweak the results array if needed. */
+ if (ts->plan == PLAN_INIT || ts->plan == PLAN_PENDING) {
+ ts->plan = PLAN_PENDING;
+ if (current > ts->count)
+ ts->count = current;
+ if (current > ts->allocated) {
+ unsigned long n;
+
+ n = (ts->allocated == 0) ? 32 : ts->allocated * 2;
+ if (n < current)
+ n = current;
+ ts->results = xrealloc(ts->results, n * sizeof(enum test_status));
+ for (i = ts->allocated; i < n; i++)
+ ts->results[i] = TEST_INVALID;
+ ts->allocated = n;
+ }
+ }
+
+ /*
+ * Handle directives. We should probably do something more interesting
+ * with unexpected passes of todo tests.
+ */
+ while (isdigit((unsigned char)(*line)))
+ line++;
+ line = skip_whitespace(line);
+ if (*line == '#') {
+ line = skip_whitespace(line + 1);
+ if (strncasecmp(line, "skip", 4) == 0)
+ status = TEST_SKIP;
+ if (strncasecmp(line, "todo", 4) == 0)
+ status = (status == TEST_FAIL) ? TEST_SKIP : TEST_FAIL;
+ }
+
+ /* Make sure that the test number is in range and not a duplicate. */
+ if (ts->results[current - 1] != TEST_INVALID) {
+ test_backspace(ts);
+ printf("ABORTED (duplicate test number %lu)\n", current);
+ ts->aborted = 1;
+ ts->reported = 1;
+ return;
+ }
+
+ /* Good results. Increment our various counters. */
+ switch (status) {
+ case TEST_PASS: ts->passed++; break;
+ case TEST_FAIL: ts->failed++; break;
+ case TEST_SKIP: ts->skipped++; break;
+ case TEST_INVALID: break;
+ }
+ ts->current = current;
+ ts->results[current - 1] = status;
+ test_backspace(ts);
+ if (isatty(STDOUT_FILENO)) {
+ outlen = printf("%lu/%lu", current, ts->count);
+ ts->length = (outlen >= 0) ? outlen : 0;
+ fflush(stdout);
+ }
+}
+
+
+/*
+ * Print out a range of test numbers, returning the number of characters it
+ * took up. Takes the first number, the last number, the number of characters
+ * already printed on the line, and the limit of number of characters the line
+ * can hold. Add a comma and a space before the range if chars indicates that
+ * something has already been printed on the line, and print ... instead if
+ * chars plus the space needed would go over the limit (use a limit of 0 to
+ * disable this).
+ */
+static unsigned int
+test_print_range(unsigned long first, unsigned long last, unsigned int chars,
+ unsigned int limit)
+{
+ unsigned int needed = 0;
+ unsigned long n;
+
+ for (n = first; n > 0; n /= 10)
+ needed++;
+ if (last > first) {
+ for (n = last; n > 0; n /= 10)
+ needed++;
+ needed++;
+ }
+ if (chars > 0)
+ needed += 2;
+ if (limit > 0 && chars + needed > limit) {
+ needed = 0;
+ if (chars <= limit) {
+ if (chars > 0) {
+ printf(", ");
+ needed += 2;
+ }
+ printf("...");
+ needed += 3;
+ }
+ } else {
+ if (chars > 0)
+ printf(", ");
+ if (last > first)
+ printf("%lu-", first);
+ printf("%lu", last);
+ }
+ return needed;
+}
+
+
+/*
+ * Summarize a single test set. The second argument is 0 if the set exited
+ * cleanly, a positive integer representing the exit status if it exited
+ * with a non-zero status, and a negative integer representing the signal
+ * that terminated it if it was killed by a signal.
+ */
+static void
+test_summarize(struct testset *ts, int status)
+{
+ unsigned long i;
+ unsigned long missing = 0;
+ unsigned long failed = 0;
+ unsigned long first = 0;
+ unsigned long last = 0;
+
+ if (ts->aborted) {
+ fputs("ABORTED", stdout);
+ if (ts->count > 0)
+ printf(" (passed %lu/%lu)", ts->passed, ts->count - ts->skipped);
+ } else {
+ for (i = 0; i < ts->count; i++) {
+ if (ts->results[i] == TEST_INVALID) {
+ if (missing == 0)
+ fputs("MISSED ", stdout);
+ if (first && i == last)
+ last = i + 1;
+ else {
+ if (first)
+ test_print_range(first, last, missing - 1, 0);
+ missing++;
+ first = i + 1;
+ last = i + 1;
+ }
+ }
+ }
+ if (first)
+ test_print_range(first, last, missing - 1, 0);
+ first = 0;
+ last = 0;
+ for (i = 0; i < ts->count; i++) {
+ if (ts->results[i] == TEST_FAIL) {
+ if (missing && !failed)
+ fputs("; ", stdout);
+ if (failed == 0)
+ fputs("FAILED ", stdout);
+ if (first && i == last)
+ last = i + 1;
+ else {
+ if (first)
+ test_print_range(first, last, failed - 1, 0);
+ failed++;
+ first = i + 1;
+ last = i + 1;
+ }
+ }
+ }
+ if (first)
+ test_print_range(first, last, failed - 1, 0);
+ if (!missing && !failed) {
+ fputs(!status ? "ok" : "dubious", stdout);
+ if (ts->skipped > 0) {
+ if (ts->skipped == 1)
+ printf(" (skipped %lu test)", ts->skipped);
+ else
+ printf(" (skipped %lu tests)", ts->skipped);
+ }
+ }
+ }
+ if (status > 0)
+ printf(" (exit status %d)", status);
+ else if (status < 0)
+ printf(" (killed by signal %d%s)", -status,
+ WCOREDUMP(ts->status) ? ", core dumped" : "");
+ putchar('\n');
+}
+
+
+/*
+ * Given a test set, analyze the results, classify the exit status, handle a
+ * few special error messages, and then pass it along to test_summarize() for
+ * the regular output. Returns true if the test set ran successfully and all
+ * tests passed or were skipped, false otherwise.
+ */
+static int
+test_analyze(struct testset *ts)
+{
+ if (ts->reported)
+ return 0;
+ if (ts->all_skipped) {
+ if (ts->reason == NULL)
+ puts("skipped");
+ else
+ printf("skipped (%s)\n", ts->reason);
+ return 1;
+ } else if (WIFEXITED(ts->status) && WEXITSTATUS(ts->status) != 0) {
+ switch (WEXITSTATUS(ts->status)) {
+ case CHILDERR_DUP:
+ if (!ts->reported)
+ puts("ABORTED (can't dup file descriptors)");
+ break;
+ case CHILDERR_EXEC:
+ if (!ts->reported)
+ puts("ABORTED (execution failed -- not found?)");
+ break;
+ case CHILDERR_STDERR:
+ if (!ts->reported)
+ puts("ABORTED (can't open /dev/null)");
+ break;
+ default:
+ test_summarize(ts, WEXITSTATUS(ts->status));
+ break;
+ }
+ return 0;
+ } else if (WIFSIGNALED(ts->status)) {
+ test_summarize(ts, -WTERMSIG(ts->status));
+ return 0;
+ } else if (ts->plan != PLAN_FIRST && ts->plan != PLAN_FINAL) {
+ puts("ABORTED (no valid test plan)");
+ ts->aborted = 1;
+ return 0;
+ } else {
+ test_summarize(ts, 0);
+ return (ts->failed == 0);
+ }
+}
+
+
+/*
+ * Runs a single test set, accumulating and then reporting the results.
+ * Returns true if the test set was successfully run and all tests passed,
+ * false otherwise.
+ */
+static int
+test_run(struct testset *ts)
+{
+ pid_t testpid, child;
+ int outfd, status;
+ unsigned long i;
+ FILE *output;
+ char buffer[BUFSIZ];
+
+ /* Run the test program. */
+ testpid = test_start(ts->path, &outfd);
+ output = fdopen(outfd, "r");
+ if (!output) {
+ puts("ABORTED");
+ fflush(stdout);
+ sysdie("fdopen failed");
+ }
+
+ /* Pass each line of output to test_checkline(). */
+ while (!ts->aborted && fgets(buffer, sizeof(buffer), output))
+ test_checkline(buffer, ts);
+ if (ferror(output) || ts->plan == PLAN_INIT)
+ ts->aborted = 1;
+ test_backspace(ts);
+
+ /*
+ * Consume the rest of the test output, close the output descriptor,
+ * retrieve the exit status, and pass that information to test_analyze()
+ * for eventual output.
+ */
+ while (fgets(buffer, sizeof(buffer), output))
+ ;
+ fclose(output);
+ child = waitpid(testpid, &ts->status, 0);
+ if (child == (pid_t) -1) {
+ if (!ts->reported) {
+ puts("ABORTED");
+ fflush(stdout);
+ }
+ sysdie("waitpid for %u failed", (unsigned int) testpid);
+ }
+ if (ts->all_skipped)
+ ts->aborted = 0;
+ status = test_analyze(ts);
+
+ /* Convert missing tests to failed tests. */
+ for (i = 0; i < ts->count; i++) {
+ if (ts->results[i] == TEST_INVALID) {
+ ts->failed++;
+ ts->results[i] = TEST_FAIL;
+ status = 0;
+ }
+ }
+ return status;
+}
+
+
+/* Summarize a list of test failures. */
+static void
+test_fail_summary(const struct testlist *fails)
+{
+ struct testset *ts;
+ unsigned int chars;
+ unsigned long i, first, last, total;
+
+ puts(header);
+
+ /* Failed Set Fail/Total (%) Skip Stat Failing (25)
+ -------------------------- -------------- ---- ---- -------------- */
+ for (; fails; fails = fails->next) {
+ ts = fails->ts;
+ total = ts->count - ts->skipped;
+ printf("%-26.26s %4lu/%-4lu %3.0f%% %4lu ", ts->file, ts->failed,
+ total, total ? (ts->failed * 100.0) / total : 0,
+ ts->skipped);
+ if (WIFEXITED(ts->status))
+ printf("%4d ", WEXITSTATUS(ts->status));
+ else
+ printf(" -- ");
+ if (ts->aborted) {
+ puts("aborted");
+ continue;
+ }
+ chars = 0;
+ first = 0;
+ last = 0;
+ for (i = 0; i < ts->count; i++) {
+ if (ts->results[i] == TEST_FAIL) {
+ if (first != 0 && i == last)
+ last = i + 1;
+ else {
+ if (first != 0)
+ chars += test_print_range(first, last, chars, 19);
+ first = i + 1;
+ last = i + 1;
+ }
+ }
+ }
+ if (first != 0)
+ test_print_range(first, last, chars, 19);
+ putchar('\n');
+ free(ts->file);
+ free(ts->path);
+ free(ts->results);
+ if (ts->reason != NULL)
+ free(ts->reason);
+ free(ts);
+ }
+}
+
+
+/*
+ * Given the name of a test, a pointer to the testset struct, and the source
+ * and build directories, find the test. We try first relative to the current
+ * directory, then in the build directory (if not NULL), then in the source
+ * directory. In each of those directories, we first try a "-t" extension and
+ * then a ".t" extension. When we find an executable program, we fill in the
+ * path member of the testset struct. If none of those paths are executable,
+ * just fill in the name of the test with "-t" appended.
+ *
+ * The caller is responsible for freeing the path member of the testset
+ * struct.
+ */
+static void
+find_test(const char *name, struct testset *ts, const char *source,
+ const char *build)
+{
+ char *path;
+ const char *bases[4];
+ unsigned int i;
+
+ bases[0] = ".";
+ bases[1] = build;
+ bases[2] = source;
+ bases[3] = NULL;
+
+ for (i = 0; i < 3; i++) {
+ if (bases[i] == NULL)
+ continue;
+ path = xmalloc(strlen(bases[i]) + strlen(name) + 4);
+ sprintf(path, "%s/%s-t", bases[i], name);
+ if (access(path, X_OK) != 0)
+ path[strlen(path) - 2] = '.';
+ if (access(path, X_OK) == 0)
+ break;
+ free(path);
+ path = NULL;
+ }
+ if (path == NULL) {
+ path = xmalloc(strlen(name) + 3);
+ sprintf(path, "%s-t", name);
+ }
+ ts->path = path;
+}
+
+
+/*
+ * Run a batch of tests from a given file listing each test on a line by
+ * itself. Takes two additional parameters: the root of the source directory
+ * and the root of the build directory. Test programs will be first searched
+ * for in the current directory, then the build directory, then the source
+ * directory. The file must be rewindable. Returns true iff all tests
+ * passed.
+ */
+static int
+test_batch(const char *testlist, const char *source, const char *build)
+{
+ FILE *tests;
+ unsigned int length, i;
+ unsigned int longest = 0;
+ char buffer[BUFSIZ];
+ unsigned int line;
+ struct testset *ts, *tmp, *all_tests = 0;
+ struct timeval start, end;
+ struct rusage stats;
+ struct testlist *failhead = NULL;
+ struct testlist *failtail = NULL;
+ struct testlist *next;
+ unsigned long total = 0;
+ unsigned long passed = 0;
+ unsigned long skipped = 0;
+ unsigned long failed = 0;
+ unsigned long aborted = 0;
+
+ /*
+ * Open our file of tests to run and scan it, checking for lines that
+ * are too long and searching for the longest line.
+ */
+ if (strcmp(testlist, "-") == 0) {
+ tests = stdin;
+ } else {
+ tests = fopen(testlist, "r");
+ }
+ if (!tests)
+ sysdie("can't open %s", testlist);
+ line = 0;
+ while (fgets(buffer, sizeof(buffer), tests)) {
+ line++;
+ length = strlen(buffer) - 1;
+ if (buffer[length] != '\n') {
+ fprintf(stderr, "%s:%u: line too long\n", testlist, line);
+ exit(1);
+ }
+ if (length > longest)
+ longest = length;
+
+ ts = xmalloc(sizeof(struct testset));
+ memset(ts, 0, sizeof(struct testset));
+ buffer[length] = '\0';
+
+ ts->plan = PLAN_INIT;
+ ts->file = xstrdup(buffer);
+ ts->next = NULL;
+ if (all_tests) {
+ tmp = all_tests;
+ while (tmp->next)
+ tmp = tmp->next;
+ tmp->next = ts;
+ } else {
+ all_tests = ts;
+ }
+ }
+
+ /*
+ * Add two to longest and round up to the nearest tab stop. This is how
+ * wide the column for printing the current test name will be.
+ */
+ longest += 2;
+ if (longest % 8)
+ longest += 8 - (longest % 8);
+
+ /* Start the wall clock timer. */
+ gettimeofday(&start, NULL);
+
+ /*
+ * Now, plow through our tests again, running each one. Check line
+ * length again out of paranoia.
+ */
+ line = 0;
+ for(ts = all_tests; ts && (tmp = ts->next, 1); ts = tmp) {
+ line++;
+ length = strlen(ts->file) - 1;
+ fputs(ts->file, stdout);
+ for (i = length; i < longest; i++)
+ putchar('.');
+ if (isatty(STDOUT_FILENO))
+ fflush(stdout);
+ find_test(ts->file, ts, source, build);
+ ts->reason = NULL;
+ if (test_run(ts)) {
+ free(ts->file);
+ free(ts->path);
+ free(ts->results);
+ if (ts->reason != NULL)
+ free(ts->reason);
+ free(ts);
+ } else {
+ if (!failhead) {
+ failhead = xmalloc(sizeof(struct testset));
+ failhead->ts = ts;
+ failhead->next = NULL;
+ failtail = failhead;
+ } else {
+ failtail->next = xmalloc(sizeof(struct testset));
+ failtail = failtail->next;
+ failtail->ts = ts;
+ failtail->next = NULL;
+ }
+ }
+ aborted += ts->aborted;
+ total += ts->count + ts->all_skipped;
+ passed += ts->passed;
+ skipped += ts->skipped + ts->all_skipped;
+ failed += ts->failed;
+ }
+ total -= skipped;
+ fclose(tests);
+
+ /* Stop the timer and get our child resource statistics. */
+ gettimeofday(&end, NULL);
+ getrusage(RUSAGE_CHILDREN, &stats);
+
+ /* Print out our final results. */
+ if (failhead != NULL) {
+ test_fail_summary(failhead);
+ while (failhead != NULL) {
+ next = failhead->next;
+ free(failhead);
+ failhead = next;
+ }
+ }
+ putchar('\n');
+ if (aborted != 0) {
+ if (aborted == 1)
+ printf("Aborted %lu test set", aborted);
+ else
+ printf("Aborted %lu test sets", aborted);
+ printf(", passed %lu/%lu tests", passed, total);
+ }
+ else if (failed == 0)
+ fputs("All tests successful", stdout);
+ else
+ printf("Failed %lu/%lu tests, %.2f%% okay", failed, total,
+ (total - failed) * 100.0 / total);
+ if (skipped != 0) {
+ if (skipped == 1)
+ printf(", %lu test skipped", skipped);
+ else
+ printf(", %lu tests skipped", skipped);
+ }
+ puts(".");
+ printf("Files=%u, Tests=%lu", line, total);
+ printf(", %.2f seconds", tv_diff(&end, &start));
+ printf(" (%.2f usr + %.2f sys = %.2f CPU)\n",
+ tv_seconds(&stats.ru_utime), tv_seconds(&stats.ru_stime),
+ tv_sum(&stats.ru_utime, &stats.ru_stime));
+ return (failed == 0 && aborted == 0);
+}
+
+
+/*
+ * Run a single test case. This involves just running the test program after
+ * having done the environment setup and finding the test program.
+ */
+static void
+test_single(const char *program, const char *source, const char *build)
+{
+ struct testset ts;
+
+ memset(&ts, 0, sizeof(ts));
+ find_test(program, &ts, source, build);
+ if (execl(ts.path, ts.path, (char *) 0) == -1)
+ sysdie("cannot exec %s", ts.path);
+}
+
+
+/*
+ * Main routine. Set the SOURCE and BUILD environment variables and then,
+ * given a file listing tests, run each test listed.
+ */
+int
+main(int argc, char *argv[])
+{
+ int option;
+ int status = 0;
+ int single = 0;
+ char *source_env = NULL;
+ char *build_env = NULL;
+ const char *list;
+ const char *source = SOURCE;
+ const char *build = BUILD;
+
+ while ((option = getopt(argc, argv, "b:hos:")) != EOF) {
+ switch (option) {
+ case 'b':
+ build = optarg;
+ break;
+ case 'h':
+ printf(usage_message, argv[0], argv[0]);
+ exit(0);
+ break;
+ case 'o':
+ single = 1;
+ break;
+ case 's':
+ source = optarg;
+ break;
+ default:
+ exit(1);
+ }
+ }
+ if (argc - optind != 1) {
+ fprintf(stderr, usage_message, argv[0], argv[0]);
+ exit(1);
+ }
+ argc -= optind;
+ argv += optind;
+
+ if (source != NULL) {
+ source_env = xmalloc(strlen("SOURCE=") + strlen(source) + 1);
+ sprintf(source_env, "SOURCE=%s", source);
+ if (putenv(source_env) != 0)
+ sysdie("cannot set SOURCE in the environment");
+ }
+ if (build != NULL) {
+ build_env = xmalloc(strlen("BUILD=") + strlen(build) + 1);
+ sprintf(build_env, "BUILD=%s", build);
+ if (putenv(build_env) != 0)
+ sysdie("cannot set BUILD in the environment");
+ }
+
+ if (single)
+ test_single(argv[0], source, build);
+ else {
+ list = strrchr(argv[0], '/');
+ if (list == NULL)
+ list = argv[0];
+ else
+ list++;
+ if (strcmp(list, "-") == 0) {
+ printf(banner, "stdin");
+ } else {
+ printf(banner, list);
+ }
+ status = test_batch(argv[0], source, build) ? 0 : 1;
+ }
+
+ /* For valgrind cleanliness. */
+ if (source_env != NULL) {
+ putenv((char *) "SOURCE=");
+ free(source_env);
+ }
+ if (build_env != NULL) {
+ putenv((char *) "BUILD=");
+ free(build_env);
+ }
+ exit(status);
+}
diff --git a/tests/tap/basic.c b/tests/tap/basic.c
new file mode 100644
index 0000000..e8196fc
--- /dev/null
+++ b/tests/tap/basic.c
@@ -0,0 +1,629 @@
+/*
+ * Some utility routines for writing tests.
+ *
+ * Here are a variety of utility routines for writing tests compatible with
+ * the TAP protocol. All routines of the form ok() or is*() take a test
+ * number and some number of appropriate arguments, check to be sure the
+ * results match the expected output using the arguments, and print out
+ * something appropriate for that test number. Other utility routines help in
+ * constructing more complex tests, skipping tests, reporting errors, setting
+ * up the TAP output format, or finding things in the test environment.
+ *
+ * This file is part of C TAP Harness. The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2009, 2010, 2011, 2012 Russ Allbery <***@stanford.edu>
+ * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <errno.h>
+#include <stdarg.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#ifdef _WIN32
+# include <direct.h>
+#else
+# include <sys/stat.h>
+#endif
+#include <sys/types.h>
+#include <unistd.h>
+
+#include <tests/tap/basic.h>
+
+/* Windows provides mkdir and rmdir under different names. */
+#ifdef _WIN32
+# define mkdir(p, m) _mkdir(p)
+# define rmdir(p) _rmdir(p)
+#endif
+
+/*
+ * The test count. Always contains the number that will be used for the next
+ * test status.
+ */
+unsigned long testnum = 1;
+
+/*
+ * Status information stored so that we can give a test summary at the end of
+ * the test case. We store the planned final test and the count of failures.
+ * We can get the highest test count from testnum.
+ *
+ * We also store the PID of the process that called plan() and only summarize
+ * results when that process exits, so as to not misreport results in forked
+ * processes.
+ *
+ * If _lazy is true, we're doing lazy planning and will print out the plan
+ * based on the last test number at the end of testing.
+ */
+static unsigned long _planned = 0;
+static unsigned long _failed = 0;
+static pid_t _process = 0;
+static int _lazy = 0;
+
+
+/*
+ * Our exit handler. Called on completion of the test to report a summary of
+ * results provided we're still in the original process. This also handles
+ * printing out the plan if we used plan_lazy(), although that's suppressed if
+ * we never ran a test (due to an early bail, for example).
+ */
+static void
+finish(void)
+{
+ unsigned long highest = testnum - 1;
+
+ if (_planned == 0 && !_lazy)
+ return;
+ fflush(stderr);
+ if (_process != 0 && getpid() == _process) {
+ if (_lazy && highest > 0) {
+ printf("1..%lu\n", highest);
+ _planned = highest;
+ }
+ if (_planned > highest)
+ printf("# Looks like you planned %lu test%s but only ran %lu\n",
+ _planned, (_planned > 1 ? "s" : ""), highest);
+ else if (_planned < highest)
+ printf("# Looks like you planned %lu test%s but ran %lu extra\n",
+ _planned, (_planned > 1 ? "s" : ""), highest - _planned);
+ else if (_failed > 0)
+ printf("# Looks like you failed %lu test%s of %lu\n", _failed,
+ (_failed > 1 ? "s" : ""), _planned);
+ else if (_planned > 1)
+ printf("# All %lu tests successful or skipped\n", _planned);
+ else
+ printf("# %lu test successful or skipped\n", _planned);
+ }
+}
+
+
+/*
+ * Initialize things. Turns on line buffering on stdout and then prints out
+ * the number of tests in the test suite.
+ */
+void
+plan(unsigned long count)
+{
+ if (setvbuf(stdout, NULL, _IOLBF, BUFSIZ) != 0)
+ fprintf(stderr, "# cannot set stdout to line buffered: %s\n",
+ strerror(errno));
+ fflush(stderr);
+ printf("1..%lu\n", count);
+ testnum = 1;
+ _planned = count;
+ _process = getpid();
+ atexit(finish);
+}
+
+
+/*
+ * Initialize things for lazy planning, where we'll automatically print out a
+ * plan at the end of the program. Turns on line buffering on stdout as well.
+ */
+void
+plan_lazy(void)
+{
+ if (setvbuf(stdout, NULL, _IOLBF, BUFSIZ) != 0)
+ fprintf(stderr, "# cannot set stdout to line buffered: %s\n",
+ strerror(errno));
+ testnum = 1;
+ _process = getpid();
+ _lazy = 1;
+ atexit(finish);
+}
+
+
+/*
+ * Skip the entire test suite and exits. Should be called instead of plan(),
+ * not after it, since it prints out a special plan line.
+ */
+void
+skip_all(const char *format, ...)
+{
+ fflush(stderr);
+ printf("1..0 # skip");
+ if (format != NULL) {
+ va_list args;
+
+ putchar(' ');
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+ exit(0);
+}
+
+
+/*
+ * Print the test description.
+ */
+static void
+print_desc(const char *format, va_list args)
+{
+ printf(" - ");
+ vprintf(format, args);
+}
+
+
+/*
+ * Takes a boolean success value and assumes the test passes if that value
+ * is true and fails if that value is false.
+ */
+void
+ok(int success, const char *format, ...)
+{
+ fflush(stderr);
+ printf("%sok %lu", success ? "" : "not ", testnum++);
+ if (!success)
+ _failed++;
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Same as ok(), but takes the format arguments as a va_list.
+ */
+void
+okv(int success, const char *format, va_list args)
+{
+ fflush(stderr);
+ printf("%sok %lu", success ? "" : "not ", testnum++);
+ if (!success)
+ _failed++;
+ if (format != NULL)
+ print_desc(format, args);
+ putchar('\n');
+}
+
+
+/*
+ * Skip a test.
+ */
+void
+skip(const char *reason, ...)
+{
+ fflush(stderr);
+ printf("ok %lu # skip", testnum++);
+ if (reason != NULL) {
+ va_list args;
+
+ va_start(args, reason);
+ putchar(' ');
+ vprintf(reason, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Report the same status on the next count tests.
+ */
+void
+ok_block(unsigned long count, int status, const char *format, ...)
+{
+ unsigned long i;
+
+ fflush(stderr);
+ for (i = 0; i < count; i++) {
+ printf("%sok %lu", status ? "" : "not ", testnum++);
+ if (!status)
+ _failed++;
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+ }
+}
+
+
+/*
+ * Skip the next count tests.
+ */
+void
+skip_block(unsigned long count, const char *reason, ...)
+{
+ unsigned long i;
+
+ fflush(stderr);
+ for (i = 0; i < count; i++) {
+ printf("ok %lu # skip", testnum++);
+ if (reason != NULL) {
+ va_list args;
+
+ va_start(args, reason);
+ putchar(' ');
+ vprintf(reason, args);
+ va_end(args);
+ }
+ putchar('\n');
+ }
+}
+
+
+/*
+ * Takes an expected integer and a seen integer and assumes the test passes
+ * if those two numbers match.
+ */
+void
+is_int(long wanted, long seen, const char *format, ...)
+{
+ fflush(stderr);
+ if (wanted == seen)
+ printf("ok %lu", testnum++);
+ else {
+ printf("# wanted: %ld\n# seen: %ld\n", wanted, seen);
+ printf("not ok %lu", testnum++);
+ _failed++;
+ }
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Takes a string and what the string should be, and assumes the test passes
+ * if those strings match (using strcmp).
+ */
+void
+is_string(const char *wanted, const char *seen, const char *format, ...)
+{
+ if (wanted == NULL)
+ wanted = "(null)";
+ if (seen == NULL)
+ seen = "(null)";
+ fflush(stderr);
+ if (strcmp(wanted, seen) == 0)
+ printf("ok %lu", testnum++);
+ else {
+ printf("# wanted: %s\n# seen: %s\n", wanted, seen);
+ printf("not ok %lu", testnum++);
+ _failed++;
+ }
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Takes an expected unsigned long and a seen unsigned long and assumes the
+ * test passes if the two numbers match. Otherwise, reports them in hex.
+ */
+void
+is_hex(unsigned long wanted, unsigned long seen, const char *format, ...)
+{
+ fflush(stderr);
+ if (wanted == seen)
+ printf("ok %lu", testnum++);
+ else {
+ printf("# wanted: %lx\n# seen: %lx\n", (unsigned long) wanted,
+ (unsigned long) seen);
+ printf("not ok %lu", testnum++);
+ _failed++;
+ }
+ if (format != NULL) {
+ va_list args;
+
+ va_start(args, format);
+ print_desc(format, args);
+ va_end(args);
+ }
+ putchar('\n');
+}
+
+
+/*
+ * Bail out with an error.
+ */
+void
+bail(const char *format, ...)
+{
+ va_list args;
+
+ fflush(stderr);
+ fflush(stdout);
+ printf("Bail out! ");
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf("\n");
+ exit(1);
+}
+
+
+/*
+ * Bail out with an error, appending strerror(errno).
+ */
+void
+sysbail(const char *format, ...)
+{
+ va_list args;
+ int oerrno = errno;
+
+ fflush(stderr);
+ fflush(stdout);
+ printf("Bail out! ");
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf(": %s\n", strerror(oerrno));
+ exit(1);
+}
+
+
+/*
+ * Report a diagnostic to stderr.
+ */
+void
+diag(const char *format, ...)
+{
+ va_list args;
+
+ fflush(stderr);
+ fflush(stdout);
+ printf("# ");
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf("\n");
+}
+
+
+/*
+ * Report a diagnostic to stderr, appending strerror(errno).
+ */
+void
+sysdiag(const char *format, ...)
+{
+ va_list args;
+ int oerrno = errno;
+
+ fflush(stderr);
+ fflush(stdout);
+ printf("# ");
+ va_start(args, format);
+ vprintf(format, args);
+ va_end(args);
+ printf(": %s\n", strerror(oerrno));
+}
+
+
+/*
+ * Allocate cleared memory, reporting a fatal error with bail on failure.
+ */
+void *
+bcalloc(size_t n, size_t size)
+{
+ void *p;
+
+ p = calloc(n, size);
+ if (p == NULL)
+ sysbail("failed to calloc %lu", (unsigned long)(n * size));
+ return p;
+}
+
+
+/*
+ * Allocate memory, reporting a fatal error with bail on failure.
+ */
+void *
+bmalloc(size_t size)
+{
+ void *p;
+
+ p = malloc(size);
+ if (p == NULL)
+ sysbail("failed to malloc %lu", (unsigned long) size);
+ return p;
+}
+
+
+/*
+ * Reallocate memory, reporting a fatal error with bail on failure.
+ */
+void *
+brealloc(void *p, size_t size)
+{
+ p = realloc(p, size);
+ if (p == NULL)
+ sysbail("failed to realloc %lu bytes", (unsigned long) size);
+ return p;
+}
+
+
+/*
+ * Copy a string, reporting a fatal error with bail on failure.
+ */
+char *
+bstrdup(const char *s)
+{
+ char *p;
+ size_t len;
+
+ len = strlen(s) + 1;
+ p = malloc(len);
+ if (p == NULL)
+ sysbail("failed to strdup %lu bytes", (unsigned long) len);
+ memcpy(p, s, len);
+ return p;
+}
+
+
+/*
+ * Copy up to n characters of a string, reporting a fatal error with bail on
+ * failure. Don't use the system strndup function, since it may not exist and
+ * the TAP library doesn't assume any portability support.
+ */
+char *
+bstrndup(const char *s, size_t n)
+{
+ const char *p;
+ char *copy;
+ size_t length;
+
+ /* Don't assume that the source string is nul-terminated. */
+ for (p = s; (size_t) (p - s) < n && *p != '\0'; p++)
+ ;
+ length = p - s;
+ copy = malloc(length + 1);
+ if (p == NULL)
+ sysbail("failed to strndup %lu bytes", (unsigned long) length);
+ memcpy(copy, s, length);
+ copy[length] = '\0';
+ return copy;
+}
+
+
+/*
+ * Locate a test file. Given the partial path to a file, look under BUILD and
+ * then SOURCE for the file and return the full path to the file. Returns
+ * NULL if the file doesn't exist. A non-NULL return should be freed with
+ * test_file_path_free().
+ *
+ * This function uses sprintf because it attempts to be independent of all
+ * other portability layers. The use immediately after a memory allocation
+ * should be safe without using snprintf or strlcpy/strlcat.
+ */
+char *
+test_file_path(const char *file)
+{
+ char *base;
+ char *path = NULL;
+ size_t length;
+ const char *envs[] = { "BUILD", "SOURCE", NULL };
+ int i;
+
+ for (i = 0; envs[i] != NULL; i++) {
+ base = getenv(envs[i]);
+ if (base == NULL)
+ continue;
+ length = strlen(base) + 1 + strlen(file) + 1;
+ path = bmalloc(length);
+ sprintf(path, "%s/%s", base, file);
+ if (access(path, R_OK) == 0)
+ break;
+ free(path);
+ path = NULL;
+ }
+ return path;
+}
+
+
+/*
+ * Free a path returned from test_file_path(). This function exists primarily
+ * for Windows, where memory must be freed from the same library domain that
+ * it was allocated from.
+ */
+void
+test_file_path_free(char *path)
+{
+ if (path != NULL)
+ free(path);
+}
+
+
+/*
+ * Create a temporary directory, tmp, under BUILD if set and the current
+ * directory if it does not. Returns the path to the temporary directory in
+ * newly allocated memory, and calls bail on any failure. The return value
+ * should be freed with test_tmpdir_free.
+ *
+ * This function uses sprintf because it attempts to be independent of all
+ * other portability layers. The use immediately after a memory allocation
+ * should be safe without using snprintf or strlcpy/strlcat.
+ */
+char *
+test_tmpdir(void)
+{
+ const char *build;
+ char *path = NULL;
+ size_t length;
+
+ build = getenv("BUILD");
+ if (build == NULL)
+ build = ".";
+ length = strlen(build) + strlen("/tmp") + 1;
+ path = bmalloc(length);
+ sprintf(path, "%s/tmp", build);
+ if (access(path, X_OK) < 0)
+ if (mkdir(path, 0777) < 0)
+ sysbail("error creating temporary directory %s", path);
+ return path;
+}
+
+
+/*
+ * Free a path returned from test_tmpdir() and attempt to remove the
+ * directory. If we can't delete the directory, don't worry; something else
+ * that hasn't yet cleaned up may still be using it.
+ */
+void
+test_tmpdir_free(char *path)
+{
+ rmdir(path);
+ if (path != NULL)
+ free(path);
+}
diff --git a/tests/tap/basic.h b/tests/tap/basic.h
new file mode 100644
index 0000000..fa4adaf
--- /dev/null
+++ b/tests/tap/basic.h
@@ -0,0 +1,134 @@
+/*
+ * Basic utility routines for the TAP protocol.
+ *
+ * This file is part of C TAP Harness. The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2009, 2010, 2011, 2012 Russ Allbery <***@stanford.edu>
+ * Copyright 2001, 2002, 2004, 2005, 2006, 2007, 2008, 2011, 2012
+ * The Board of Trustees of the Leland Stanford Junior University
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef TAP_BASIC_H
+#define TAP_BASIC_H 1
+
+#include <tests/tap/macros.h>
+#include <stdarg.h> /* va_list */
+#include <sys/types.h> /* size_t */
+
+/*
+ * Used for iterating through arrays. ARRAY_SIZE returns the number of
+ * elements in the array (useful for a < upper bound in a for loop) and
+ * ARRAY_END returns a pointer to the element past the end (ISO C99 makes it
+ * legal to refer to such a pointer as long as it's never dereferenced).
+ */
+#define ARRAY_SIZE(array) (sizeof(array) / sizeof((array)[0]))
+#define ARRAY_END(array) (&(array)[ARRAY_SIZE(array)])
+
+BEGIN_DECLS
+
+/*
+ * The test count. Always contains the number that will be used for the next
+ * test status.
+ */
+extern unsigned long testnum;
+
+/* Print out the number of tests and set standard output to line buffered. */
+void plan(unsigned long count);
+
+/*
+ * Prepare for lazy planning, in which the plan will be printed automatically
+ * at the end of the test program.
+ */
+void plan_lazy(void);
+
+/* Skip the entire test suite. Call instead of plan. */
+void skip_all(const char *format, ...)
+ __attribute__((__noreturn__, __format__(printf, 1, 2)));
+
+/*
+ * Basic reporting functions. The okv() function is the same as ok() but
+ * takes the test description as a va_list to make it easier to reuse the
+ * reporting infrastructure when writing new tests.
+ */
+void ok(int success, const char *format, ...)
+ __attribute__((__format__(printf, 2, 3)));
+void okv(int success, const char *format, va_list args);
+void skip(const char *reason, ...)
+ __attribute__((__format__(printf, 1, 2)));
+
+/* Report the same status on, or skip, the next count tests. */
+void ok_block(unsigned long count, int success, const char *format, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void skip_block(unsigned long count, const char *reason, ...)
+ __attribute__((__format__(printf, 2, 3)));
+
+/* Check an expected value against a seen value. */
+void is_int(long wanted, long seen, const char *format, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void is_string(const char *wanted, const char *seen, const char *format, ...)
+ __attribute__((__format__(printf, 3, 4)));
+void is_hex(unsigned long wanted, unsigned long seen, const char *format, ...)
+ __attribute__((__format__(printf, 3, 4)));
+
+/* Bail out with an error. sysbail appends strerror(errno). */
+void bail(const char *format, ...)
+ __attribute__((__noreturn__, __nonnull__, __format__(printf, 1, 2)));
+void sysbail(const char *format, ...)
+ __attribute__((__noreturn__, __nonnull__, __format__(printf, 1, 2)));
+
+/* Report a diagnostic to stderr prefixed with #. */
+void diag(const char *format, ...)
+ __attribute__((__nonnull__, __format__(printf, 1, 2)));
+void sysdiag(const char *format, ...)
+ __attribute__((__nonnull__, __format__(printf, 1, 2)));
+
+/* Allocate memory, reporting a fatal error with bail on failure. */
+void *bcalloc(size_t, size_t)
+ __attribute__((__alloc_size__(1, 2), __malloc__));
+void *bmalloc(size_t)
+ __attribute__((__alloc_size__(1), __malloc__));
+void *brealloc(void *, size_t)
+ __attribute__((__alloc_size__(2), __malloc__));
+char *bstrdup(const char *)
+ __attribute__((__malloc__, __nonnull__));
+char *bstrndup(const char *, size_t)
+ __attribute__((__malloc__, __nonnull__));
+
+/*
+ * Find a test file under BUILD or SOURCE, returning the full path. The
+ * returned path should be freed with test_file_path_free().
+ */
+char *test_file_path(const char *file)
+ __attribute__((__malloc__, __nonnull__));
+void test_file_path_free(char *path);
+
+/*
+ * Create a temporary directory relative to BUILD and return the path. The
+ * returned path should be freed with test_tmpdir_free.
+ */
+char *test_tmpdir(void)
+ __attribute__((__malloc__));
+void test_tmpdir_free(char *path);
+
+END_DECLS
+
+#endif /* TAP_BASIC_H */
diff --git a/tests/tap/float.c b/tests/tap/float.c
new file mode 100644
index 0000000..67dd555
--- /dev/null
+++ b/tests/tap/float.c
@@ -0,0 +1,67 @@
+/*
+ * Utility routines for writing floating point tests.
+ *
+ * Currently provides only one function, which checks whether a double is
+ * equal to an expected value within a given epsilon. This is broken into a
+ * separate source file from the rest of the basic C TAP library because it
+ * may require linking with -lm on some platforms, and the package may not
+ * otherwise care about floating point.
+ *
+ * This file is part of C TAP Harness. The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2008, 2010, 2012 Russ Allbery <***@stanford.edu>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+/* Required for isnan() and isinf(). */
+#if defined(__STRICT_ANSI__) || defined(PEDANTIC)
+# ifndef _XOPEN_SOURCE
+# define _XOPEN_SOURCE 600
+# endif
+#endif
+
+#include <math.h>
+#include <stdarg.h>
+#include <stdio.h>
+
+#include <tests/tap/basic.h>
+#include <tests/tap/float.h>
+
+/*
+ * Takes an expected double and a seen double and assumes the test passes if
+ * those two numbers are within delta of each other.
+ */
+void
+is_double(double wanted, double seen, double epsilon, const char *format, ...)
+{
+ va_list args;
+
+ va_start(args, format);
+ fflush(stderr);
+ if ((isnan(wanted) && isnan(seen))
+ || (isinf(wanted) && isinf(seen) && wanted == seen)
+ || fabs(wanted - seen) <= epsilon)
+ okv(1, format, args);
+ else {
+ printf("# wanted: %g\n# seen: %g\n", wanted, seen);
+ okv(0, format, args);
+ }
+}
diff --git a/tests/tap/float.h b/tests/tap/float.h
new file mode 100644
index 0000000..7464535
--- /dev/null
+++ b/tests/tap/float.h
@@ -0,0 +1,42 @@
+/*
+ * Floating point check function for the TAP protocol.
+ *
+ * This file is part of C TAP Harness. The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2008, 2010, 2012 Russ Allbery <***@stanford.edu>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef TAP_FLOAT_H
+#define TAP_FLOAT_H 1
+
+#include <tests/tap/macros.h>
+
+BEGIN_DECLS
+
+/* Check an expected value against a seen value within epsilon. */
+void is_double(double wanted, double seen, double epsilon,
+ const char *format, ...)
+ __attribute__((__format__(printf, 4, 5)));
+
+END_DECLS
+
+#endif /* TAP_FLOAT_H */
diff --git a/tests/tap/libtap.sh b/tests/tap/libtap.sh
new file mode 100644
index 0000000..f9347d8
--- /dev/null
+++ b/tests/tap/libtap.sh
@@ -0,0 +1,246 @@
+# Shell function library for test cases.
+#
+# Note that while many of the functions in this library could benefit from
+# using "local" to avoid possibly hammering global variables, Solaris /bin/sh
+# doesn't support local and this library aspires to be portable to Solaris
+# Bourne shell. Instead, all private variables are prefixed with "tap_".
+#
+# This file provides a TAP-compatible shell function library useful for
+# writing test cases. It is part of C TAP Harness, which can be found at
+# <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+#
+# Written by Russ Allbery <***@stanford.edu>
+# Copyright 2009, 2010, 2011, 2012 Russ Allbery <***@stanford.edu>
+# Copyright 2006, 2007, 2008
+# The Board of Trustees of the Leland Stanford Junior University
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to
+# deal in the Software without restriction, including without limitation the
+# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+# sell copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in
+# all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+# IN THE SOFTWARE.
+
+# Print out the number of test cases we expect to run.
+plan () {
+ count=1
+ planned="$1"
+ failed=0
+ echo "1..$1"
+ trap finish 0
+}
+
+# Prepare for lazy planning.
+plan_lazy () {
+ count=1
+ planned=0
+ failed=0
+ trap finish 0
+}
+
+# Report the test status on exit.
+finish () {
+ tap_highest=`expr "$count" - 1`
+ if [ "$planned" = 0 ] ; then
+ echo "1..$tap_highest"
+ planned="$tap_highest"
+ fi
+ tap_looks='# Looks like you'
+ if [ "$planned" -gt 0 ] ; then
+ if [ "$planned" -gt "$tap_highest" ] ; then
+ if [ "$planned" -gt 1 ] ; then
+ echo "$tap_looks planned $planned tests but only ran" \
+ "$tap_highest"
+ else
+ echo "$tap_looks planned $planned test but only ran" \
+ "$tap_highest"
+ fi
+ elif [ "$planned" -lt "$tap_highest" ] ; then
+ tap_extra=`expr "$tap_highest" - "$planned"`
+ if [ "$planned" -gt 1 ] ; then
+ echo "$tap_looks planned $planned tests but ran" \
+ "$tap_extra extra"
+ else
+ echo "$tap_looks planned $planned test but ran" \
+ "$tap_extra extra"
+ fi
+ elif [ "$failed" -gt 0 ] ; then
+ if [ "$failed" -gt 1 ] ; then
+ echo "$tap_looks failed $failed tests of $planned"
+ else
+ echo "$tap_looks failed $failed test of $planned"
+ fi
+ elif [ "$planned" -gt 1 ] ; then
+ echo "# All $planned tests successful or skipped"
+ else
+ echo "# $planned test successful or skipped"
+ fi
+ fi
+}
+
+# Skip the entire test suite. Should be run instead of plan.
+skip_all () {
+ tap_desc="$1"
+ if [ -n "$tap_desc" ] ; then
+ echo "1..0 # skip $tap_desc"
+ else
+ echo "1..0 # skip"
+ fi
+ exit 0
+}
+
+# ok takes a test description and a command to run and prints success if that
+# command is successful, false otherwise. The count starts at 1 and is
+# updated each time ok is printed.
+ok () {
+ tap_desc="$1"
+ if [ -n "$tap_desc" ] ; then
+ tap_desc=" - $tap_desc"
+ fi
+ shift
+ if "$@" ; then
+ echo ok "$count$tap_desc"
+ else
+ echo not ok "$count$tap_desc"
+ failed=`expr $failed + 1`
+ fi
+ count=`expr $count + 1`
+}
+
+# Skip the next test. Takes the reason why the test is skipped.
+skip () {
+ echo "ok $count # skip $*"
+ count=`expr $count + 1`
+}
+
+# Report the same status on a whole set of tests. Takes the count of tests,
+# the description, and then the command to run to determine the status.
+ok_block () {
+ tap_i=$count
+ tap_end=`expr $count + $1`
+ shift
+ while [ "$tap_i" -lt "$tap_end" ] ; do
+ ok "$@"
+ tap_i=`expr $tap_i + 1`
+ done
+}
+
+# Skip a whole set of tests. Takes the count and then the reason for skipping
+# the test.
+skip_block () {
+ tap_i=$count
+ tap_end=`expr $count + $1`
+ shift
+ while [ "$tap_i" -lt "$tap_end" ] ; do
+ skip "$@"
+ tap_i=`expr $tap_i + 1`
+ done
+}
+
+# Portable variant of printf '%s\n' "$*". In the majority of cases, this
+# function is slower than printf, because the latter is often implemented
+# as a builtin command. The value of the variable IFS is ignored.
+#
+# This macro must not be called via backticks inside double quotes, since this
+# will result in bizarre escaping behavior and lots of extra backslashes on
+# Solaris.
+puts () {
+ cat << EOH
+$@
+EOH
+}
+
+# Run a program expected to succeed, and print ok if it does and produces the
+# correct output. Takes the description, expected exit status, the expected
+# output, the command to run, and then any arguments for that command.
+# Standard output and standard error are combined when analyzing the output of
+# the command.
+#
+# If the command may contain system-specific error messages in its output,
+# add strip_colon_error before the command to post-process its output.
+ok_program () {
+ tap_desc="$1"
+ shift
+ tap_w_status="$1"
+ shift
+ tap_w_output="$1"
+ shift
+ tap_output=`"$@" 2>&1`
+ tap_status=$?
+ if [ $tap_status = $tap_w_status ] \
+ && [ x"$tap_output" = x"$tap_w_output" ] ; then
+ ok "$tap_desc" true
+ else
+ echo "# saw: ($tap_status) $tap_output"
+ echo "# not: ($tap_w_status) $tap_w_output"
+ ok "$tap_desc" false
+ fi
+}
+
+# Strip a colon and everything after it off the output of a command, as long
+# as that colon comes after at least one whitespace character. (This is done
+# to avoid stripping the name of the program from the start of an error
+# message.) This is used to remove system-specific error messages (coming
+# from strerror, for example).
+strip_colon_error() {
+ tap_output=`"$@" 2>&1`
+ tap_status=$?
+ tap_output=`puts "$tap_output" | sed 's/^\([^ ]* [^:]*\):.*/\1/'`
+ puts "$tap_output"
+ return $tap_status
+}
+
+# Bail out with an error message.
+bail () {
+ echo 'Bail out!' "$@"
+ exit 1
+}
+
+# Output a diagnostic on standard error, preceded by the required # mark.
+diag () {
+ echo '#' "$@"
+}
+
+# Search for the given file first in $BUILD and then in $SOURCE and echo the
+# path where the file was found, or the empty string if the file wasn't
+# found.
+#
+# This macro uses puts, so don't run it using backticks inside double quotes
+# or bizarre quoting behavior will happen with Solaris sh.
+test_file_path () {
+ if [ -n "$BUILD" ] && [ -f "$BUILD/$1" ] ; then
+ puts "$BUILD/$1"
+ elif [ -n "$SOURCE" ] && [ -f "$SOURCE/$1" ] ; then
+ puts "$SOURCE/$1"
+ else
+ echo ''
+ fi
+}
+
+# Create $BUILD/tmp for use by tests for storing temporary files and return
+# the path (via standard output).
+#
+# This macro uses puts, so don't run it using backticks inside double quotes
+# or bizarre quoting behavior will happen with Solaris sh.
+test_tmpdir () {
+ if [ -z "$BUILD" ] ; then
+ tap_tmpdir="./tmp"
+ else
+ tap_tmpdir="$BUILD"/tmp
+ fi
+ if [ ! -d "$tap_tmpdir" ] ; then
+ mkdir "$tap_tmpdir" || bail "Error creating $tap_tmpdir"
+ fi
+ puts "$tap_tmpdir"
+}
diff --git a/tests/tap/macros.h b/tests/tap/macros.h
new file mode 100644
index 0000000..33fee42
--- /dev/null
+++ b/tests/tap/macros.h
@@ -0,0 +1,88 @@
+/*
+ * Helpful macros for TAP header files.
+ *
+ * This is not, strictly speaking, related to TAP, but any TAP add-on is
+ * probably going to need these macros, so define them in one place so that
+ * everyone can pull them in.
+ *
+ * This file is part of C TAP Harness. The current version plus supporting
+ * documentation is at <http://www.eyrie.org/~eagle/software/c-tap-harness/>.
+ *
+ * Copyright 2008, 2012 Russ Allbery <***@stanford.edu>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef TAP_MACROS_H
+#define TAP_MACROS_H 1
+
+/*
+ * __attribute__ is available in gcc 2.5 and later, but only with gcc 2.7
+ * could you use the __format__ form of the attributes, which is what we use
+ * (to avoid confusion with other macros), and only with gcc 2.96 can you use
+ * the attribute __malloc__. 2.96 is very old, so don't bother trying to get
+ * the other attributes to work with GCC versions between 2.7 and 2.96.
+ */
+#ifndef __attribute__
+# if __GNUC__ < 2 || (__GNUC__ == 2 && __GNUC_MINOR__ < 96)
+# define __attribute__(spec) /* empty */
+# endif
+#endif
+
+/*
+ * We use __alloc_size__, but it was only available in fairly recent versions
+ * of GCC. Suppress warnings about the unknown attribute if GCC is too old.
+ * We know that we're GCC at this point, so we can use the GCC variadic macro
+ * extension, which will still work with versions of GCC too old to have C99
+ * variadic macro support.
+ */
+#if !defined(__attribute__) && !defined(__alloc_size__)
+# if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 3)
+# define __alloc_size__(spec, args...) /* empty */
+# endif
+#endif
+
+/*
+ * LLVM and Clang pretend to be GCC but don't support all of the __attribute__
+ * settings that GCC does. For them, suppress warnings about unknown
+ * attributes on declarations. This unfortunately will affect the entire
+ * compilation context, but there's no push and pop available.
+ */
+#if !defined(__attribute__) && (defined(__llvm__) || defined(__clang__))
+# pragma GCC diagnostic ignored "-Wattributes"
+#endif
+
+/* Used for unused parameters to silence gcc warnings. */
+#define UNUSED __attribute__((__unused__))
+
+/*
+ * BEGIN_DECLS is used at the beginning of declarations so that C++
+ * compilers don't mangle their names. END_DECLS is used at the end.
+ */
+#undef BEGIN_DECLS
+#undef END_DECLS
+#ifdef __cplusplus
+# define BEGIN_DECLS extern "C" {
+# define END_DECLS }
+#else
+# define BEGIN_DECLS /* empty */
+# define END_DECLS /* empty */
+#endif
+
+#endif /* TAP_MACROS_H */
diff --git a/tests/testutil.c b/tests/testutil.c
new file mode 100644
index 0000000..3bb3aa1
--- /dev/null
+++ b/tests/testutil.c
@@ -0,0 +1,115 @@
+/**
+ * @file testutil.c
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#include "testutil.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+
+/* Taken from bytes.com/topic/c/answers/763239-printing-hex-dump-buffer */
+void dump_buffer(FILE *stream, unsigned int n, const void *b) {
+ const unsigned char* buf = b;
+ int on_this_line = 0;
+ while (n-- > 0) {
+ fprintf(stream, "%02X ", *buf++);
+ on_this_line += 1;
+ if (on_this_line == 16 || n == 0) {
+ int i;
+ fputs(" ", stream);
+ for (i = on_this_line; i < 16; i++)
+ fputs(" ", stream);
+ for (i = on_this_line; i > 0; i--)
+ fputc(isprint(buf[-i]) ? buf[-i] : '.', stream);
+ fputs("\n", stream);
+ on_this_line = 0;
+ }
+ }
+}
+
+int cmp_buffers(FILE *stream, unsigned int n, const void *b1, const void *b2) {
+ int result = 0;
+ int on_this_line = 0;
+
+ const unsigned char *buf1 = b1;
+ const unsigned char *buf2 = b2;
+ while (n-- > 0) {
+ if (*buf1 == *buf2) {
+ fprintf(stream, "%02X ", *buf1);
+ } else {
+ result = 1;
+ fprintf(stream, "%02X!%02X ", *buf1, *buf2);
+ }
+ buf1++;
+ buf2++;
+ on_this_line += 1;
+ if (on_this_line == 16 || n == 0) {
+ int i;
+ fputs(" ", stream);
+ for (i = on_this_line; i < 16; i++)
+ fputs(" ", stream);
+ for (i = on_this_line; i > 0; i--)
+ fputc(isprint(buf1[-i]) ? buf1[-i] : '.', stream);
+ fputs("\n", stream);
+ on_this_line = 0;
+ }
+ }
+ return result;
+}
+
+void ptp_text_set(struct PTPText *t, const char *txt) {
+ size_t len;
+ if (txt) {
+ len = strlen(txt);
+ if (len > 255) len = 255;
+ t->length = len;
+ t->text = (Octet*)txt;
+ } else {
+ t->length = 0;
+ t->text = 0;
+ }
+}
+
+static struct childproc *the_child = 0;
+
+static void killchild() {
+ if (the_child) {
+ childproc_kill(the_child);
+ the_child = 0;
+ }
+}
+
+struct childproc *test_childproc(const char *config) {
+ if (the_child == 0) {
+ if (childproc_new(&the_child) != 0) {
+ printf("Failed childproc_new\n");
+ exit(1);
+ }
+ if (childproc_spawn(the_child, config) != 0) {
+ printf("Failed childproc_spawn\n");
+ exit(1);
+ }
+ atexit(killchild);
+ }
+ return the_child;
+}
+
+struct childproc *test_restart_childproc(const char *config) {
+ killchild();
+ return test_childproc(config);
+}
diff --git a/tests/testutil.h b/tests/testutil.h
new file mode 100644
index 0000000..797b602
--- /dev/null
+++ b/tests/testutil.h
@@ -0,0 +1,42 @@
+/**
+ * @file testutil.h
+ * @brief Utilities for tests
+ * @note Copyright (C) 2012 Smart Energy Instruments Inc. <***@se-instruments.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ */
+#ifndef TEST_UTIL_H
+#define TEST_UTIL_H
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "mgmt.h"
+#include "childproc.h"
+
+struct sequence_ids {
+ UInteger16 mgmt;
+};
+
+void dump_buffer(FILE *stream, unsigned int n, const void* buf);
+int cmp_buffers(FILE *stream, unsigned int n, const void* buf1, const void* buf2);
+
+void ptp_text_set(struct PTPText *t, const char *txt);
+
+struct childproc *test_childproc(const char *config);
+struct childproc *test_restart_childproc(const char *config);
+#endif
--
1.8.0.2
Richard Cochran
2012-12-29 16:23:30 UTC
Permalink
Post by Geoff Salmon
Hi
I delayed replying until I could send you some patches, which took
longer than I expected. I was unable to easily break up the second
patch into smaller patches, so I'm sending it as is for now. It's a
bit of a monster.
Yep.
Post by Geoff Salmon
By sending patches I'm not trying to ignore the
discussion from the previous thread, just to give us something more
concrete to discuss. Now that the patches are available, what do you
think of this approach for handling TLVs?
Well you sure gave us plenty to chew on ;)

I am glad to have the patches to look at, since they are, as you say,
something concrete. Of course, it will take some time to weed through
them.

Thanks,
Richard
Richard Cochran
2012-12-31 22:45:44 UTC
Permalink
Post by Geoff Salmon
The patches also add unit tests. Run "make check" to run them.
Currently the tests either link with object files directly or fork a
ptp4l process and communicate with it over the UDS. In future I think
most of ptp4l should be built as a static library that the tests can
link with. If we could mock the transports, PHC and timerfds, then I
think tests could cover a lot of the functionality and various
configurations available in PTP.
Thanks for posting your testing code. I think a testing framework
would be a useful and substantial addition to the code, and I will
take a closer look at your patches later on.

For now, let me offer a few general remarks on the testing
idea. First, I think the testing framework would be a major new
feature, and it surely deserves its own patch series. Ideally, it
should go in first, before any new features appear. In that way, you
can have some confidence that no regressions are introduced.

Secondly, while testing that the message buffers are correctly
formatted is all well and good, for me it would be far more
interesting to be able to test the BMC in a simulated network, and to
test the clock servos as well. Take a look at Miroslav Lichvar's clock
and network simulator to get an idea of what can be done.

http://mlichvar.fedorapeople.org/clknetsim

I know this might sound rather ambitious, and I haven't thought too
much about how realistic this would be, but I just wanted to put a few
ideas out there and encourage you to aim high.

Thanks,
Richard
Geoff Salmon
2013-01-01 01:04:04 UTC
Permalink
That clknetsim is really interesting. I hadn't seen it before. I was
thinking along the lines of testing multiple ptp4l processes together on
one machine, but I hadn't thought about using LD_PRELOAD to do it.

Because we can change ptp4l itself, it would probably be easier to add
our own interface for the PHC and timerfds and then let tests configure
mock versions of those plus a mock transport somehow. I imagine those
interfaces would be easier to implement than getting the right subset of
features of the underlying syscalls working. Lots of potential
approaches to think about...

I'm working on a patch of just the testing framework that will apply on
master. It doesn't actually test anything at this point, becuase all the
existing tests relied on the TLV and management changes. I'll see if I
can add some simple tests like invalid config files.

- Geoff
Post by Richard Cochran
Post by Geoff Salmon
The patches also add unit tests. Run "make check" to run them.
Currently the tests either link with object files directly or fork a
ptp4l process and communicate with it over the UDS. In future I think
most of ptp4l should be built as a static library that the tests can
link with. If we could mock the transports, PHC and timerfds, then I
think tests could cover a lot of the functionality and various
configurations available in PTP.
Thanks for posting your testing code. I think a testing framework
would be a useful and substantial addition to the code, and I will
take a closer look at your patches later on.
For now, let me offer a few general remarks on the testing
idea. First, I think the testing framework would be a major new
feature, and it surely deserves its own patch series. Ideally, it
should go in first, before any new features appear. In that way, you
can have some confidence that no regressions are introduced.
Secondly, while testing that the message buffers are correctly
formatted is all well and good, for me it would be far more
interesting to be able to test the BMC in a simulated network, and to
test the clock servos as well. Take a look at Miroslav Lichvar's clock
and network simulator to get an idea of what can be done.
http://mlichvar.fedorapeople.org/clknetsim
I know this might sound rather ambitious, and I haven't thought too
much about how realistic this would be, but I just wanted to put a few
ideas out there and encourage you to aim high.
Thanks,
Richard
Loading...