golang

Understanding golang Reader/Writer

Original post at Medium - Understanding golang Reader/Writer

Reader/Writer are basic interfaces designed in Golang. For example, if a struct have a function like:

type Example struct {  
}
func (e *Example) Write(p byte[]) (n int, err error) {  
}
func (e *Example) Read(p byte[]) (n int, err error) {  
}

We will say Example implements Read/Write interface, and if we want do a force type conversions, we will see Example is type of Reader&Writer

reader, err := example.(Reader)  
if err != nil {  
    // Shouldn't throw an error here.
}

But how Reader/Writer works ?

For example, File struct already implement the Read/Write function then call the syscall read/write function read the data from file.

func (f *File) Read(b []byte) (n int, err error) {  
    if err := f.checkValid("read"); err != nil {
        return 0, err
    }
    n, e := f.read(b)
    if n == 0 && len(b) > 0 && e == nil {
        return 0, io.EOF
    }
    if e != nil {
        err = &PathError{"read", f.name, e}
    }
    return n, err
}
And another example here is  
type Example struct {  
    r File
}
func (e *Example) Read(p []byte)(n int, err error) {  
    return e.f.Read(p)
}

This is an example see how do we pass the Reader/Writer from one struct to another, when example instance initialized with a File , example can read the data what f get and example still be a type of Reader.So we can build a Reader chain like

type A struct {  
    b B
}
type B struct {  
    c C
}
func (a *A) call() (r Reader, err error) {  
    return a.b.c
}
// If A,B,C all implement Read function, then when we call 
// a.call function will get an C instance which is type of Reader.

So how to design the API for Reader/Writer struct ?

If we want build a chain of Reader/Writer and has a friendly API for others, how can we do ? In golang source code file we can see two type of API design for example:

func (a *A) call(in Writer) (out Writer, err error) {}  
or  
func (b *B) call(out Writer) (in Writer, err error) {}  

First, there is no best practice for the API design, when you call the function you will be get two types of way,

// A
out, err := a.call(w)  
io.Copy(w, reader)  
// B
in, err := a.call(w)  
io.Copy(in, reader)  

hmm, it seems the exampleA is a very common way when we design the API, we apply parameter and then get the result. But has the pointer support just like C-lang, so we can have solution B. But B is used every where inside of golang, so when do we need use A. In my opinion, when we want write some describe data or meta data for content data, we need use A because inside of call function Writer/w is already initialized and we can access all the functions, props it has.

Conclusion

Reader/Writer are very important in golang, understanding how it works is very important and a friendly API is also a important things.

All this is my personal thoughts.

Refs:

  1. https://golang.org/pkg/io/#Copy
  2. https://github.com/xeodou/aesf

AES 文件加密

在之前的一份工作中有型参与设计了一款由 React.JS + Electron.JS + Golang 开发的跨平台桌面应用,由于软件需求的特殊性,对数据安全由特殊的需求,虽然在最终实现的方式是使用 GPG 非对称加密的方式对数据加密,但是对于如何用对称加密的方式保证数据的安全也做了一些研究,而最近才有时间用 Golang 实现一个简单 golang library。

上文提到的桌面应用的设计可以看我之前写的一篇博客 缅甸大选中的安全桌面应用设计

加密与解密

通常我们使用 AES 进行数据加密的时候,使用一个 key 来加密一整个明文后得到密文。一般情况下如果我们不知道 key(密钥)是无法解除明文的,但是随着计算机的技术的发展以及 GPU 和 CPU 运算能力的提高,计算机已经可以通过穷举的方式破解 AES-128 的加密方式了,AES-128 是指密钥长度为 16 bytes 的AES 加密方式,AES 支持 16、24、32 密钥长度加密,分别对应 AES-128、AES-196、AES-256 三种加密方式,密钥的长度越长期安全性越高,但是所需加密和解密的时间也就越长。

而单一的 key(密钥)也会带来存储的安全性问题,这时通过某种方法产生密钥就是必要的了,也就是通常说的 key derivation function,如:

dk := pbkdf2.Key([]byte(password), []byte(salt), 4096, 34, sha1.New)  

