forked from agorise/c-ipfs
Initial implementation of namesys/publisher
This commit is contained in:
parent
6b9d205ef2
commit
b17403b61a
2 changed files with 119 additions and 347 deletions
9
include/ipfs/namesys/publisher.h
Normal file
9
include/ipfs/namesys/publisher.h
Normal file
|
@ -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
|
|
@ -1,362 +1,125 @@
|
|||
var PublishPutValTimeout = time.Minute
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#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;
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue