Cocoa

『側に / Sobani』 —— 在你身旁

Sobani —— Fully self-hostable and customizable media entertaining stream service based on Node.js for couples, friends, collaboration teams, and even quarantined office workers.

在几周前的一个晚上,跟猫猫闲聊时,猫猫有提到说「要是可以一起听音乐 / 看电影就好了」。虽然 Team Viewer 的音频共享可以将被连接的电脑的音频发送过来,同时也会将屏幕共享过来,在网络不太好的情况下音频会断断续续,同时视频的质量也不太高,体验会比较差QwQ 另外 Team Viewer 在开始语音的时候,就不能 forward 电脑输出的音频给对方了,因此还需要别的软件来做语音通话Σ(・□・;)

在一边想象着跟猫猫听音乐 / 看电影的时候,一边就在跟猫猫开始计划着写这个项目。每次开始写项目时,猫猫和 Cocoa 一致认为日常最难问题就是叫什么名字喵,一开始猫猫提到可以用「接続 / Setsuzoku」抽取一个部分和 link 接起来,也就是「setsulink」这样的;又或者是「cosetsu」,即 coop 和 setsuzoku。在我们纠结一段时间名字之后,

猫猫提议:把自己的心愿写进去

Cocoa:Sobani そばに?

猫猫:诶 是什么呀

Cocoa:就是在身边的意思,要说心愿的话,大概就是这个 w 想要在身边,但是又只能暂时用软件来实现啦

在决定好用这样的心愿起名字之后,就开始了这个满怀爱(狗)意(粮)的项目啦/ 头一次用 VS Code 的 Live Share 一起写代码!

那么想到的连接办法当然就是 NAT 穿透 了!在最开始的时候,我们决定使用的语言是 Go,然后本来是想用 libp2p 来完成的,但是后来验证发现,libp2p 默认使用的是 TCP 连接,虽然 TCP 的 NAT 穿透是有方法的,但是似乎实现起来会太复杂了,以及对于 Go 上跨平台的、好看的 UI 还没有什么头绪(>﹏<)

项目的地址在 nekomeowww/sobani,最新的 build 都放在 release 里面啦~同时支持 macOS / Linux / Windows 的说(*^3^)

公开的 tracker 服务器的地址是 34.80.41.119:3000,这个 Sobani 的 tracker 服务器也是可以自己搭建的/

Sobani tracker 的项目地址是 nekomeowww/sobani-tracker,里面的 deploy.sh 脚本在 Debian / Ubuntu 上验证过~

Continue reading 『側に / Sobani』 —— 在你身旁

被 CloudFlare 耽误的 v2ray ws+tls

前几天跟佑奈在闲聊的时候,听佑奈吐槽说她的 v2ray 似乎被制裁了,于是 Cocoa 这边推荐她用 v2ray 的 ws+tls+CDN,不过佑奈说「CDN 之后,除了 Mac 其他的都访问不到了」

于是 Cocoa 就跟着佑奈研究了好久好久,把几乎所有可能的地方都怀疑了一下,从 DNS、VPS 的 IP、acme.sh 申请证书,一路怀疑到 Nginx 版本、SSL Cipher 协商、TLS 版本、三级域名设置……那天晚上折腾到了凌晨 6 点才睡QwQ 最后发现……居然是 CloudFlare 的锅!

详细的过程和最后的总结的话,佑奈执笔跟 Cocoa 在佑奈的博客上一起写了一篇非常非常详细的心累历程记录~ 是佑奈写得炒鸡用心的一篇记录喵~被 CloudFlare 耽误的 v2ray ws+tls

虽然最后在写这个详细的记录的时候,又遇到了一些奇妙的问题(>﹏<) 改前端果然也是一个大坑呀Σ(・□・;)

在 CentOS 7 中编译安装 Aria2

咕噜~昨天猫猫在她的 VPS 上给了我一个账号,然后还花了好长时间帮我配置,zsh 的主题也是猫猫自己写的哦,而且还给了 2TB 的空间~是头一次有人给 Cocoa 配置系统,超级感动的说 (>﹏<)

在拿到之后就跟着猫猫安装了 neofetch~( ^ω^ )

是我之前几乎没用过的 CentOS 7!在一边安装一边聊的时候,猫猫说她在 CentOS 上使用 Aria2 的时候遇到过一些问题,那么~这里就让 Cocoa 来帮猫猫解决吧!

