diff --git a/include/ipfs/namesys/routing.h b/include/ipfs/namesys/routing.h index 7d6fbdd..bebc872 100644 --- a/include/ipfs/namesys/routing.h +++ b/include/ipfs/namesys/routing.h @@ -10,7 +10,7 @@ struct cacheEntry { char *key; char *value; - struct stime eol; + struct timespec eol; }; struct routingResolver { @@ -29,7 +29,8 @@ // ipfs_namesys_routing_resolve_once implements resolver. Uses the IPFS // routing system to resolve SFS-like names. int ipfs_namesys_routing_resolve_once (char **path, char *name, int depth, char *prefix, struct namesys_pb *pb); - int ipfs_namesys_routing_check_EOL (struct stime *st, struct namesys_pb *pb); + int ipfs_namesys_routing_check_EOL (struct timespec *ts, struct namesys_pb *pb); + int ipfs_namesys_routing_get_value (char*, char*); int ipfs_namesys_routing_getpublic_key (char*, struct MultiHash*); #endif // IPNS_NAMESYS_ROUTING_H diff --git a/namesys/publisher.c b/namesys/publisher.c new file mode 100644 index 0000000..a3566e0 --- /dev/null +++ b/namesys/publisher.c @@ -0,0 +1,362 @@ +var PublishPutValTimeout = time.Minute + +// ipnsPublisher is capable of publishing and resolving names to the IPFS +// routing system. +type ipnsPublisher struct { + routing routing.ValueStore + ds ds.Datastore +} + +// 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} +} + +// 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)) +} + +// 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 +// given 'val' is an IpnsEntry and that that entry is valid. +int ValidateIpnsRecord (char *k, char *val) +{ + int err; + +} +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) { + return ErrAllocFailed; + } + + *ipnskey = malloc(i); + if (!*ipnskey) { + free (*namekey); + *namekey = NULL; + return ErrAllocFailed; + } + + 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; +} diff --git a/namesys/routing.c b/namesys/routing.c index 3842300..924878b 100644 --- a/namesys/routing.c +++ b/namesys/routing.c @@ -13,15 +13,15 @@ char* ipfs_routing_cache_get (char *key, struct ipns_entry *ientry) { int i; struct routingResolver *cache; - struct stime now; + struct timespec now; if (key && ientry) { cache = ientry->cache; if (cache) { - get_gmttime (&now); + timespec_get (&now); for (i = 0 ; i < cache->next ; i++) { - if (((now.t < cache->data[i]->eol.t || - (now.t == cache->data[i]->eol.t && now.ts.tv_nsec < cache->data[i]->eol.ts.tv_nsec))) && + if (((now.tv_sec < cache->data[i]->eol.tv_sec || + (now.tv_sec == cache->data[i]->eol.tv_sec && now.tv_nsec < cache->data[i]->eol.tv_nsec))) && strcmp(cache->data[i]->key, key) == 0) { return cache->data[i]->value; } @@ -43,8 +43,8 @@ void ipfs_routing_cache_set (char *key, char *value, struct ipns_entry *ientry) if (n) { n->key = key; n->value = value; - get_gmttime (&n->eol); // now - n->eol.t += DefaultResolverCacheTTL; // sum TTL seconds to time seconds. + timespec_get (&n->eol); // now + n->eol.tv_sec += DefaultResolverCacheTTL; // sum TTL seconds to time seconds. cache->data[cache->next++] = n; } } @@ -152,7 +152,7 @@ int ipfs_namesys_routing_resolve_once (char **path, char *name, int depth, char } // check sig with pk - err = libp2p_crypto_verify (ipfs_ipns_entry_data_for_sig(pb->IpnsEntry), ipfs_ipns_entry_get_signature(pb->IpnsEntry), &ok); + err = libp2p_crypto_verify (ipns_entry_data_for_sig(pb->IpnsEntry), pb->IpnsEntry->signature, &ok); if (err || !ok) { char buf[500]; snprintf(buf, sizeof(buf), Err[ErrInvalidSignatureFmt], pubkey); @@ -198,12 +198,12 @@ int ipfs_namesys_routing_resolve_once (char **path, char *name, int depth, char return 0; } -int ipfs_namesys_routing_check_EOL (struct stime *st, struct namesys_pb *pb) +int ipfs_namesys_routing_check_EOL (struct timespec *ts, struct namesys_pb *pb) { int err; - if (ipfs_namesys_pb_get_validity_type (pb->IpnsEntry) == IpnsEntry_EOL) { - err = ipfs_util_time_parse_RFC3339 (st, ipfs_namesys_pb_get_validity (pb->IpnsEntry)); + if (*(pb->IpnsEntry->validityType) == IpnsEntry_EOL) { + err = ipfs_util_time_parse_RFC3339 (ts, pb->IpnsEntry->validity); if (!err) { return 1; }