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; }