以前 Cocoa 自己折腾的时候主要是 Ubuntu / Debian,自己有记录一份 Ubuntu / Debian 下 Aria2 的编译安装流程~不过 CentOS 的话,因为是 RedHat 系,所以有蛮多不一样的地方,于是就一边摸索一边记录下来了~

Continue reading 在 CentOS 7 中编译安装 Aria2

Go net/http 与 nodejs Koa 的 Σ(・□・;)

最近跟猫猫正在写一个一起听音乐的项目「側に」,然后昨天晚上就在一起通宵写代码的说~我对 stream 和 P2P 也了解得蛮少的,就跟着猫猫一起一边学一边写~是 Pair Programming (/ω\)!

猫猫找到了一个 Go 里面的 P2P 的库 libp2p。于是一番商量之后,打算先写一个可以通过 P2P 方式相互连接的小 demo 出来~猫猫跑去用 nodejs 写服务器端的 tracker,我则是跑去研究这个库里的 example 写 client 出来~

一边开着 TeamViewer 一起听歌,一边开了几个 VSCode 的 Live Share 开始写~其实到这里都还好喵,还是蛮顺利的!然而在测试的时候发现, Go 这边一 POST JSON 数据过去,那边服务器就 throw error 了 Σ(・□・;)

一开始的时候在想为什么 Koa 一直没有收到数据,后来用「手动挡」发现,自己给 Koa addListener 的话,的确数据都是正常的样子><