通过 KDF 方法通过密钥和一个随机值(salt),产生特定长度的数据。这时候我们用一部份的 dk[:16] 做密钥,第二部分做分组加密的密钥。

block, _ := aes.NewCipher(dk[:16])  
stream := cipher.NewCTR(block, dk[16:32])  

这时需要加密的明文通过上述的方法可以产生相同长度的密文。

密码验证与数据一致性

当我们通过密钥来解密一段密文时,我们需要通过某种方法来验证密钥的有效性,这时就需要通过我们设计的特殊密文结构来验证。

密文结构

密文主要由 x bytes 的随机值(salt)、2 bytes 的密钥验证块、明文等长的密文、10 bytes 的签名组成。当我们需要验证密码时,我们就再使用一次加密时用的 KDF 方法产生特定长度 dk,如上述的例子中,dk[32:34]就是我们的密码验证块,当解密时产生的 2 bytes 与密文中的数据一致时,我们初步判定密钥时有效的。这时我们就会执行密文的解密工作。

dk := aesf.pbkdf2Key(header[:8])  
if !bytes.Equal(header[8:], dk[32:]) {  
    return nil, ErrBadPassword
}
block, _ := aes.NewCipher(dk[:16])  
stream := cipher.NewCTR(block, dk[16:32])  

签名字段是我们在我们加密和解密明文时对整个明文进行 sha1HMAC 计算产生值的前10 个bytes,当我们在解密时如果发现密文签名用的签名块不一致时,我们就会认为密文已经被篡改,此时应该停止解密并清除解密后的数据。

结论

对于 AES 加密算法来说安全性确实比 GPG 等非对称加密要弱许多,但是当我们将其与 KDF 和 sha1HMAC 签名算法结合在一起后,也不失为一个快速简单的加密算法,golang 实现的加密库可以在我的 github 上看到 https://github.com/xeodou/aesf

refs:
1: AES - 高级加密标准
2: 分组密码工作模式
3: kdf
4: rfc2898
5: aesf

Golang 开发跨平台SDK(iOS, Android),Part 2

    上一篇简单的介绍了下Golang Mobile的工作原理,这一篇讲介绍如何使用Golang 做一个跨平台的RSS SDK。

RSS和HTTP

    RSS是基于XML标准,在互联网上被广泛采用的内容包装和投递协议,主要用来描述和同步网站内容。既然RSS是一种网络协议,那我们就需要使用Golang的http模块了。整个流程可以归结为,RSS的SDK提供一个方法去请求RSS源获得数据,然后通过callback获得数据,而这些数据最好是Android/iOS都可以直接使用的model集合。

    作为一个http请求在移动端最好是在子进程中执行,通过异步回调返回给主进程或UI 进程,这其中带来的明显好处就是我们的http任务不会阻塞整个UI,用户在请求RSS源获取最新网站内容的同时依然可以继续对应用进行操作。Golang 本身并没有提供一个异步执行http的库,但是我们可以利用Golang routine 简单的封装一个异步执行http的库。

func (t *Task) Runtask() {  
    r := t.create()      // 这里create是创建channel
    go func() {
        res, err := http.Get(t.Url)
        if err != nil {
            r.err <- err
        } else {
            buf, err := ioutil.ReadAll(res.Body)
            if err != nil {
                r.err <- err
            } else {
                r.data <- buf
            }
        }
    }()
}

上面的代码段摘自我实现的库go-async,其原理就是将http任务在 golang routine中去执行,当执行结束是通过channel 返回。

接口

    通过简单的继承上面的go-async我们就可以实现一个简单的RSS http请求。

func RSS() string {  
    return "http://rss.cnbeta.com/rss"
}
type Listener interface {  
    OnSuccess(buf []byte)
    OnFailure(err string)
    OnEnd()
}
type CnBeta struct {  
    listener Listener
}
func (cb *CnBeta) Run() {  
    t := async.NewTask(RSS(), cb)
    t.Runtask()
}
func (cb *CnBeta) Success(buf []byte) {  
}

