$ git clone https://socialnetwork.ion.nu/socialnetwork.git
commit 00fbcf33aec0eb140e39f0ab5964e38ed9c0454d
Author: Alicia <...>
Date:   Wed Jan 18 22:20:48 2017 +0100

    Implemented more of circles (friendslists)

diff --git a/Makefile b/Makefile
index 390b28c..d3f053f 100644
--- a/Makefile
+++ b/Makefile
@@ -13,3 +13,6 @@ peertest: peertest.o peer.o udpstream.o
 
 udptest: udptest.o udpstream.o
  $(CC) $^ -o $@
+
+clean:
+ rm -f *.o *.so socialtest peertest udptest
diff --git a/social.c b/social.c
index 2958012..a0d82b3 100644
--- a/social.c
+++ b/social.c
@@ -207,7 +207,7 @@ void social_findfriends(void) // Call a second or so after init (once we have so
   }
 }
 
-static struct friendslist* user_getcircle(struct user* user, uint32_t circle)
+struct friendslist* social_user_getcircle(struct user* user, uint32_t circle)
 {
   if(circle>=user->circlecount)
   {
@@ -229,12 +229,29 @@ void social_user_addtocircle(struct user* user, uint32_t circle, const unsigned
 {
   struct user* friend=social_finduser(id);
   if(!friend){friend=user_new(id);}
-  struct friendslist* c=user_getcircle(user, circle);
+  struct friendslist* c=social_user_getcircle(user, circle);
   ++c->count;
   c->friends=realloc(c->friends, sizeof(void*)*c->count);
   c->friends[c->count-1]=friend;
 }
 
+void social_user_removefromcircle(struct user* user, uint32_t circle, const unsigned char id[20])
+{
+  struct user* friend=social_finduser(id);
+  if(!friend){friend=user_new(id);}
+  struct friendslist* c=social_user_getcircle(user, circle);
+  unsigned int i;
+  for(i=0; i<c->count; ++i)
+  {
+    if(c->friends[i]==friend)
+    {
+      --c->count;
+      memmove(&c->friends[i], &c->friends[i+1], sizeof(void*)*(c->count-i));
+      // TODO: Garbage-collect users who are no longer friends of anyone we know?
+    }
+  }
+}
+
 void social_addfriend(const unsigned char id[20], uint32_t circle)
 {
   struct user* friend=social_finduser(id);
@@ -242,6 +259,7 @@ void social_addfriend(const unsigned char id[20], uint32_t circle)
   if(!friend->peer)
   {
     peer_findpeer(id);
+    // TODO: Request updates from any mutual friends we're connected to in the meantime
   }else{
     if(!friend->pubkey)
     {
@@ -260,7 +278,6 @@ void social_addfriend(const unsigned char id[20], uint32_t circle)
   struct update* update=social_update_getfriend(social_self, circle, id);
   ++social_self->seq;
   update->seq=social_self->seq;
-  update->type=UPDATE_FRIENDS;
   update->timestamp=time(0);
   privcpy(update->privacy, social_self->circles[circle].privacy);
   update->friends.add=1;
@@ -269,6 +286,21 @@ void social_addfriend(const unsigned char id[20], uint32_t circle)
   social_shareupdate(update);
 }
 
+void social_removefriend(const unsigned char id[20], uint32_t circle)
+{
+  social_user_removefromcircle(social_self, circle, id);
+  struct update* update=social_update_getfriend(social_self, circle, id);
+  ++social_self->seq;
+  update->seq=social_self->seq;
+  update->type=UPDATE_FRIENDS;
+  update->timestamp=time(0);
+  privcpy(update->privacy, social_self->circles[circle].privacy);
+  update->friends.add=0;
+  social_update_sign(update);
+  social_update_save(social_self, update);
+  social_shareupdate(update);
+}
+
 void social_createpost(const char* msg, struct privacy* privacy)
 {
   // TODO: Posts attached to users and/or users' updates
@@ -289,7 +321,6 @@ void social_updatefield(const char* name, const char* value, struct privacy* pri
   struct update* post=social_update_getfield(social_self, name);
   ++social_self->seq;
   post->seq=social_self->seq;
-  post->type=UPDATE_FIELD;
   post->timestamp=time(0);
   privcpy(post->privacy, *privacy);
   post->field.value=strdup(value);
@@ -353,3 +384,23 @@ char social_privacy_check(struct user* origin, struct privacy* privacy, struct u
   }
   return 0;
 }
+
+void social_setcircle(uint32_t circle, const char* name, struct privacy* privacy)
+{
+  struct friendslist* c=social_user_getcircle(social_self, circle);
+  free(c->name);
+  c->name=strdup(name);
+  privcpy(c->privacy, *privacy);
+  // Private circle update
+  struct update* update=social_update_getcircle(social_self, circle);
+  free((void*)update->circle.name);
+  ++social_self->seq;
+  update->seq=social_self->seq;
+  update->timestamp=time(0);
+  // TODO: Is there any situation where we would want this update to be public?
+  update->circle.circle=circle;
+  update->circle.name=strdup(name);
+  privcpy(update->circle.privacy, *privacy);
+  social_update_sign(update);
+  social_update_save(social_self, update);
+}
diff --git a/social.h b/social.h
index 8fb27fe..6bfce28 100644
--- a/social.h
+++ b/social.h
@@ -58,11 +58,15 @@ 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 struct friendslist* social_user_getcircle(struct user* user, uint32_t circle);
 extern void social_user_addtocircle(struct user* user, uint32_t circle, const unsigned char id[20]);
+extern void social_user_removefromcircle(struct user* user, uint32_t circle, const unsigned char id[20]);
 extern void social_addfriend(const unsigned char id[20], uint32_t circle);
+extern void social_removefriend(const unsigned char id[20], uint32_t circle);
 extern void social_createpost(const char* msg, struct privacy* privacy);
 extern void social_updatefield(const char* name, const char* value, struct privacy* privacy);
 extern struct user* social_finduser(const unsigned char id[20]);
 extern void social_shareupdate(struct update* update);
 extern char social_privacy_check(struct user* origin, struct privacy* privacy, struct user* user);
+extern void social_setcircle(uint32_t circle, const char* name, struct privacy* privacy);
 #endif
diff --git a/socialtest.c b/socialtest.c
index 33e49e0..8269a7b 100644
--- a/socialtest.c
+++ b/socialtest.c
@@ -26,6 +26,35 @@
 #include "social.h"
 #include "update.h"
 
+// Inject after "Will be shown/visible to ..."
+void printprivacy(struct privacy* privacy)
+{
+  if(!privacy->flags)
+  {
+    if(!privacy->circlecount){printf("no one"); return;}
+    printf("the circles ");
+    unsigned int usertotal=0;
+    unsigned int i;
+    for(i=0; i<privacy->circlecount; ++i)
+    {
+      if(i){printf(", ");}
+      struct friendslist* circle=social_user_getcircle(social_self, privacy->circles[i]);
+      printf("'%s'", circle->name?circle->name:"Unnamed circle");
+      usertotal+=circle->count;
+    }
+    printf(", %u users in total", usertotal);
+  }
+  else if(privacy->flags&PRIVACY_ANYONE)
+  {
+    printf("anyone");
+  }
+  else if(privacy->flags&PRIVACY_FRIENDS)
+  {
+    printf("all friends");
+  }
+  else{printf("unknown privacy flag set!");}
+}
+
 int main(int argc, char** argv)
 {
   int sock=socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
@@ -76,13 +105,21 @@ int main(int argc, char** argv)
         {
           struct update* update=&social_self->updates[i];
           time_t timestamp=update->timestamp;
+          printf("\nVisible to ");
+          printprivacy(&update->privacy);
           switch(update->type)
           {
           case UPDATE_FIELD:
-            printf("\nField %s%s = %s\n", ctime(&timestamp), update->field.name, update->field.value);
+            printf("\nField %s%s = %s", ctime(&timestamp), update->field.name, update->field.value);
             break;
           case UPDATE_POST:
-            printf("\nPost %s%s\n", ctime(&timestamp), update->post.message);
+            printf("\nPost %s%s", ctime(&timestamp), update->post.message);
+            break;
+          case UPDATE_FRIENDS:
+            printf("\nFriend %s%s\n", ctime(&timestamp), update->friends.add?"Add":"Remove");
+            break;
+          case UPDATE_CIRCLE:
+            printf("\nCircle %s%u: %s\n", ctime(&timestamp), update->circle.circle, update->circle.name);
             break;
           }
         }
@@ -132,30 +169,16 @@ int main(int argc, char** argv)
         for(i=0; i<social_self->circlecount; ++i)
         {
           struct friendslist* circle=&social_self->circles[i];
-          printf("%u: %s (%u friends)\n", i, circle->name?circle->name:"Unnamed circle", circle->count);
+          printf("%u: %s (%u friends), additions/removals are visible to ", i, circle->name?circle->name:"Unnamed circle", circle->count);
+          printprivacy(&circle->privacy);
+          printf("\n");
         }
       }
       else if(!strcmp(buf, "privacy"))
       {
-        if(!privacy.flags)
-        {
-          printf("%u circles of friends can see updates with this setting:\n", privacy.circlecount);
-          for(i=0; i<privacy.circlecount; ++i)
-          {
-            if(privacy.circles[i]>=social_self->circlecount){printf("Undefined circle\n"); continue;}
-            struct friendslist* circle=&social_self->circles[privacy.circles[i]];
-            printf("%s (%u friends)\n", circle->name?circle->name:"Unnamed circle", circle->count);
-          }
-        }
-        else if(privacy.flags&PRIVACY_ANYONE)
-        {
-          printf("Anyone can see updates with this setting\n");
-        }
-        else if(privacy.flags&PRIVACY_FRIENDS)
-        {
-          printf("Friends in any circle can see updates with this setting\n");
-        }
-        else{printf("Unknown privacy flag set!\n");}
+        printf("With this privacy setting updates will be visible to ");
+        printprivacy(&privacy);
+        printf("\n");
       }
       else if(!strncmp(buf, "privacy flag ", 13))
       {
@@ -192,6 +215,17 @@ int main(int argc, char** argv)
           printf("Added\n");
         }
       }
+      else if(!strncmp(buf, "setcircle ", 10))
+      {
+        uint32_t circle=strtoul(&buf[10], 0, 0);
+        printf("Enter name: "); fflush(stdout);
+        unsigned int len=read(0, buf, 1023);
+        buf[len]=0;
+        char* end;
+        while((end=strchr(buf, '\r'))||(end=strchr(buf, '\n'))){end[0]=0;}
+        // Note: we're also setting the privacy setting that will be referenced for future friend additions/removals from this circle, which might not be readily apparent. The generated update that sets the name (and privacy) of the circle is always private.
+        social_setcircle(circle, buf, &privacy);
+      }
       else{printf("Unknown command '%s'\n", buf);}
     }
     if(pfd[1].revents) // UDP
diff --git a/update.c b/update.c
index f23df64..5b63b5f 100644
--- a/update.c
+++ b/update.c
@@ -73,6 +73,10 @@ void social_update_write(struct buffer* buf, struct update* update)
   case UPDATE_CIRCLE:
     buffer_write(*buf, &update->circle.circle, sizeof(update->circle.circle));
     buffer_writestr(*buf, update->circle.name);
+    buffer_write(*buf, &update->circle.privacy.flags, sizeof(update->circle.privacy.flags));
+    buffer_write(*buf, &update->circle.privacy.circlecount, sizeof(update->circle.privacy.circlecount));
+    buffer_write(*buf, update->circle.privacy.circles, update->circle.privacy.circlecount);
+    buffer_write(*buf, &privplaceholder, sizeof(privplaceholder));
     break;
   }
 }
@@ -132,6 +136,7 @@ struct update* social_update_getfield(struct user* user, const char* name)
     if(!strcmp(user->updates[i].field.name, name)){return &user->updates[i];}
   }
   struct update* ret=social_update_new(user);
+  ret->type=UPDATE_FIELD;
   ret->seq=0;
   ret->signature=0;
   ret->field.name=strdup(name);
@@ -149,6 +154,7 @@ struct update* social_update_getfriend(struct user* user, uint32_t circle, const
     if(!memcmp(user->updates[i].friends.id, id, 20)){return &user->updates[i];}
   }
   struct update* ret=social_update_new(user);
+  ret->type=UPDATE_FRIENDS;
   ret->seq=0;
   ret->signature=0;
   ret->friends.circle=circle;
@@ -156,6 +162,26 @@ struct update* social_update_getfriend(struct user* user, uint32_t circle, const
   return ret;
 }
 
+struct update* social_update_getcircle(struct user* user, uint32_t circle)
+{
+  unsigned int i;
+  for(i=0; i<user->updatecount; ++i)
+  {
+    if(user->updates[i].type!=UPDATE_CIRCLE){continue;}
+    if(user->updates[i].circle.circle==circle){return &user->updates[i];}
+  }
+  struct update* ret=social_update_new(user);
+  ret->type=UPDATE_CIRCLE;
+  ret->seq=0;
+  ret->signature=0;
+  ret->circle.circle=circle;
+  ret->circle.name=0;
+  ret->circle.privacy.flags=0;
+  ret->circle.privacy.circles=0;
+  ret->circle.privacy.circlecount=0;
+  return ret;
+}
+
 #define advance(data, size, length) data+=length; size-=length
 #define readbin(data, datalen, buf, buflen) \
   if(datalen<buflen){return 0;} \
@@ -185,7 +211,7 @@ struct update* social_update_parse(struct user* user, void* data, unsigned int l
   readbin(data, len, &privacy.circlecount, sizeof(privacy.circlecount));
   uint32_t privcircles[privacy.circlecount];
   privacy.circles=privcircles;
-  readbin(data, len, privacy.circles, privacy.circlecount);
+  readbin(data, len, privacy.circles, sizeof(uint32_t)*privacy.circlecount);
   // Placeholder, potentially supporting lists of individuals in addition to circles
   uint32_t privplaceholder;
   readbin(data, len, &privplaceholder, sizeof(privplaceholder));
@@ -250,12 +276,39 @@ struct update* social_update_parse(struct user* user, void* data, unsigned int l
     if(add)
     {
       social_user_addtocircle(user, circle, id);
-    } // TODO: Removal
+    }else{
+      social_user_removefromcircle(user, circle, id);
+    }
     update=social_update_getfriend(user, circle, id);
     if(update->seq>seq){return 0;} // Old version
     update->friends.add=add;
     }
     break;
+  case UPDATE_CIRCLE:
+    {
+    uint32_t circle;
+    readbin(data, len, &circle, sizeof(circle));
+    uint32_t namelen;
+    readbin(data, len, &namelen, sizeof(namelen));
+    char name[namelen+1];
+    readbin(data, len, name, namelen);
+    name[namelen]=0;
+    struct privacy privacy;
+    readbin(data, len, &privacy.flags, sizeof(privacy.flags));
+    readbin(data, len, &privacy.circlecount, sizeof(privacy.circlecount));
+    uint32_t privcircles[privacy.circlecount];
+    privacy.circles=privcircles;
+    readbin(data, len, privacy.circles, sizeof(uint32_t)*privacy.circlecount);
+    struct friendslist* c=social_user_getcircle(user, circle);
+    free(c->name);
+    c->name=strdup(name);
+    privcpy(c->privacy, privacy);
+    update=social_update_getcircle(user, circle);
+    free((void*)update->circle.name);
+    update->circle.name=strdup(name);
+    privcpy(update->circle.privacy, privacy);
+    }
+    break;
   default: return 0;
   }
   void* sigbuf=malloc(signaturesize);
diff --git a/update.h b/update.h
index bda758e..f215655 100644
--- a/update.h
+++ b/update.h
@@ -62,6 +62,7 @@ struct update
     {
       uint32_t circle;
       const char* name;
+      struct privacy privacy;
     } circle;
   };
 };
@@ -71,4 +72,5 @@ extern void social_update_sign(struct update* update);
 extern void social_update_save(struct user* user, struct update* update);
 extern struct update* social_update_getfield(struct user* user, const char* name);
 extern struct update* social_update_getfriend(struct user* user, uint32_t circle, const unsigned char id[20]);
+extern struct update* social_update_getcircle(struct user* user, uint32_t circle);
 extern struct update* social_update_parse(struct user* user, void* data, unsigned int len); // Both for receiving updates and loading them from file