$ git clone https://socialnetwork.ion.nu/socialnetwork.git
commit abfdadfa9d72ebfb6935415c98c201d2ae2ef156
Author: Alicia <...>
Date:   Tue Jan 17 14:34:48 2017 +0100

    Added the beginning of a social layer on top of the peer layer (somewhat messy for now)

diff --git a/Makefile b/Makefile
index a43b65e..390b28c 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,12 @@
 LIBS=$(shell pkg-config --libs gnutls)
-CFLAGS=-g3 $(shell pkg-config --cflags gnutls)
+CFLAGS=-g3 -Wall $(shell pkg-config --cflags gnutls)
+
+socialtest: socialtest.o libsocial.so
+ $(CC) $^ -Wl,-R. -o $@
+
+libsocial.so: CFLAGS+=-fPIC
+libsocial.so: social.o peer.o update.o udpstream.o
+ $(CC) -shared $^ $(LIBS) -o $@
 
 peertest: peertest.o peer.o udpstream.o
  $(CC) $^ $(LIBS) -o $@
diff --git a/buffer.h b/buffer.h
new file mode 100644
index 0000000..a563db3
--- /dev/null
+++ b/buffer.h
@@ -0,0 +1,44 @@
+/*
+    Socialnetwork, a truly peer-to-peer social network (in search of a better name)
+    Copyright (C) 2017  alicia@ion.nu
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License version 3
+    as published by the Free Software Foundation.
+
+    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 Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef BUFFER_H
+#define BUFFER_H
+#define buffer_init(bufobj) \
+  (bufobj).buf=0; \
+  (bufobj).size=0; \
+  (bufobj).memsize=0
+#define buffer_write(bufobj, data, datasize) \
+  if((bufobj).memsize-(bufobj).size<datasize) \
+  { \
+    (bufobj).memsize=(bufobj).size+datasize+128; \
+    (bufobj).buf=realloc((bufobj).buf, (bufobj).memsize); \
+  } \
+  memcpy((bufobj).buf+(bufobj).size, data, datasize); \
+  (bufobj).size+=datasize
+#define buffer_writestr(bufobj, str) \
+  { \
+    uint32_t len=strlen(str); \
+    buffer_write((bufobj), &len, sizeof(len)); \
+    buffer_write((bufobj), str, len); \
+  }
+#define buffer_deinit(bufobj) free((bufobj).buf)
+struct buffer
+{
+  void* buf;
+  unsigned int size;
+  unsigned int memsize;
+};
+#endif
diff --git a/social.c b/social.c
new file mode 100644
index 0000000..2671895
--- /dev/null
+++ b/social.c
@@ -0,0 +1,317 @@
+/*
+    Socialnetwork, a truly peer-to-peer social network (in search of a better name)
+    Copyright (C) 2017  alicia@ion.nu
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License version 3
+    as published by the Free Software Foundation.
+
+    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 Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <gnutls/abstract.h>
+#include "peer.h"
+#include "buffer.h"
+#include "update.h"
+#include "social.h"
+struct user** social_users=0;
+unsigned int social_usercount=0;
+struct user* social_self;
+// Abstract away all the messagepassing and present information more or less statically
+// TODO: Think about privacy for all data updates
+// TODO: We must also sign all data updates to prevent forgeries
+
+static void updateinfo(struct peer* peer, void* data, unsigned int len)
+{
+  // <id, 20><sigsize, 4><signature><seq, 8><type, 1><timestamp, 8><type-specific data>
+  if(len<20){return;}
+  struct user* user=social_finduser(data);
+  if(!user || !user->pubkey){return;}
+  struct update* update=social_update_parse(user, data+20, len-20);
+  if(update){social_update_save(user, update);}
+}
+
+static void user_save(struct user* user)
+{
+  if(!user->pubkey){return;}
+  // TODO: Absolute path, something like $HOME/.socialnetwork
+  mkdir("users", 0700);
+  char path[strlen("users/0")+40];
+  sprintf(path, "users/"PEERFMT, PEERARG(user->id));
+  int f=open(path, O_WRONLY|O_CREAT|O_TRUNC, 0600);
+  gnutls_datum_t key;
+  gnutls_pubkey_export2(user->pubkey, GNUTLS_X509_FMT_PEM, &key);
+  uint32_t size=key.size;
+  write(f, &size, sizeof(size));
+  write(f, key.data, size);
+  gnutls_free(key.data);
+  close(f);
+}
+
+static void greetpeer(struct peer* peer, void* data, unsigned int len)
+{
+  // Figure out if they're one of our friends (TODO: or friends of friends)
+  unsigned int i, i2;
+  for(i=0; i<social_self->circlecount; ++i)
+  for(i2=0; i2<social_self->circles[i].count; ++i2)
+  {
+    struct user* user=social_self->circles[i].friends[i2];
+    if(!memcmp(user->id, peer->id, 20))
+    {
+      user->peer=peer;
+// TODO: Better way of getting someone's public key (I guess this is fine, but we need to be able to get it when they're not online too)
+      if(!user->pubkey)
+      {
+        gnutls_pubkey_init(&user->pubkey);
+        gnutls_pubkey_import_x509(user->pubkey, peer->cert, 0);
+        user_save(user);
+      }
+      // Ask for updates
+      len=20+sizeof(uint64_t);
+      unsigned char arg[len];
+      memcpy(arg, user->id, 20);
+      memcpy(arg+20, &user->seq, sizeof(user->seq));
+      peer_sendcmd(user->peer, "getupdates", arg, len);
+    }
+  }
+}
+
+static void sendupdate(struct peer* peer, const unsigned char id[20], struct update* update)
+{
+  struct buffer buf;
+  buffer_init(buf);
+  buffer_write(buf, id, 20);
+  buffer_write(buf, &update->signaturesize, sizeof(update->signaturesize));
+  buffer_write(buf, update->signature, update->signaturesize);
+  social_update_write(&buf, update);
+  peer_sendcmd(peer, "updateinfo", buf.buf, buf.size);
+  buffer_deinit(buf);
+}
+
+static void sendupdates(struct peer* peer, void* data, unsigned int len)
+{
+  // <ID, 20><seq, 8>
+  uint64_t seq;
+  if(len<20+sizeof(seq)){return;}
+  memcpy(&seq, data+20, sizeof(seq));
+  struct user* user;
+  // "getupdates" can also be requests for data of friends of friends
+  user=social_finduser(data);
+  if(!user){return;}
+  unsigned int i;
+  for(i=0; i<user->updatecount; ++i)
+  {
+    // TODO: Check privacy rules
+    // Also make sure not to send old news (based on seq)
+    if(user->updates[i].seq<=seq){continue;}
+    sendupdate(peer, user->id, &user->updates[i]);
+  }
+}
+
+static void user_load(struct user* user)
+{
+  // TODO: Absolute path, something like $HOME/.socialnetwork
+  // Load user data (only pubkey atm), but spare pubkey if it's already set
+  if(!user->pubkey)
+  {
+    char path[strlen("users/0")+40];
+    sprintf(path, "users/"PEERFMT, PEERARG(user->id));
+    int f=open(path, O_RDONLY);
+    if(f>=0)
+    {
+      uint32_t size;
+      read(f, &size, sizeof(size));
+      unsigned char keydata[size];
+      read(f, keydata, size);
+      close(f);
+      gnutls_datum_t key={.data=keydata, .size=size};
+      gnutls_pubkey_init(&user->pubkey);
+      gnutls_pubkey_import(user->pubkey, &key, GNUTLS_X509_FMT_PEM);
+    }
+  }
+  // Load updates
+  char path[strlen("updates/0")+40];
+  sprintf(path, "updates/"PEERFMT, PEERARG(user->id));
+  int f=open(path, O_RDONLY);
+  if(f<0){return;}
+  uint64_t size;
+  while(read(f, &size, sizeof(size))==sizeof(size))
+  {
+    uint8_t buf[size];
+    read(f, buf, size);
+    social_update_parse(user, buf, size);
+  }
+}
+
+static struct user* user_new(const unsigned char id[20])
+{
+  struct user* user=malloc(sizeof(struct user));
+  memcpy(user->id, id, 20);
+  user->pubkey=0;
+  user->peer=peer_findbyid(id);
+  user->name=0;
+  user->circles=0;
+  user->circlecount=0;
+  user->seq=0;
+  user->updates=0;
+  user->updatecount=0;
+  ++social_usercount;
+  social_users=realloc(social_users, sizeof(void*)*social_usercount);
+  social_users[social_usercount-1]=user;
+  user_load(user);
+  return user;
+}
+
+void social_init(const char* keypath)
+{
+  // Load key, friends, circles, etc. our own profile
+  peer_init(keypath);
+  social_self=user_new(peer_id);
+  if(!social_self->pubkey)
+  {
+    // Get our own pubkey
+    gnutls_pubkey_init(&social_self->pubkey);
+    gnutls_pubkey_import_privkey(social_self->pubkey, peer_privkey, 0, 0);
+    // Save our public key and reload our updates
+    user_save(social_self);
+    user_load(social_self);
+  }
+  peer_registercmd("updateinfo", updateinfo);
+  peer_registercmd("getpeers", greetpeer);
+  peer_registercmd("getupdates", sendupdates);
+// TODO: Set up socket and bootstrap here too? or accept an already set up socket?
+}
+
+void social_findfriends(void) // Call a second or so after init (once we have some bootstrap peers)
+{
+  // Loop through friends-list and try to find everyone's peers
+  unsigned int i;
+  for(i=0; i<social_usercount; ++i)
+  {
+    if(social_users[i]->peer){continue;}
+    peer_findpeer(social_users[i]->id);
+  }
+}
+
+void social_user_addtocircle(struct user* user, uint32_t circle, unsigned char id[20])
+{
+  if(circle>=user->circlecount)
+  {
+    user->circles=realloc(user->circles, sizeof(struct friendslist)*(circle+1));
+    for(; user->circlecount<=circle; ++user->circlecount)
+    {
+      user->circles[user->circlecount].name=0;
+      user->circles[user->circlecount].friends=0;
+      user->circles[user->circlecount].count=0;
+    }
+  }
+  struct user* friend=social_finduser(id);
+  if(!friend){friend=user_new(id);}
+  struct friendslist* c=&user->circles[circle];
+  ++c->count;
+  c->friends=realloc(c->friends, sizeof(void*)*c->count);
+  c->friends[c->count-1]=friend;
+}
+
+void social_addfriend(unsigned char id[20], uint32_t circle)
+{
+  struct user* friend=social_finduser(id);
+  if(!friend){friend=user_new(id);}
+  if(!friend->peer)
+  {
+    peer_findpeer(id);
+  }else{
+    if(!friend->pubkey)
+    {
+      gnutls_pubkey_init(&friend->pubkey);
+      gnutls_pubkey_import_x509(friend->pubkey, friend->peer->cert, 0);
+      user_save(friend);
+    }
+    unsigned int len=20+sizeof(uint64_t);
+    unsigned char arg[len];
+    memcpy(arg, friend->id, 20);
+    memcpy(arg+20, &friend->seq, sizeof(friend->seq));
+    peer_sendcmd(friend->peer, "getupdates", arg, len);
+  }
+  social_user_addtocircle(social_self, circle, id);
+// TODO: Send a friend request/notification at some point?
+  struct update* update=social_update_new(social_self);
+  ++social_self->seq;
+  update->seq=social_self->seq;
+  update->type=UPDATE_FRIENDS;
+  update->timestamp=time(0);
+  update->friends.circle=circle;
+  update->friends.add=1;
+  memcpy(update->friends.id, id, 20);
+  social_update_sign(update);
+  social_update_save(social_self, update);
+  social_shareupdate(update);
+}
+
+void social_createpost(const char* msg)
+{
+  // TODO: Posts attached to users and/or users' updates
+  struct update* post=social_update_new(social_self);
+  ++social_self->seq;
+  post->seq=social_self->seq;
+  post->type=UPDATE_POST;
+  post->timestamp=time(0);
+  post->post.message=strdup(msg);
+  social_update_sign(post);
+  social_update_save(social_self, post);
+  social_shareupdate(post);
+}
+
+void social_updatefield(const char* name, const char* value)
+{
+  struct update* post=social_update_new(social_self);
+  ++social_self->seq;
+  post->seq=social_self->seq;
+  post->type=UPDATE_FIELD;
+  post->timestamp=time(0);
+  post->field.name=strdup(name);
+  post->field.value=strdup(value);
+  social_update_sign(post);
+  social_update_save(social_self, post);
+  social_shareupdate(post);
+}
+
+struct user* social_finduser(unsigned char id[20])
+{
+  unsigned int i;
+  for(i=0; i<social_usercount; ++i)
+  {
+    if(!memcmp(social_users[i]->id, id, 20)){return social_users[i];}
+  }
+  return 0;
+}
+
+void social_shareupdate(struct update* update)
+{
+  // Send update to anyone who is currently online and which the update's privacy settings allow
+  unsigned int i;
+  for(i=0; i<social_self->circlecount; ++i)
+  {
+    struct friendslist* c=&social_self->circles[i];
+    unsigned int i2;
+    for(i2=0; i2<c->count; ++i2)
+    {
+// TODO: Privacy settings for updates
+      if(c->friends[i2]->peer)
+      {
+        sendupdate(c->friends[i2]->peer, social_self->id, update);
+      }
+    }
+  }
+}
diff --git a/social.h b/social.h
new file mode 100644
index 0000000..91fcd0b
--- /dev/null
+++ b/social.h
@@ -0,0 +1,61 @@
+/*
+    Socialnetwork, a truly peer-to-peer social network (in search of a better name)
+    Copyright (C) 2017  alicia@ion.nu
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License version 3
+    as published by the Free Software Foundation.
+
+    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 Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#ifndef SOCIAL_H
+#define SOCIAL_H
+#include <stdint.h>
+/* TODO: Privacy
+enum privacy
+{
+  PRIVACY_SELF=0,
+  PRIVACY_FRIENDS,
+  PRIVACY_FRIENDSOFFRIENDS,
+  PRIVACY_ANYONE
+};
+*/
+// TODO: Posts need to be restrictable to given circles
+
+struct friendslist
+{
+  char* name; // What to call this circle of friends
+  struct user** friends;
+  unsigned int count;
+};
+
+struct user
+{
+  unsigned char id[20];
+  gnutls_pubkey_t pubkey;
+  struct peer* peer;
+  const char* name;
+  struct friendslist* circles;
+  unsigned int circlecount;
+  uint64_t seq; // Sequence of updates we have from this user. 64 bits should be enough for a lifetime of updates (18446744073709551616 updates, enough to update 584 times per millisecond for a million years)
+  struct update* updates;
+  unsigned int updatecount;
+};
+
+extern struct user** social_users;
+extern unsigned int social_usercount;
+extern struct user* social_self; // Most things we need to keep track of for ourself are the same things we need to keep track of for others
+extern void social_init(const char* keypath);
+extern void social_user_addtocircle(struct user* user, uint32_t circle, unsigned char id[20]);
+extern void social_addfriend(unsigned char id[20], uint32_t circle);
+extern void social_createpost(const char* msg);
+extern void social_updatefield(const char* name, const char* value);
+extern struct user* social_finduser(unsigned char id[20]);
+extern void social_shareupdate(struct update* update);
+#endif
diff --git a/socialtest.c b/socialtest.c
new file mode 100644
index 0000000..7beeb75
--- /dev/null
+++ b/socialtest.c
@@ -0,0 +1,137 @@
+/*
+    Socialnetwork, a truly peer-to-peer social network (in search of a better name)
+    Copyright (C) 2017  alicia@ion.nu
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License version 3
+    as published by the Free Software Foundation.
+
+    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 Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <string.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <time.h>
+#include <netdb.h>
+#include <sys/poll.h>
+#include <sys/socket.h>
+#include "peer.h"
+#include "social.h"
+#include "update.h"
+
+int main(int argc, char** argv)
+{
+  int sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
+  if(argc>1)
+  {
+    struct addrinfo* ai;
+    getaddrinfo("0.0.0.0", argv[1], 0, &ai);
+    bind(sock, ai->ai_addr, ai->ai_addrlen);
+    freeaddrinfo(ai);
+  }
+  social_init("priv.pem");
+  peer_bootstrap(sock, "127.0.0.1:4000");
+
+  struct pollfd pfd[]={{.fd=0, .events=POLLIN, .revents=0}, {.fd=sock, .events=POLLIN, .revents=0}};
+  char buf[1024];
+  unsigned int i;
+  while(1)
+  {
+    printf("> ");
+    fflush(stdout);
+    poll(pfd, 2, -1);
+    if(pfd[0].revents) // stdin
+    {
+      pfd[0].revents=0;
+      ssize_t len=read(0, buf, 1023);
+      while(len>0 && strchr("\r\n", buf[len-1])){--len;}
+      buf[len]=0;
+      if(!strcmp(buf, "lsfriends")) // TODO: These aren't necessarily friends
+      {
+        for(i=0; i<social_usercount; ++i)
+        {
+          printf("%p %s\n", social_users[i], social_users[i]->peer?"(connected)":"");
+          unsigned int i2;
+          for(i2=0; i2<social_users[i]->updatecount; ++i2)
+          {
+            if(social_users[i]->updates[i2].type==UPDATE_FIELD)
+            {
+              printf("  %s = %s", social_users[i]->updates[i2].field.name, social_users[i]->updates[i2].field.value);
+            }
+          }
+        }
+      }
+      else if(!strcmp(buf, "lsupdates")) // List our own updates for starters
+      {
+        unsigned int i;
+        for(i=0; i<social_self->updatecount; ++i)
+        {
+          struct update* update=&social_self->updates[i];
+          time_t timestamp=update->timestamp;
+          switch(update->type)
+          {
+          case UPDATE_FIELD:
+            printf("\nField %s%s = %s\n", ctime(&timestamp), update->field.name, update->field.value);
+            break;
+          case UPDATE_POST:
+            printf("\nPost %s%s\n", ctime(&timestamp), update->post.message);
+            break;
+          }
+        }
+      }
+      else if(!strncmp(buf, "addfriend ", 10))
+      {
+        if(strlen(&buf[10])<40){continue;}
+        char byte[3]={0,0,0};
+        unsigned char binid[20];
+        unsigned int i;
+        for(i=0; i<20; ++i)
+        {
+          memcpy(byte, &buf[10+i*2], 2);
+          binid[i]=strtoul(byte, 0, 16);
+        }
+        // TODO: Prompt for circle
+        social_addfriend(binid, 0);
+      }
+      else if(!strcmp(buf, "update post"))
+      {
+        printf("Enter post: (finish with ctrl+D)\n");
+        buf[0]=0;
+        unsigned int len;
+        while((len=strlen(buf))<1023)
+        {
+          ssize_t r=read(0, &buf[len], 1023-len);
+          if(r<1){break;}
+          buf[len+r]=0;
+        }
+        social_createpost(buf);
+      }
+      else if(!strncmp(buf, "update field ", 13))
+      {
+        char name[strlen(&buf[13]+1)];
+        strcpy(name, &buf[13]);
+        printf("Enter value: "); fflush(stdout);
+        unsigned int len=read(0, buf, 1023);
+        buf[len]=0;
+        social_updatefield(name, buf);
+      }
+    }
+    if(pfd[1].revents) // UDP
+    {
+      pfd[1].revents=0;
+      // Erase prompt
+      printf("\r  \r");
+      fflush(stdout);
+      peer_handlesocket(sock);
+// TODO: Notify of updates as they happen
+    }
+  }
+  return 0;
+}
diff --git a/update.c b/update.c
new file mode 100644
index 0000000..81f2e1d
--- /dev/null
+++ b/update.c
@@ -0,0 +1,251 @@
+/*
+    Socialnetwork, a truly peer-to-peer social network (in search of a better name)
+    Copyright (C) 2017  alicia@ion.nu
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License version 3
+    as published by the Free Software Foundation.
+
+    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 Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include <string.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <gnutls/abstract.h>
+#include "peer.h"
+#include "buffer.h"
+#include "social.h"
+#include "update.h"
+
+static void mkdirp(char* path)
+{
+  char* next=path;
+  char* slash;
+  while((slash=strchr(next, '/')))
+  {
+    next=&slash[1];
+    slash[0]=0;
+    mkdir(path, 0700);
+    slash[0]='/';
+  }
+}
+
+void social_update_write(struct buffer* buf, struct update* update)
+{
+  // Write update to buffer, minus the signature (in part to generate the signature)
+  buffer_write(*buf, &update->seq, sizeof(update->seq));
+  buffer_write(*buf, &update->type, sizeof(update->type));
+  buffer_write(*buf, &update->timestamp, sizeof(update->timestamp));
+  switch(update->type)
+  {
+  case UPDATE_FIELD:
+    buffer_writestr(*buf, update->field.name);
+    buffer_writestr(*buf, update->field.value);
+    break;
+  case UPDATE_POST:
+    buffer_writestr(*buf, update->post.message);
+    break;
+  case UPDATE_MEDIA:
+// TODO: Handle large media, can't keep it all in RAM. Maybe only send name and size here and handle requests for the actual data separately?
+    buffer_writestr(*buf, update->media.name);
+    buffer_write(*buf, &update->media.size, sizeof(update->media.size));
+// TODO: Include signature of file?
+    break;
+  case UPDATE_FRIENDS:
+    buffer_write(*buf, &update->friends.circle, sizeof(update->friends.circle));
+    buffer_write(*buf, &update->friends.add, sizeof(update->friends.add));
+    buffer_write(*buf, update->friends.id, 20);
+    break;
+  case UPDATE_CIRCLE:
+    buffer_write(*buf, &update->circle.circle, sizeof(update->circle.circle));
+    buffer_writestr(*buf, update->circle.name);
+    break;
+  }
+}
+
+struct update* social_update_new(struct user* user)
+{
+  ++user->updatecount;
+  user->updates=realloc(user->updates, sizeof(struct update)*user->updatecount);
+  return &user->updates[user->updatecount-1];
+}
+
+void social_update_sign(struct update* update)
+{
+  struct buffer buf;
+  buffer_init(buf);
+  social_update_write(&buf, update);
+  gnutls_datum_t data={.data=buf.buf, .size=buf.size};
+  gnutls_datum_t signature;
+  gnutls_privkey_sign_data(peer_privkey, GNUTLS_DIG_SHA1, 0, &data, &signature);
+  buffer_deinit(buf);
+  update->signaturesize=signature.size;
+  void* sigbuf=malloc(signature.size);
+  memcpy(sigbuf, signature.data, signature.size);
+  gnutls_free(signature.data);
+  update->signature=sigbuf;
+}
+
+void social_update_save(struct user* user, struct update* update)
+{
+  // TODO: Absolute path, something like $HOME/.socialnetwork
+  char path[strlen("updates/0")+40];
+  sprintf(path, "updates/"PEERFMT, PEERARG(user->id));
+  mkdirp(path);
+  int f=open(path, O_WRONLY|O_CREAT|O_APPEND, 0600);
+  struct buffer buf;
+  buffer_init(buf);
+  social_update_write(&buf, update);
+  uint64_t size=sizeof(update->signaturesize)+update->signaturesize+buf.size;
+  write(f, &size, sizeof(size));
+  write(f, &update->signaturesize, sizeof(update->signaturesize));
+  write(f, update->signature, update->signaturesize);
+  write(f, buf.buf, buf.size);
+  buffer_deinit(buf);
+  close(f);
+// TODO: Is it bad to close and reopen a file in rapid succession? if it is maybe we should implement some kind of cache for cases where we're saving many updates fast, like receiving someone else's updates for the first time
+}
+
+static struct update* update_getfield(struct user* user, const char* name)
+{
+  unsigned int i;
+  for(i=0; i<user->updatecount; ++i)
+  {
+    if(user->updates[i].type!=UPDATE_FIELD){continue;}
+    if(!strcmp(user->updates[i].field.name, name)){return &user->updates[i];}
+  }
+  struct update* ret=social_update_new(user);
+  ret->seq=0;
+  ret->signature=0;
+  ret->field.name=strdup(name);
+  ret->field.value=0;
+  return ret;
+}
+
+static struct update* update_getfriend(struct user* user, uint32_t circle, const unsigned char id[20])
+{
+  unsigned int i;
+  for(i=0; i<user->updatecount; ++i)
+  {
+    if(user->updates[i].type!=UPDATE_FRIENDS){continue;}
+    if(user->updates[i].friends.circle!=circle){continue;}
+    if(!memcmp(user->updates[i].friends.id, id, 20)){return &user->updates[i];}
+  }
+  struct update* ret=social_update_new(user);
+  ret->seq=0;
+  ret->signature=0;
+  ret->friends.circle=circle;
+  memcpy(ret->friends.id, id, 20);
+  return ret;
+}
+
+#define advance(data, size, length) data+=length; size-=length
+#define readbin(data, datalen, buf, buflen) \
+  if(datalen<buflen){return 0;} \
+  memcpy(buf, data, buflen); \
+  advance(data, datalen, buflen)
+struct update* social_update_parse(struct user* user, void* data, unsigned int len) // Both for receiving updates and loading them from file
+{
+  // <sigsize, 4><signature><seq, 8><type, 1><timestamp, 8><type-specific data>
+  uint32_t signaturesize;
+  uint64_t seq;
+  uint8_t type;
+  uint64_t timestamp;
+  readbin(data, len, &signaturesize, sizeof(signaturesize));
+  unsigned char signature[signaturesize];
+  readbin(data, len, signature, signaturesize);
+  if(!user->pubkey){return 0;} // Don't have their public key to verify yet
+  // 1. Verify signature
+  gnutls_datum_t verifydata={.data=data, .size=len};
+  gnutls_datum_t verifysig={.data=signature, .size=signaturesize};
+  if(gnutls_pubkey_verify_data2(user->pubkey, GNUTLS_SIGN_RSA_SHA1, 0, &verifydata, &verifysig)<0){return 0;} // Forgery
+  readbin(data, len, &seq, sizeof(seq));
+  readbin(data, len, &type, sizeof(type));
+  readbin(data, len, &timestamp, sizeof(timestamp));
+  // 2. Check sequence number uniqueness
+  // NOTE: If instead of checking uniqueness we just checked if seq was higher than the user's previous seq: When relaying updates to a friend's friend a malicious peer could skip some entries, but pass along a more recent one, to effectively censor the earlier entries even when the author themself sends them at a later time. Hopefully it'll be enough that we probably get updates from multiple sources, so if one skips stuff we'll still get filled in by someone else
+  unsigned int i;
+  for(i=0; i<user->updatecount; ++i)
+  {
+    if(user->updates[i].seq==seq){return 0;} // Old update
+  }
+// TODO: To avoid an accidental form of the above when a friend's friend is in different circles and doesn't get the same updates, only update seq when we get the update directly from user or when seq==user->seq+1
+  if(user->seq<seq){user->seq=seq;} // Update user's sequence
+  // 3. Add to list of updates, replacing any old entry for the same data when applicable (e.g. updating profile fields, but not posts)
+  struct update* update;
+  switch(type)
+  {
+  case UPDATE_FIELD:
+    {
+    uint32_t namelen;
+    readbin(data, len, &namelen, sizeof(namelen));
+    uint32_t valuelen;
+    if(len<namelen+sizeof(valuelen)){return 0;}
+    char name[namelen+1];
+    readbin(data, len, name, namelen);
+    name[namelen]=0;
+    readbin(data, len, &valuelen, sizeof(valuelen));
+    if(len<valuelen){return 0;}
+    char* value=malloc(valuelen+1);
+    readbin(data, len, value, valuelen);
+    value[valuelen]=0;
+    // Erase/replace any old field with the same name if the sequence number is higher
+    update=update_getfield(user, name);
+    if(update->seq>seq){free(value); return 0;} // Old version
+    free((void*)update->signature);
+    free((void*)update->field.value);
+    update->field.value=value;
+    }
+    break;
+  case UPDATE_POST:
+    {
+    uint32_t msglen;
+    readbin(data, len, &msglen, sizeof(msglen));
+    if(len<msglen){return 0;}
+    char* msg=malloc(msglen+1);
+    readbin(data, len, msg, msglen);
+    msg[msglen]=0;
+    update=social_update_new(user);
+    update->post.message=msg;
+    }
+    break;
+  case UPDATE_MEDIA:
+    return 0; // TODO: Implement
+    break;
+  case UPDATE_FRIENDS:
+    {
+    uint32_t circle;
+    unsigned char id[20];
+    char add;
+    readbin(data, len, &circle, sizeof(circle));
+    readbin(data, len, &add, sizeof(add));
+    readbin(data, len, id, 20);
+    if(add)
+    {
+      social_user_addtocircle(user, circle, id);
+    } // TODO: Removal
+    update=update_getfriend(user, circle, id);
+    if(update->seq>seq){return 0;} // Old version
+    update->friends.add=add;
+    }
+    break;
+  default: return 0;
+  }
+  void* sigbuf=malloc(signaturesize);
+  memcpy(sigbuf, signature, signaturesize);
+  update->signaturesize=signaturesize;
+  update->signature=sigbuf;
+  update->seq=seq;
+  update->type=type;
+  update->timestamp=timestamp;
+  return update;
+}
diff --git a/update.h b/update.h
new file mode 100644
index 0000000..bb7a301
--- /dev/null
+++ b/update.h
@@ -0,0 +1,72 @@
+/*
+    Socialnetwork, a truly peer-to-peer social network (in search of a better name)
+    Copyright (C) 2017  alicia@ion.nu
+
+    This program is free software: you can redistribute it and/or modify
+    it under the terms of the GNU Affero General Public License version 3
+    as published by the Free Software Foundation.
+
+    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 Affero General Public License for more details.
+
+    You should have received a copy of the GNU Affero General Public License
+    along with this program.  If not, see <http://www.gnu.org/licenses/>.
+*/
+#include "social.h"
+#include "buffer.h"
+
+enum updatetype
+{
+  UPDATE_FIELD=0, // Name, profile description etc.
+  UPDATE_POST,
+  UPDATE_MEDIA, // Pictures, other files?
+  UPDATE_FRIENDS, // Friend addition/removal
+  UPDATE_CIRCLE, // Private circle names
+// TODO: UPDATE_DELETEPOST? post/media comments? (or maybe commenting on any kind of update?)
+};
+struct update
+{
+  const char* signature;
+  uint32_t signaturesize;
+  uint64_t seq; // Sequence of this update
+  uint8_t type;
+  uint64_t timestamp;
+// TODO: Privacy settings
+  union
+  {
+    struct // I guess we let it be possible to have any kind of field
+    {
+      const char* name;
+      const char* value;
+      // Don't include media but allow referencing other shared media
+    } field;
+    struct
+    {
+      // TODO: some equivalent of posting on someone/something's wall?
+      const char* message;
+    } post;
+    struct
+    {
+      const char* name;
+      uint64_t size; // Allow large files, TODO: but maybe have a per-client limit of how large things you'll host. Also large files need to be split up somehow, can't send 1 gigabyte UDP packets
+    } media;
+    struct
+    {
+      uint32_t circle;
+      unsigned char id[20];
+      char add; // 1=add, 0=remove
+    } friends;
+    struct
+    {
+      uint32_t circle;
+      const char* name;
+    } circle;
+  };
+};
+extern void social_update_write(struct buffer* buf, struct update* update);
+extern struct update* social_update_new(struct user* user);
+extern void social_update_sign(struct update* update);
+extern void social_update_save(struct user* user, struct update* update);
+extern struct update* social_update_parse(struct user* user, void* data, unsigned int len); // Both for receiving updates and loading them from file