func (cb *CnBeta) Failure(err error) {  
}

因为Golang现在不支持倒出constant类型的数据结构,所以我们需要定义一个function,然后利用function的返回值取得这个常量。SuccessFailure是从go-async继承的方法,当异步http callback的时候就会执行其中一个方法。Listener是定义的一个外部接口,当然在Java/swift/Objective-C中实现这个接口在应用中拿到异步callback的数据。

Model

    上面我实现了请求RSS源并且异步获取了byte数据,但是我们的目标是设计一个SDk,只提供方法并不能满足一个完整的SDK的需要,SDK需要接口返回的是Model或者Model的集合。

func NewRssXml() *RssXml {  
    return &RssXml{
        Channel: &RssFeed{},
    }
}

type RssXml struct {  
    Version string
    Channel *RssFeed
}

type RssFeed struct {  
    Title       string
    Link        string
    Description string
    Language    string
    Copyright   string
    PubDate     string

    Items []*RssItem
}

type RssItem struct {  
    Title       string
    Link        string
    Description string
    PubDate     string
}

func (rss *RssXml) Feed() *RssFeed {  
    return rss.Channel
}

上面就是使用Golang实现的RSS的model,由于Golang mobile 本身的限制只能export基本的数据类型,所以我只export需要的类型,但是我也想要直接export Items []*RssItem,这时我们可以通过实现一个类型方法来实现

type RssFeed struct {  
    Title       string
    Link        string
    Description string
    Language    string
    Copyright   string
    PubDate     string

    items []*RssItem
}

func (feed *RssFeed) Length() int64 {  
    return int64(len(feed.items))
}

func (feed *RssFeed) Item(p int64) *RssItem {  
    if p >= feed.Length() {
        return &RssItem{}
    }
    return feed.items[p]
}

通过 rssfeed.Length() 我们获得总共有多少RssItem,而通过rssfeed.Item(1) 我们变可以得到第二个RssItem,变相的实现了直接使用rssfee.Items[1]

整合

    实现了Rss Model这时我们便可以将接口改写下

type Listener interface {  
    OnSuccess(rss *RssXml)
    OnFailure(err string)
    OnEnd()
}

func (cb *CnBeta) Success(buf []byte) {  
    doc := xmlx.New()
    if err := doc.LoadBytes(buf, nil); err != nil {
        cb.Failure(err)
        return
    }
    cb.listener.OnSuccess(parser(doc))
    cb.listener.OnEnd()
}

func (cb *CnBeta) Failure(err error) {  
    cb.listener.OnFailure(err.Error())
    cb.listener.OnEnd()
}

这是一整个SDK 便是完成了,但是最关键的部分是编译后在应用开发时引入,以iOS为例通过前一篇的方法我们得到了rss.framework,这时我们只要通过XCode将这个rss.framework引入到项目中便可以使用了。

func loadRss() {  
    let cnbeta = GoCbNewCnBeta(self)
    cnbeta.run()
}

// MARK: - cnbeta lisener interface
func onEnd() {  
}

func onFailure(err: String!) {  
}

func onSuccess(rss: GoCbRssXml!) {  
    cbRss = rss

    dispatch_async(dispatch_get_main_queue(), {

        if (self.refreshControl!.refreshing) {
            self.refreshControl!.endRefreshing()
        }
        self.tableView.reloadData()
    })
}

下面的截图便是我写的一个Demo demo

结论

    作为一个跨平台的语言,Golang拥有其特有的优势,编译成机器码带来的高效的性能,而且作为一个编写SDK或者数据处理层的工具,其高效的开发效率也是值得考虑的。至于能不能用在production的项目上,我想说Google都在用。

refs

Golang 开发跨平台SDK(iOS, Android),Part 1