然后就陷入了沉思,莫非是又遇到了什么神奇的坑了么(゚o゚;; 而且猫猫那边用 nodejs axios 发给 server 却又都是正常的……咕噜咕噜呜(>﹏<)

啊,这实在是不够科学!于是找猫猫要了一下 tracker 部分的代码喵,在本地跑了一下之后发现,这边本地 POST 也炸了!

炸了之后跟猫猫怀疑是不是 Go 里面 net/http POST 的时候默认 gzip 了,但是 Content-Length 又的确是原始的 JSON 字符串的长度的说……

Continue reading Go net/http 与 nodejs Koa 的 Σ(・□・;)

WebP Server in Rust

Generate WebP images for JPG / PNG files on-the-fly with Rust! BlueCocoa/webp_server_rs

Speaking of switching to WebP image, at the first glance, I just did it in a very naive approach.

Then @Nova wrote a Node.JS server that can serve JPG/PNGs as WebP format on-the-fly. You can find that at n0vad3v/webp_server.

A few days ago, @Nova and @Benny rewrite the WebP Server in Golang, webp-sh/webp_server_go

And that looks really promising, the size of the webp server, according to its description, had greatly reduced from 43 MB to 15 MB, and it is a single binary instead of webp_server with node_modules.

I cloned that project and added a tiny feature. However, I just found that although it is absolutely easy to implement the tiny feature, there is a potential design issue with the `fasthttp` module. In order to get everything work, it took me about 4 hours to debug on it.

Finally, it turned out to be a pointer of an internal variable (ctx.Request.uri, or so) was directly returned from ctx.Path(), and if users invoke ctx.SendFile(filepath), the ctx.Request.uri will be set to filepath, which will also propagate to all variables that hold the shared value of ctx.Path(). You may visit my previous blog post for details.

Well, in aforementioned blog post, I said that it would be better if it was written in Rust. Now, let's make it come true and push the WebP server even further.

There are some comparisons among n0vad3v/webp_server, webp-sh/webp_server_go and BlueCocoa/webp_server_rs.

As for size,

  • webp_server(nodejs) with node_modules takes 43 MB
  • webp-server(go) has reduced to 15 MB, and it's single binary
  • webp-server(Rust) pushes that even further, only 3.6 MB on macOS and 6.4 MB on Linux

In terms of convenience, you can just download the binary file and run if you choose either webp-server(go) or webp-server(Rust). However, webp_server(nodejs) requires pm2 to run.

Performance, to be honest, I haven't got time to run some benchmarks on them. But IMHO it (webp-server(Rust)) should be as fast as golang version.

记 Golang 下遇到的一回「毫无由头」的内存更改

前两天看到 @Nova Kwok@BennyThink 做了一个 WebP Server,于是 clone 下来玩了一下,发现貌似没有做“原始图像更新后,重新生成相应的 WebP 图像”的功能。好的,这个说起来简单,做起来也很简单,就是 os.Stat 一下,然后取到图像最后修改时间的 UNIX timstamp,STAT.ModTime().Unix(),最后再跟先前生成好的 WebP 文件名比较一下就好(timestamp 会放在生成的 WebP 文件名里)。

上面为止真的都很简单,在 macOS 上测试了一下,看起来没问题~于是就提交 Pull Request

然而 Nova 告诉我说,

Nice PR, but there seems a little problem that the older converted images are not deleted after the change of the original image, this might cause a possible leakage of the original one's content.

显然我是一头雾水,一开始还以为自己提交 PR 的时候是不是手滑删掉了几行,检查了一下之后发现并没有!然后姑且先把 macOS 上测试的截图 comment 在了 PR 下面。

接着我估计 Nova 应该是在 Linux 下跑的测试,于是就在一台新的 VPS 上安装了 go,把我 fork 且修改过的那份代码 clone 在 VPS 里测试。本来我预估的时候要么是我搞错了文件,要么也许是 Nova 不小心用了以前编译好的文件。然后一测试我就惊了,居然真的没有删除以前生成的 WebP 图像 Σ(・□・;)?!

由于没有 Linux 机器,也懒得安装虚拟机了,只能一头雾水的在 VPS 用 fmt.Println 输出来简易 debug 了。根据 fmt.Println 的输出,发现 ImgName 不知道为什么就突然之间被改了!

[1]ImgName: webp_server.png
[2]ImgName: webp_server.png
[3]ImgName: webp_server.png
[4]ImgName: webp_server.png
[5]ImgName: root/webp_serve

上面是在 5 处不同的地方 fmt.Println("ImgName", ImgName) 的输出,虽然我放了这么多,但是实际上在代码里 ImgName 在其作用域内只有过一次赋值,

ImgPath := c.Path()
// ... 略去 10 行左右判断文件扩展名的代码
ImgName := path.Base(ImgPath)

然后就没写过了,仅有读的操作,没有任何赋值,中间只有一次被用来当作 Sprintf 的一个参数

WebpImgPath := fmt.Sprintf("%s/%s.%d.webp", DirPath, ImgName, ModifiedTime)

但显然这个也不会更改 ImgName 的内存嘛。“这不科学!” 虽然想这么叫出来,但是想想这个肯定还是有原因的!

Continue reading 记 Golang 下遇到的一回「毫无由头」的内存更改

「不可愛」清單

昨天和今天看到推特上有在玩把人加入到「可愛」清單的遊戲,我自己的話,既沒有被任何人加到這樣的清單裡,也並不在意這些的說,但是剛有看到 @FiveYellowMicepost 說 ——

于是黄鼠有想做类似的事情,可是在考虑了一下之后,觉得还是放弃会比较好了。

因为有着被加入清单的人,就意味有没有被加入清单的人。被加入的人会很开心,可是对于没有加入的人而言,这样就是明显的排除和拒绝了。不在意黄鼠的人当然不会去在意自己有没有被加入,可是如果是在意黄鼠的人,没有被加入清单一定会是一个打击吧。也许这个打击不会很大,因为黄鼠大概也不会有那么重要,也许会很在意黄鼠的看法的人根本不存在。但是那是黄鼠没有获知的事情,万一在意黄鼠的看法的人是存在的,或者在未来的某一天出现了,那样的话,黄鼠不想要让他们觉得自己被排除和拒绝了呢。

那麼既然建立類似於「白名單」的可能會對在意自己的看法的人造成傷害的話,不如建立一個「黑名單」好啦!

於是咕嚕咕嚕地跑去創建了一個空白的「不可愛」清單,然後裡面一個人都沒有~!

不知道為什麼,突然間就想到了內田彩的「Merry Go」裡的一段歌詞

何だって 裏表だね
為什麼會有表裡存在
裏だって 表なら
如果說裡是表
表だったさ 裏なんです!
那表也是裡了呀(・8・)
幸せの反対も
就算在幸福的背面
回せば幸せに姿を変える
只要轉身就能遇見幸福
悲しみの背中には
悲傷的背後
喜びの翼が生えてるの
也有喜悅的翅膀在生長

从零开始的 Rust 学习笔记(19) —— Rewrite insert_dylib in Rust

最近鹹魚了蠻長一段時間,發現大約有一個多月沒有寫這個系列了,今天繼續學習 Rust 好啦!雖然有在看「The Rust Programming Language」,但是還是得寫寫的~想了一會兒之後,決定把在「另一种方法获取 macOS 网易云音乐的正在播放」裡用過的 insert_dylib 用 Rust 重寫一下(^O^)/

insert_dylib 本身來說並不複雜,但因為不像 C/C++/Objective-C 裡那樣可以直接 #import <mach-o/loader.h> 等,於是 MachO 的一些 struct 就需要自己在 Rust 中重寫一遍~

當然,實際上也可以用 Rust 寫個 Parser,然後去 parse 這些 header 文件,並且自動生成 Rust 的 struct。可是我太懶了,留到下次試試看好啦(咕咕咕) 這次的就放在 GitHub 上了,BlueCocoa/insert_dylib_rs

不過需要注意的就是有個 BigEndian 和 LittleEndian 的問題,不同的 MachO 使用的可能不一樣,因此就增加了一個 swap_bytes! 的 macro 和一個 FixMachOStructEndian 的 trait

src/macho/macho.rs 裡隨機選一個 struct 出來展示的話,大約就是如下這樣子

use super::prelude::*;

macro_rules! swap_bytes {
    ($self:ident, $field_name:ident) => {
        $self.$field_name = $self.$field_name.swap_bytes();
    };
}

pub trait FixMachOStructEndian {
    fn fix_endian(&mut self);
}

#[derive(Debug)]
pub struct SymtabCommand {
    pub cmd: u32,
    pub cmdsize: u32,
    pub symoff: u32,
    pub nsyms: u32,
    pub stroff: u32,
    pub strsize: u32,
}

impl SymtabCommand {
    pub fn from(buffer: [u8; 24], is_little_endian: bool) -> SymtabCommand {
        let sc_buffer: [u32; 6] =
            unsafe { std::mem::transmute_copy::<[u8; 24], [u32; 6]>(&buffer) };
        let mut symtab_command = SymtabCommand {
            cmd: sc_buffer[0],
            cmdsize: sc_buffer[1],
            symoff: sc_buffer[2],
            nsyms: sc_buffer[3],
            stroff: sc_buffer[4],
            strsize: sc_buffer[5],
        };

        if is_little_endian {
            symtab_command.fix_endian();
        }

        symtab_command
    }

    pub fn to_u8(&self) -> [u8; 24] {
        let mut data: [u32; 6] = [0u32; 6];
        data[0] = self.cmd;
        data[1] = self.cmdsize;
        data[2] = self.symoff;
        data[3] = self.nsyms;
        data[4] = self.stroff;
        data[5] = self.strsize;

        unsafe { std::mem::transmute_copy::<[u32; 6], [u8; 24]>(&data) }
    }
}

impl FixMachOStructEndian for SymtabCommand {
    fn fix_endian(&mut self) {
        swap_bytes!(self, cmd);
        swap_bytes!(self, cmdsize);
        swap_bytes!(self, symoff);
        swap_bytes!(self, nsyms);
        swap_bytes!(self, stroff);
        swap_bytes!(self, strsize);
    }
}
Continue reading 从零开始的 Rust 学习笔记(19) —— Rewrite insert_dylib in Rust

另一种方法获取 macOS 网易云音乐的正在播放

虽然标题里面写的是“另一种”,但是先前的方法其实不是我写的?而是来自可爱少女 Makito 的两篇 post ——

于是就看到了直接从 Mach 内核入手的方法,是賢い、かわいい Makito~!不过今天跑去 clone 代码下来尝试的时候似乎会 crash 的样子,毕竟距离上次 update 代码也过去了 9 个月左右了,猜想可能是网易云音乐有所修改导致(在我用别的方法尝试的时候,也是莫名 crash 了)

于是这里就写一个另一种获取 macOS 网易云音乐的正在播放的方法吧~

Continue reading 另一种方法获取 macOS 网易云音乐的正在播放

Copy List with Random Pointer

A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null.

Return a deep copy of the list.

The Linked List is represented in the input/output as a list of n nodes. Each node is represented as a pair of [val, random_index] where:

  • val: an integer representing Node.val
  • random_index: the index of the node (range from 0 to n-1) where random pointer points to, or null if it does not point to any node.

Example 1:

Input: head = [[7,null],[13,0],[11,4],[10,2],[1,0]]
Output: [[7,null],[13,0],[11,4],[10,2],[1,0]]

Example 2:

Input: head = [[1,1],[2,1]]
Output: [[1,1],[2,1]]

Example 3:

Input: head = [[3,null],[3,0],[3,null]]
Output: [[3,null],[3,0],[3,null]]

Example 4:

Input: head = []
Output: []
Explanation: Given linked list is empty (null pointer), so return null.

Constraints:

  • -10000 <= Node.val <= 10000
  • Node.random is null or pointing to a node in the linked list.
  • Number of Nodes will not exceed 1000.
Continue reading Copy List with Random Pointer

経験値上昇中☆