diff --git a/include/ipfs/namesys/publisher.h b/include/ipfs/namesys/publisher.h new file mode 100644 index 0000000..b68afb0 --- /dev/null +++ b/include/ipfs/namesys/publisher.h @@ -0,0 +1,9 @@ +#ifndef IPFS_PUBLISHER_H + #define IPFS_PUBLISHER_H + char* ipns_entry_data_for_sig (struct ipns_entry *entry); + int ipns_selector_func (int *idx, struct ipns_entry ***recs, char *k, char **vals); + int ipns_select_record (int *idx, struct ipns_entry **recs, char **vals); + // ipns_validate_ipns_record implements ValidatorFunc and verifies that the + // given 'val' is an IpnsEntry and that that entry is valid. + int ipns_validate_ipns_record (char *k, char *val); +#endif // IPFS_PUBLISHER_H diff --git a/namesys/publisher.c b/namesys/publisher.c index a3566e0..1710b93 100644 --- a/namesys/publisher.c +++ b/namesys/publisher.c @@ -1,362 +1,125 @@ -var PublishPutValTimeout = time.Minute +#include +#include +#include "ipfs/errs.h" +#include "ipfs/util/time.h" +#include "ipfs/namesys/pb.h" +#include "ipfs/namesys/publisher.h" -// ipnsPublisher is capable of publishing and resolving names to the IPFS -// routing system. -type ipnsPublisher struct { - routing routing.ValueStore - ds ds.Datastore +char* ipns_entry_data_for_sig (struct ipns_entry *entry) +{ + char *ret; + + if (!entry || !entry->value || !entry->validity) { + return NULL; + } + ret = calloc (1, strlen(entry->value) + strlen (entry->validity) + sizeof(IpnsEntry_ValidityType) + 1); + if (ret) { + strcpy(ret, entry->value); + strcat(ret, entry->validity); + if (entry->validityType) { + memcpy(ret+strlen(entry->value)+strlen(entry->validity), entry->validityType, sizeof(IpnsEntry_ValidityType)); + } else { + memcpy(ret+strlen(entry->value)+strlen(entry->validity), &IpnsEntry_EOL, sizeof(IpnsEntry_ValidityType)); + } + } + return ret; } -// NewRoutingPublisher constructs a publisher for the IPFS Routing name system. -func NewRoutingPublisher(route routing.ValueStore, ds ds.Datastore) *ipnsPublisher { - if ds == nil { - panic("nil datastore") - } - return &ipnsPublisher{routing: route, ds: ds} +int ipns_selector_func (int *idx, struct ipns_entry ***recs, char *k, char **vals) +{ + int err, i, c; + + if (!idx || !recs || !k || !vals) { + return ErrInvalidParam; + } + + for (c = 0 ; vals[c] ; c++); // count array + + *recs = calloc(c+1, sizeof (void*)); // allocate return array. + if (!*recs) { + return ErrAllocFailed; + } + for (i = 0 ; i < c ; i++) { + *recs[i] = calloc(1, sizeof (struct ipns_entry)); // alloc every record + if (!*recs[i]) { + return ErrAllocFailed; + } + //err = proto.Unmarshal(vals[i], *recs[i]); // and decode. + if (err) { + ipfs_namesys_ipnsentry_reset (*recs[i]); // make sure record is empty. + } + } + return ipns_select_record(idx, *recs, vals); } -// Publish implements Publisher. Accepts a keypair and a value, -// and publishes it out to the routing system -func (p *ipnsPublisher) Publish(ctx context.Context, k ci.PrivKey, value path.Path) error { - log.Debugf("Publish %s", value) - return p.PublishWithEOL(ctx, k, value, time.Now().Add(time.Hour*24)) +int ipns_select_record (int *idx, struct ipns_entry **recs, char **vals) +{ + int err, i, best_i = -1, best_seq = 0; + struct timespec rt, bestt; + + if (!idx || !recs || !vals) { + return ErrInvalidParam; + } + + for (i = 0 ; recs[i] ; i++) { + if (!(recs[i]->sequence) || *(recs[i]->sequence) < best_seq) { + continue; + } + + if (best_i == -1 || *(recs[i]->sequence) > best_seq) { + best_seq = *(recs[i]->sequence); + best_i = i; + } else if (*(recs[i]->sequence) == best_seq) { + err = ipfs_util_time_parse_RFC3339 (&rt, ipfs_namesys_pb_get_validity (recs[i])); + if (err) { + continue; + } + err = ipfs_util_time_parse_RFC3339 (&bestt, ipfs_namesys_pb_get_validity (recs[best_i])); + if (err) { + continue; + } + if (rt.tv_sec > bestt.tv_sec || (rt.tv_sec == bestt.tv_sec && rt.tv_nsec > bestt.tv_nsec)) { + best_i = i; + } else if (rt.tv_sec == bestt.tv_sec && rt.tv_nsec == bestt.tv_nsec) { + if (memcmp(vals[i], vals[best_i], strlen(vals[best_i])) > 0) { // FIXME: strlen? + best_i = i; + } + } + } + } + if (best_i == -1) { + return ErrNoRecord; + } + *idx = best_i; + return 0; } -// PublishWithEOL is a temporary stand in for the ipns records implementation -// see here for more details: https://github.com/ipfs/specs/tree/master/records -func (p *ipnsPublisher) PublishWithEOL(ctx context.Context, k ci.PrivKey, value path.Path, eol time.Time) error { - - id, err := peer.IDFromPrivateKey(k) - if err != nil { - return err - } - - _, ipnskey := IpnsKeysForID(id) - - // get previous records sequence number - seqnum, err := p.getPreviousSeqNo(ctx, ipnskey) - if err != nil { - return err - } - - // increment it - seqnum++ - - return PutRecordToRouting(ctx, k, value, seqnum, eol, p.routing, id) -} - -func (p *ipnsPublisher) getPreviousSeqNo(ctx context.Context, ipnskey string) (uint64, error) { - prevrec, err := p.ds.Get(dshelp.NewKeyFromBinary(ipnskey)) - if err != nil && err != ds.ErrNotFound { - // None found, lets start at zero! - return 0, err - } - var val []byte - if err == nil { - prbytes, ok := prevrec.([]byte) - if !ok { - return 0, fmt.Errorf("unexpected type returned from datastore: %#v", prevrec) - } - dhtrec := new(dhtpb.Record) - err := proto.Unmarshal(prbytes, dhtrec) - if err != nil { - return 0, err - } - - val = dhtrec.GetValue() - } else { - // try and check the dht for a record - ctx, cancel := context.WithTimeout(ctx, time.Second*30) - defer cancel() - - rv, err := p.routing.GetValue(ctx, ipnskey) - if err != nil { - // no such record found, start at zero! - return 0, nil - } - - val = rv - } - - e := new(pb.IpnsEntry) - err = proto.Unmarshal(val, e) - if err != nil { - return 0, err - } - - return e.GetSequence(), nil -} - -// setting the TTL on published records is an experimental feature. -// as such, i'm using the context to wire it through to avoid changing too -// much code along the way. -func checkCtxTTL(ctx context.Context) (time.Duration, bool) { - v := ctx.Value("ipns-publish-ttl") - if v == nil { - return 0, false - } - - d, ok := v.(time.Duration) - return d, ok -} - -func PutRecordToRouting(ctx context.Context, k ci.PrivKey, value path.Path, seqnum uint64, eol time.Time, r routing.ValueStore, id peer.ID) error { - ctx, cancel := context.WithCancel(ctx) - defer cancel() - - namekey, ipnskey := IpnsKeysForID(id) - entry, err := CreateRoutingEntryData(k, value, seqnum, eol) - if err != nil { - return err - } - - ttl, ok := checkCtxTTL(ctx) - if ok { - entry.Ttl = proto.Uint64(uint64(ttl.Nanoseconds())) - } - - errs := make(chan error, 2) - - go func() { - errs <- PublishEntry(ctx, r, ipnskey, entry) - }() - - go func() { - errs <- PublishPublicKey(ctx, r, namekey, k.GetPublic()) - }() - - err = waitOnErrChan(ctx, errs) - if err != nil { - return err - } - - err = waitOnErrChan(ctx, errs) - if err != nil { - return err - } - - return nil -} - -func waitOnErrChan(ctx context.Context, errs chan error) error { - select { - case err := <-errs: - return err - case <-ctx.Done(): - return ctx.Err() - } -} - -func PublishPublicKey(ctx context.Context, r routing.ValueStore, k string, pubk ci.PubKey) error { - log.Debugf("Storing pubkey at: %s", k) - pkbytes, err := pubk.Bytes() - if err != nil { - return err - } - - // Store associated public key - timectx, cancel := context.WithTimeout(ctx, PublishPutValTimeout) - defer cancel() - err = r.PutValue(timectx, k, pkbytes) - if err != nil { - return err - } - - return nil -} - -func PublishEntry(ctx context.Context, r routing.ValueStore, ipnskey string, rec *pb.IpnsEntry) error { - timectx, cancel := context.WithTimeout(ctx, PublishPutValTimeout) - defer cancel() - - data, err := proto.Marshal(rec) - if err != nil { - return err - } - - log.Debugf("Storing ipns entry at: %s", ipnskey) - // Store ipns entry at "/ipns/"+b58(h(pubkey)) - if err := r.PutValue(timectx, ipnskey, data); err != nil { - return err - } - - return nil -} - -func CreateRoutingEntryData(pk ci.PrivKey, val path.Path, seq uint64, eol time.Time) (*pb.IpnsEntry, error) { - entry := new(pb.IpnsEntry) - - entry.Value = []byte(val) - typ := pb.IpnsEntry_EOL - entry.ValidityType = &typ - entry.Sequence = proto.Uint64(seq) - entry.Validity = []byte(u.FormatRFC3339(eol)) - - sig, err := pk.Sign(ipnsEntryDataForSig(entry)) - if err != nil { - return nil, err - } - entry.Signature = sig - return entry, nil -} - -func ipnsEntryDataForSig(e *pb.IpnsEntry) []byte { - return bytes.Join([][]byte{ - e.Value, - e.Validity, - []byte(fmt.Sprint(e.GetValidityType())), - }, - []byte{}) -} - -var IpnsRecordValidator = &record.ValidChecker{ - Func: ValidateIpnsRecord, - Sign: true, -} - -func IpnsSelectorFunc(k string, vals [][]byte) (int, error) { - var recs []*pb.IpnsEntry - for _, v := range vals { - e := new(pb.IpnsEntry) - err := proto.Unmarshal(v, e) - if err == nil { - recs = append(recs, e) - } else { - recs = append(recs, nil) - } - } - - return selectRecord(recs, vals) -} - -func selectRecord(recs []*pb.IpnsEntry, vals [][]byte) (int, error) { - var best_seq uint64 - best_i := -1 - - for i, r := range recs { - if r == nil || r.GetSequence() < best_seq { - continue - } - - if best_i == -1 || r.GetSequence() > best_seq { - best_seq = r.GetSequence() - best_i = i - } else if r.GetSequence() == best_seq { - rt, err := u.ParseRFC3339(string(r.GetValidity())) - if err != nil { - continue - } - - bestt, err := u.ParseRFC3339(string(recs[best_i].GetValidity())) - if err != nil { - continue - } - - if rt.After(bestt) { - best_i = i - } else if rt == bestt { - if bytes.Compare(vals[i], vals[best_i]) > 0 { - best_i = i - } - } - } - } - if best_i == -1 { - return 0, errors.New("no usable records in given set") - } - - return best_i, nil -} - -// ValidateIpnsRecord implements ValidatorFunc and verifies that the +// ipns_validate_ipns_record implements ValidatorFunc and verifies that the // given 'val' is an IpnsEntry and that that entry is valid. -int ValidateIpnsRecord (char *k, char *val) +int ipns_validate_ipns_record (char *k, char *val) { int err; + struct ipns_entry *entry = ipfs_namesys_pb_new_ipns_entry(); + struct timespec ts, now; -} -func ValidateIpnsRecord(k string, val []byte) error { - entry := new(pb.IpnsEntry) - err := proto.Unmarshal(val, entry) - if err != nil { - return err - } - switch entry.GetValidityType() { - case pb.IpnsEntry_EOL: - t, err := u.ParseRFC3339(string(entry.GetValidity())) - if err != nil { - log.Debug("failed parsing time for ipns record EOL") - return err - } - if time.Now().After(t) { - return ErrExpiredRecord - } - default: - return ErrUnrecognizedValidity - } - return nil -} - -// InitializeKeyspace sets the ipns record for the given key to -// point to an empty directory. -// TODO: this doesnt feel like it belongs here -int InitializeKeyspace (DAGService ds, Publisher pub, Pinner pins, ciPrivKey key) -{ - int err; - Node emptyDir; - Cid nodek; - - err = ipfs_merkledag_add(ds, nodek, emptyDir); - if (err) { - return err; - } - - // pin recursively because this might already be pinned - // and doing a direct pin would throw an error in that case - err = ipfs_pins_pin(emptyDir, TRUE); - if (err) { - return err; - } - - err = ipfs_pins_flush(); - if (err) { - return err; - } - - err = ipfs_pub_publish(key, PathFromCid(nodek)); - if (err) { - return err; - } - - return 0; -} - -int IpnsKeysForID (char **namekey, char **ipnskey, char *id) -{ - char namekey_prefix[] = "/pk/"; - char ipnskey_prefix[] = "/ipns/"; - int i, n; - - n = sizeof(namekey_prefix) + strlen(id); - *namekey = malloc(n); - if (!*namekey) { + if (!entry) { return ErrAllocFailed; } - - *ipnskey = malloc(i); - if (!*ipnskey) { - free (*namekey); - *namekey = NULL; - return ErrAllocFailed; + //err = proto.Unmarshal(val, entry); + if (err) { + return err; + } + if (ipfs_namesys_pb_get_validity_type (entry) == IpnsEntry_EOL) { + err = ipfs_util_time_parse_RFC3339 (&ts, ipfs_namesys_pb_get_validity (entry)); + if (err) { + //log.Debug("failed parsing time for ipns record EOL") + return err; + } + timespec_get (&now, TIME_UTC); + if (now.tv_nsec > ts.tv_nsec || (now.tv_nsec == ts.tv_nsec && now.tv_nsec > ts.tv_nsec)) { + return ErrExpiredRecord; + } + } else { + return ErrUnrecognizedValidity; } - - namekey[--n] = '\0'; - strncpy (*namekey, namekey_prefix, n); - strncat (*namekey, id, n - strlen (namekey)); - - ipnskey[--i] = '\0'; - strncpy (*ipnskey, ipnskey_prefix, i); - strncat (*ipnskey, id, i - strlen (ipnskey)); - - return 0; }