前一篇介绍了用React-Native做跨平台开发,其实Golang在1.5发布以后也是支持做移动端的跨平台开发的,Golang的移动端开发支持两种模式,一种是使用Golang开发整个应用,另外一种便是使用Golang开发common library。这两种各有优缺点,前者没有完善的UI库,如用来开发一个完整的应用需要的工作量着实有点不小,或者用来开发游戏可能也是一个不错的选择,亦或者寄望于Google可以开发出完善的UI库,至于后者想对于前者就方便多了,Google的GoMobile项目已经完善了大部分的工作,现在的缺点就是支持的数据类型还是远远不够,而且现只支持ARM架构。所以现阶段二者都还有些限制,但是作为一个跨平台的备选方案还是有其可取之处的。

How it works

在Google GoMobile的项目里Google提供了一个工具gobindgobind可以生成对于Java和Objective-C的bindings。下面是一段Golang代码

package mypkg

type Counter struct {  
    Value int
}

func (c *Counter) Inc() { c.Value++ }

func New() *Counter { return &Counter{ 5 } }  

生成的Java Bindings是

public abstract class Mypkg {  
    private Mypkg() {}
    public static final class Counter {
        public void Inc();
        public long GetValue();
        public void SetValue(long value);
    }
    public static Counter New();
}

Objective-C 代码如下:

@class GoMypkgCounter;

@interface GoMypkgCounter : NSObject {
}

@property(strong, readonly) GoSeqRef *ref;
- (void)Inc;
- (int64_t)Value;
- (void)setValue:(int64_t)v;
@end

FOUNDATION_EXPORT GoMypkgCounter* GoMypkgNewCounter();  

其实简单的来说就是将Golang翻译成Java和Objective-C,只不过bindings只是提供访问Golang编译后的二进制文件的接口。

How to build it

比如说你有个一个test.go文件

package mypkg  
type Counter struct {  
    Value int
}
func (c *Counter) Inc() { c.Value++ }  
func New() *Counter { return &Counter{ 5 } }  

1.在编译Android的library时可以使用
ANDROID_HOME=your_android_sdk_path gomobile bind -target=android -o file_path_for_android_library.aar -v test.go
这段代码会生成一个Java的二进制libray file_path_for_android_library.aar,其中包涵了ARM架构的.so二进制文件,.jniJava bindings classes等文件,在Android的项目中直接引用这个aar这个文件就可以用了。

Counter c = Mypkg.New();  
c.Inc();  
c.GetValue();  


2.在编译iOS的library时可以使用
gomobile bind -target=iOS -o file_path_for_android_library.framework -v test.go
这段代码会生成一个iOS可用的framework库,然后在iOS的项目中引用这个file_path_for_android_library.framework就可以了。

GoMypkgCounter *c = [GoMypkgCounter init];  
c.Inc();  
c.Value();  

更多信息可以看 https://godoc.org/golang.org/x/mobile/cmd/gobind

一个简单的问题

最近一直忙于缅甸大选的投票系统,做Windows客户端给缅甸的人民用。由于种种原因我们用SQLCipher做客户端的数据库,Golang做中间的数据层。
我们的SQLCipher的Golang驱动是通过SQLCipher和SQLite驱动修改来的,中间用了很多cgo的混合编码,其中就有著名的加密库OpenSSL。 编译好的exe文件直接执行的话会报missing libeay32.dll的错误。error 之前我们的做法是把编译后的libeay.ddl放到系统目录C:/windows/system32文件下,这个文件下的所有dll文件都是可以被任何应用共享的,问题自然就解决了。但是有一个问题是需要管理员确认才能将文件复制到该目录,所以我们就需要一个window installer去复制文件,其中还需要用户确认。现在我们的需求是解决这个问题,在这个项目之前我从来没有任何windows的经验,所有的经验都是以前用windows和google 来的。所以我搜索golang load dll go use static library等等,最后我才在这里找到了https://forum.qt.io/topic/17705/use-local-dll-instead-of-system32-s-dll

Windows searches for libraries in the following sequence:

The directory where the executable module for the current process is located.  
 * The current directory.
 * The Windows system directory. 
 * The Windows directory. The GetWindowsDirectory function retrieves the path of this directory.
 * The directories listed in the PATH environment variable.

原来windows加载dll的优先级是 1.找可执行文件所在目录
2.找执行目录
3.GetSystemDirectory返回的目录
4.环境变量里面定义的路径

-)